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

View File

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

View File

@@ -12,6 +12,7 @@ import androidx.core.os.bundleOf
import androidx.lifecycle.*
import androidx.navigation.NavOptions
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
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.Course
import de.sebse.fuplanner2.database.User
import de.sebse.fuplanner2.utils.console
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
import de.sebse.fuplanner2.databinding.ActivityMainBinding
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -31,30 +31,33 @@ class MainActivity() : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var activityViewModel: MainActivityViewModel
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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.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_subtitle).text = it?.email
}
}
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(
this@MainActivity,
if (it == 0) R.drawable.ic_menu_notifications_none else R.drawable.ic_menu_notifications
)
actionView = if (it == 0)
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) {
in 0..99 -> it.toString()
else -> "99+"
@@ -63,23 +66,23 @@ class MainActivity() : AppCompatActivity() {
}
}
activityViewModel.latestSemester.observe(this) {
val courseOrder = nav_view.menu.findItem(R.id.nav_courses).order
var i = nav_view.menu.size() - 1
val courseOrder = binding.navView.menu.findItem(R.id.nav_courses).order
var i = binding.navView.menu.size() - 1
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) {
nav_view.menu.removeItem(menuItem.itemId)
binding.navView.menu.removeItem(menuItem.itemId)
}
}
it.mapIndexed { index, course ->
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(
R.id.course_details,
bundleOf("courseId" to course.uid, "title" to course.title),
NavOptions.Builder().setPopUpTo(R.id.nav_courses, false).build()
)
drawer_layout.closeDrawers()
binding.drawerLayout.closeDrawers()
false
}
}
@@ -87,10 +90,10 @@ class MainActivity() : AppCompatActivity() {
appBarConfiguration = AppBarConfiguration(
setOf(R.id.nav_courses, R.id.nav_canteen, R.id.nav_schedule, R.id.nav_notifications),
drawer_layout
binding.drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
nav_view.setupWithNavController(navController)
binding.navView.setupWithNavController(navController)
if (intent.getBooleanExtra(EXTRA_OPEN_NOTIFICATIONS, false)) {
navController.navigate(R.id.nav_notifications)
@@ -138,6 +141,7 @@ class MainActivityViewModel : ViewModel() {
val notificationCnt: LiveData<Int> = database.notificationDao().getUnreadRowCount()
val latestSemester: LiveData<List<Course>> = database.courseDao().getLatestSemester()
@DelicateCoroutinesApi
fun updateSelectedUser(account: Account) {
GlobalScope.launch {
user.postValue(database.userDao().findByUsername(account.name))

View File

@@ -29,7 +29,7 @@ abstract class FUAuthModule {
if (response.networkResponse.statusCode == 200) {
return parseResponse(response.body)
} else {
val relLocation = response.headers["Location"]
val relLocation = response.headers?.get("Location")
?: throw invalidResponse(100110, "No IDP form location!")
val formUri = URI(samlUrl).resolve(relLocation).toString()
requester.head(formUri, getCookies(user))
@@ -72,8 +72,8 @@ abstract class FUAuthModule {
}
private fun updateCookies(user: User, response: NetData) {
val setCookies = parseCookies(response.networkResponse.allHeaders)
setCookies["JSESSIONID"]?.let {
val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
setCookies?.get("JSESSIONID")?.let {
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) {
val requester = Requester(ctx)
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!")
if (!samlUri.startsWith(RESTORE_SESSION_URI)) {
@@ -56,13 +56,13 @@ object Blackboard: FUAuthModule() {
updateCookies(user, response)
// Finish BB
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)
)
}
// Start Session
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)
)
@@ -88,19 +88,19 @@ object Blackboard: FUAuthModule() {
}
private fun updateCookies(user: User, response: NetData) {
val setCookies = parseCookies(response.networkResponse.allHeaders)
setCookies["JSESSIONID"]?.let {
val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
setCookies?.get("JSESSIONID")?.let {
user.cookies.bbJsessionId = it
}
setCookies["session_id"]?.let {
setCookies?.get("session_id")?.let {
user.cookies.bbSessionId = it
}
setCookies["s_session_id"]?.let {
setCookies?.get("s_session_id")?.let {
user.cookies.bbSSessionId = it
}
setCookies
.filter{ (key, _) -> key.startsWith("_shibsession_") }
.forEach { (key, value) ->
?.filter{ (key, _) -> key.startsWith("_shibsession_") }
?.forEach { (key, value) ->
user.cookies.bbShibKey = key
user.cookies.bbShibValue = value
return@forEach

View File

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

View File

@@ -72,7 +72,7 @@ class CustomRequest: Request<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))
}

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ import de.sebse.fuplanner2.database.Announcement
import de.sebse.fuplanner2.database.AppDatabase
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() {

View File

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

View File

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

View File

@@ -10,7 +10,8 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import de.sebse.fuplanner2.R
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.launch
@@ -19,6 +20,7 @@ class NotificationFragment : Fragment() {
private lateinit var viewModel: NotificationViewModel
private lateinit var navController: NavController
private lateinit var binding: FragmentRecyclerBinding
init {
setHasOptionsMenu(true)
@@ -43,7 +45,7 @@ class NotificationFragment : Fragment() {
})
return inflater.inflate(R.layout.fragment_recycler, container, false).apply {
recycler_view.apply {
binding.recyclerView.apply {
//setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter

View File

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

View File

@@ -40,7 +40,7 @@ object Whiteboard: FUAuthModule() {
override suspend fun login(ctx: Context, name: String, password: String, user: User) {
val requester = Requester(ctx)
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!")
if (samlUri == RESTORE_ON_REDIRECT_TO) {
updateCookies(user, request)
@@ -60,7 +60,7 @@ object Whiteboard: FUAuthModule() {
updateCookies(user, response)
// Finish BB & Start Session
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)
)
@@ -77,13 +77,13 @@ object Whiteboard: FUAuthModule() {
}
private fun updateCookies(user: User, response: NetData) {
val setCookies = parseCookies(response.networkResponse.allHeaders)
setCookies["JSESSIONID"]?.let {
val setCookies = response.networkResponse.allHeaders?.let { parseCookies(it) }
setCookies?.get("JSESSIONID")?.let {
user.cookies.wbJsessionId = it
}
setCookies
.filter{ (key, _) -> key.startsWith("_shibsession_") }
.forEach { (key, value) ->
?.filter{ (key, _) -> key.startsWith("_shibsession_") }
?.forEach { (key, value) ->
user.cookies.wbShibKey = key
user.cookies.wbShibValue = value
return@forEach

View File

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

View File

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

View File

@@ -11,19 +11,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:columnGap="0dp"
app:headerRowPadding="12dp"
app:headerPadding="12dp"
app:hourHeight="45dp"
app:maxHour="20"
app:minHour="7"
app:numberOfVisibleDays="5"
app:showCompleteDay="false"
app:showDistinctPastFutureColor="true"
app:timeColumnPadding="8dp"
app:timeColumnTextSize="10sp"
app:daySeparatorColor="@color/scheduleSeparator"
app:futureBackgroundColor="@color/scheduleFutureBackground"
app:headerRowBackgroundColor="@color/scheduleNavBackground"
app:headerRowTextColor="@color/scheduleOtherText"
app:headerBackgroundColor="@color/scheduleNavBackground"
app:headerTextColor="@color/scheduleOtherText"
app:hourSeparatorColor="@color/scheduleSeparator"
app:pastBackgroundColor="@color/schedulePastBackground"
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.
buildscript {
ext.kotlin_version = '1.3.71'
ext.kotlin_version = '1.5.31'
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
}
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 '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
// in the individual module build.gradle files
@@ -19,7 +20,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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