StwParser.kt now collects all its data before adding everything to the database all at once, fixing problems where LiveData would update before all transactions were finished; ViewPagers have been updated to use Adapters extending FragmentStateAdapter instead of RecyclerView.Adapter

This commit is contained in:
denizk0461
2023-04-27 21:58:10 +02:00
parent ccd77a5daf
commit ce7b854e8c
15 changed files with 373 additions and 164 deletions
+1 -1
View File
@@ -8,10 +8,10 @@
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintStringFormatTrivial" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="AndroidLintStringFormatTrivial" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintTypos" enabled="true" level="TYPO" enabled_by_default="true" /> <inspection_tool class="AndroidLintTypos" enabled="true" level="TYPO" enabled_by_default="true" />
<inspection_tool class="BooleanParameter" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="Destructure" enabled="true" level="INFO" enabled_by_default="true" /> <inspection_tool class="Destructure" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="DirectUseOfResultType" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> <inspection_tool class="DirectUseOfResultType" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="KDocMissingDocumentation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NoConstructorMigration" enabled="true" level="ERROR" enabled_by_default="true" /> <inspection_tool class="NoConstructorMigration" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NonExhaustiveWhenStatementMigration" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="NonExhaustiveWhenStatementMigration" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicApiImplicitType" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> <inspection_tool class="PublicApiImplicitType" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
@@ -49,6 +49,15 @@ class CanteenOfferItemAdapter(
// Retrieve item for current position // Retrieve item for current position
val currentItem = offers[position] val currentItem = offers[position]
/*
* Children of the line container MUST be removed before any are added, as otherwise, during
* recycling, lines will be duplicated when they are scrolled off the visible screen area
* and then scrolled onto the screen again. In other terms, if removeAllViews() wasn't
* called and the user was to scroll down a list and then back up again, the items at the
* top of the list would be added again, creating duplicates.
*/
holder.binding.lineContainer.removeAllViews()
// If an item has been added to mark that there are no offers available, tell the user // If an item has been added to mark that there are no offers available, tell the user
if (currentItem.category == "NO\$ITEMS") { if (currentItem.category == "NO\$ITEMS") {
@@ -1,16 +1,15 @@
package com.denizk0461.studip.adapter package com.denizk0461.studip.adapter
import android.view.LayoutInflater import androidx.fragment.app.Fragment
import android.view.ViewGroup import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.recyclerview.widget.RecyclerView import com.denizk0461.studip.fragment.CanteenPageFragment
import com.denizk0461.studip.databinding.ItemScrollablePageBinding
import com.denizk0461.studip.model.CanteenOfferGroup import com.denizk0461.studip.model.CanteenOfferGroup
/** /**
* Custom RecyclerView adapter for managing multiple pages of canteen offers in a RecyclerView or * Custom ViewPager adapter for managing multiple pages of canteen offers.
* ViewPager.
* *
* @param fragmentActivity parent fragment activity
* @param offers all offers for a given canteen, grouped by category (not filtered by day at * @param offers all offers for a given canteen, grouped by category (not filtered by day at
* this point) * this point)
* @param daysCovered tells for how many days offers are available for. * @param daysCovered tells for how many days offers are available for.
@@ -20,54 +19,23 @@ import com.denizk0461.studip.model.CanteenOfferGroup
* @param displayAllergens whether the user wants allergens to be marked * @param displayAllergens whether the user wants allergens to be marked
*/ */
class CanteenOfferPageAdapter( class CanteenOfferPageAdapter(
fragmentActivity: FragmentActivity,
private var offers: List<CanteenOfferGroup>, private var offers: List<CanteenOfferGroup>,
private var daysCovered: Int, private var daysCovered: Int,
private val onClickListener: CanteenOfferItemAdapter.OnClickListener, private val onClickListener: CanteenOfferItemAdapter.OnClickListener,
private val displayAllergens: Boolean, private val displayAllergens: Boolean,
) : RecyclerView.Adapter<CanteenOfferPageAdapter.CanteenOfferPageViewHolder>() { ) : FragmentStateAdapter(fragmentActivity) {
/**
* View holder class for parent class
*
* @param binding view binding object
*/
class CanteenOfferPageViewHolder(val binding: ItemScrollablePageBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CanteenOfferPageViewHolder =
CanteenOfferPageViewHolder(
ItemScrollablePageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
// Returns the count of days covered // Returns the count of days covered
override fun getItemCount(): Int = daysCovered override fun getItemCount(): Int = daysCovered
override fun onBindViewHolder(holder: CanteenOfferPageViewHolder, position: Int) { override fun createFragment(position: Int): Fragment =
CanteenPageFragment(
holder.binding.pageRecyclerView.apply { offers,
// Set page to horizontally scroll onClickListener,
layoutManager = LinearLayoutManager( displayAllergens,
holder.binding.root.context, LinearLayoutManager.VERTICAL, false position,
) )
/*
* Create new adapter for every page. Attribute position denotes day that will be set up
* by the newly created adapter (0 = Monday, 4 = Friday)
* TODO check if the filtered list is empty, and tell the user if it is
*/
adapter = CanteenOfferItemAdapter(
offers.filter { it.dateId == position },
onClickListener,
displayAllergens,
)
// Animate creation of new page
scheduleLayoutAnimation()
}
}
/** /**
* Update the entire list of items * Update the entire list of items
@@ -1,61 +1,31 @@
package com.denizk0461.studip.adapter package com.denizk0461.studip.adapter
import android.view.LayoutInflater import androidx.fragment.app.Fragment
import android.view.ViewGroup import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.recyclerview.widget.RecyclerView
import com.denizk0461.studip.model.StudIPEvent import com.denizk0461.studip.model.StudIPEvent
import com.denizk0461.studip.databinding.ItemScrollablePageBinding import com.denizk0461.studip.fragment.EventPageFragment
/** /**
* Custom RecyclerView adapter for managing multiple pages of Stud.IP events in a RecyclerView or * Custom ViewPager adapter for managing multiple pages of Stud.IP events.
* ViewPager.
* *
* @param fragmentActivity parent fragment activity
* @param events all events (not filtered by day at this point) * @param events all events (not filtered by day at this point)
* @param onClickListener for managing click and long press events * @param onClickListener for managing click and long press events
*/ */
class StudIPEventPageAdapter( class StudIPEventPageAdapter(
fragmentActivity: FragmentActivity,
private var events: List<StudIPEvent>, private var events: List<StudIPEvent>,
private val onClickListener: StudIPEventItemAdapter.OnClickListener, private val onClickListener: StudIPEventItemAdapter.OnClickListener,
) : RecyclerView.Adapter<StudIPEventPageAdapter.EventPageViewHolder>() { ) : FragmentStateAdapter(fragmentActivity) {
/**
* View holder class for parent class
*
* @param binding view binding object
*/
class EventPageViewHolder(val binding: ItemScrollablePageBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventPageViewHolder = EventPageViewHolder(
ItemScrollablePageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
/* /*
* Assume that there will always be 5 pages, for 5 days - Monday through Friday. * Assume that there will always be 7 pages, for 7 days - Monday through Sunday.
* TODO is it right to assume that the user only has events on weekdays?
*/ */
override fun getItemCount(): Int = 5 override fun getItemCount(): Int = 7
// Set up page override fun createFragment(position: Int): Fragment =
override fun onBindViewHolder(holder: EventPageViewHolder, position: Int) { EventPageFragment(events, position, onClickListener)
holder.binding.pageRecyclerView.apply {
// Set page to horizontally scroll
layoutManager = LinearLayoutManager(holder.binding.root.context, LinearLayoutManager.VERTICAL, false)
/*
* Create new adapter for every page. Attribute position denotes day that will be set up
* by the newly created adapter (0 = Monday, 4 = Friday)
*/
adapter = StudIPEventItemAdapter(events, currentDay = position, onClickListener) // TODO check if empty
// Animate creation of new page
scheduleLayoutAnimation()
}
}
/** /**
* Update the entire list of items * Update the entire list of items
@@ -1,6 +1,5 @@
package com.denizk0461.studip.data package com.denizk0461.studip.data
import com.denizk0461.studip.db.AppRepository
import com.denizk0461.studip.model.* import com.denizk0461.studip.model.*
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@@ -63,8 +62,15 @@ class StwParser {
Dependencies.repo.nukeOffers() Dependencies.repo.nukeOffers()
// Fetch offers from the chosen canteen // Fetch offers from the chosen canteen
parseFromPage(link, Dependencies.repo) val (dates, canteens, categories, items) = parseFromPage(link)
// Save everything all at once into the database
with (Dependencies.repo) {
insertDates(dates)
insertCanteens(canteens)
insertCategories(categories)
insertItems(items)
}
// TODO implement onRefresh(Int) // TODO implement onRefresh(Int)
// Action call once all fetching activities have finished // Action call once all fetching activities have finished
@@ -75,9 +81,14 @@ class StwParser {
* Fetch and parse the plan of a specific canteen. * Fetch and parse the plan of a specific canteen.
* *
* @param url link to the canteen to be scraped. Must be a subpage of stw-bremen.de * @param url link to the canteen to be scraped. Must be a subpage of stw-bremen.de
* @param repo reference to the app repository used to save the items to persistent storage
*/ */
private fun parseFromPage(url: String, repo: AppRepository) { private fun parseFromPage(url: String): StwResults {
val dates = mutableListOf<OfferDate>()
val canteens = mutableListOf<OfferCanteen>()
val categories = mutableListOf<OfferCategory>()
val items = mutableListOf<OfferItem>()
// Used to store a parsed date string without creating a new variable on every loop // Used to store a parsed date string without creating a new variable on every loop
var date: String var date: String
@@ -120,8 +131,8 @@ class StwParser {
// Trim off excess line breaks // Trim off excess line breaks
openingHours = openingHours.trim() openingHours = openingHours.trim()
// Save the canteen to persistent storage // Save the canteen to its list
repo.insert( canteens.add(
OfferCanteen( OfferCanteen(
canteenId, canteenId,
doc.getElementsByClass("pane-title")[1].text(), doc.getElementsByClass("pane-title")[1].text(),
@@ -148,12 +159,8 @@ class StwParser {
*/ */
date = "${rawDate[0]}${rawDate[1].monthToNumber()}." date = "${rawDate[0]}${rawDate[1].monthToNumber()}."
/* // Save the date to its list
* Save the date to persistent storage. dates.add(OfferDate(dateId, date))
* TODO this will duplicate date entries for every canteen that is fetched. This is
* inefficient.
*/
repo.insert(OfferDate(dateId, date))
dateHasItems = false dateHasItems = false
@@ -165,8 +172,8 @@ class StwParser {
// Retrieve the category text // Retrieve the category text
val categoryTitle = category.getElementsByClass("category-name")[0].text() val categoryTitle = category.getElementsByClass("category-name")[0].text()
// Save the category to persistent storage // Save the category to its list
repo.insert(OfferCategory(categoryId, dateId, canteenId, categoryTitle)) categories.add(OfferCategory(categoryId, dateId, canteenId, categoryTitle))
// Iterate through all items in a category // Iterate through all items in a category
category category
@@ -195,8 +202,8 @@ class StwParser {
val filteredText = tableRows[1].getFilteredText() val filteredText = tableRows[1].getFilteredText()
// Save the item to persistent storage // Save the item to its list
repo.insert( items.add(
OfferItem( OfferItem(
itemId, itemId,
categoryId, categoryId,
@@ -219,8 +226,8 @@ class StwParser {
* is available. Text will then be handled in the adapter class. * is available. Text will then be handled in the adapter class.
*/ */
if (!dateHasItems) { if (!dateHasItems) {
repo.insert(OfferCategory(categoryId, dateId, canteenId, "NO\$ITEMS")) categories.add(OfferCategory(categoryId, dateId, canteenId, "NO\$ITEMS"))
repo.insert( items.add(
OfferItem( OfferItem(
itemId, itemId,
categoryId, categoryId,
@@ -240,6 +247,8 @@ class StwParser {
} }
// Increment the canteen ID to avoid conflict // Increment the canteen ID to avoid conflict
canteenId += 1 canteenId += 1
return StwResults(dates, canteens, categories, items)
} }
/** /**
@@ -366,6 +375,13 @@ class StwParser {
// .replace("<p>", "") // .replace("<p>", "")
// .replace("</p>", "") // .replace("</p>", "")
private data class StwResults(
val dates: List<OfferDate>,
val canteens: List<OfferCanteen>,
val categories: List<OfferCategory>,
val items: List<OfferItem>,
)
// Image links to all dietary preferences used for checking whether a preference is met // Image links to all dietary preferences used for checking whether a preference is met
private val imageLinkPrefFair = "https://www.stw-bremen.de/sites/default/files/images/pictograms/at_small.png" private val imageLinkPrefFair = "https://www.stw-bremen.de/sites/default/files/images/pictograms/at_small.png"
private val imageLinkPrefFish = "https://www.stw-bremen.de/sites/default/files/images/pictograms/fisch.png" private val imageLinkPrefFish = "https://www.stw-bremen.de/sites/default/files/images/pictograms/fisch.png"
@@ -49,7 +49,7 @@ interface AppDAO {
* *
* @param events objects to be saved to the database * @param events objects to be saved to the database
*/ */
@Insert fun insert(events: List<StudIPEvent>) @Insert fun insertEvents(events: List<StudIPEvent>)
/** /**
* Deletes all Stud.IP events from the database. * Deletes all Stud.IP events from the database.
@@ -124,17 +124,31 @@ interface AppDAO {
/** /**
* Inserts a date into the database. * Inserts a date into the database.
* *
* @param date object to be saved to the database * @param date object to be saved to the database
*/ */
@Insert fun insert(date: OfferDate) @Insert fun insert(date: OfferDate)
/**
* Inserts a list of dates into the database.
*
* @param dates objects to be saved to the database
*/
@Insert fun insertDates(dates: List<OfferDate>)
/** /**
* Inserts a canteen into the database. * Inserts a canteen into the database.
* *
* @param canteen object to be saved to the database * @param canteen object to be saved to the database
*/ */
@Insert fun insert(canteen: OfferCanteen) @Insert fun insert(canteen: OfferCanteen)
/**
* Inserts a list of canteens into the database.
*
* @param canteens objects to be saved to the database
*/
@Insert fun insertCanteens(canteens: List<OfferCanteen>)
/** /**
* Inserts a canteen offer category into the database. * Inserts a canteen offer category into the database.
* *
@@ -142,6 +156,13 @@ interface AppDAO {
*/ */
@Insert fun insert(category: OfferCategory) @Insert fun insert(category: OfferCategory)
/**
* Inserts a list of categories into the database.
*
* @param categories objects to be saved to the database
*/
@Insert fun insertCategories(categories: List<OfferCategory>)
/** /**
* Inserts a canteen offer item into the database. * Inserts a canteen offer item into the database.
* *
@@ -149,6 +170,13 @@ interface AppDAO {
*/ */
@Insert fun insert(item: OfferItem) @Insert fun insert(item: OfferItem)
/**
* Inserts a list of items into the database.
*
* @param items objects to be saved to the database
*/
@Insert fun insertItems(items: List<OfferItem>)
/** /**
* Retrieves the opening hours of the canteen as a string. * Retrieves the opening hours of the canteen as a string.
* *
@@ -37,7 +37,7 @@ class AppRepository(app: Application) {
* *
* @param events objects to be saved to the database * @param events objects to be saved to the database
*/ */
fun insertEvents(events: List<StudIPEvent>) { dao.insert(events) } fun insertEvents(events: List<StudIPEvent>) { dao.insertEvents(events) }
/** /**
* Deletes all Stud.IP events from the database. * Deletes all Stud.IP events from the database.
@@ -173,13 +173,27 @@ class AppRepository(app: Application) {
*/ */
fun insert(date: OfferDate) { dao.insert(date) } fun insert(date: OfferDate) { dao.insert(date) }
/**
* Inserts a list of dates into the database.
*
* @param dates objects to be saved to the database
*/
fun insertDates(dates: List<OfferDate>) { dao.insertDates(dates) }
/** /**
* Inserts a canteen into the database. * Inserts a canteen into the database.
* *
* @param canteen object to be saved to the database * @param canteen object to be saved to the database
*/ */
fun insert(canteen: OfferCanteen) { dao.insert(canteen) } fun insert(canteen: OfferCanteen) { dao.insert(canteen) }
/**
* Inserts a list of canteens into the database.
*
* @param canteens objects to be saved to the database
*/
fun insertCanteens(canteens: List<OfferCanteen>) { dao.insertCanteens(canteens) }
/** /**
* Inserts a canteen offer category into the database. * Inserts a canteen offer category into the database.
* *
@@ -187,6 +201,13 @@ class AppRepository(app: Application) {
*/ */
fun insert(category: OfferCategory) { dao.insert(category) } fun insert(category: OfferCategory) { dao.insert(category) }
/**
* Inserts a list of categories into the database.
*
* @param categories objects to be saved to the database
*/
fun insertCategories(categories: List<OfferCategory>) { dao.insertCategories(categories) }
/** /**
* Inserts a canteen offer item into the database. * Inserts a canteen offer item into the database.
* *
@@ -194,6 +215,13 @@ class AppRepository(app: Application) {
*/ */
fun insert(item: OfferItem) { dao.insert(item) } fun insert(item: OfferItem) { dao.insert(item) }
/**
* Inserts a list of items into the database.
*
* @param items objects to be saved to the database
*/
fun insertItems(items: List<OfferItem>) { dao.insertItems(items) }
// --- settings preferences --- // // --- settings preferences --- //
/** /**
@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.denizk0461.studip.R import com.denizk0461.studip.R
import com.denizk0461.studip.adapter.CanteenOfferItemAdapter import com.denizk0461.studip.adapter.CanteenOfferItemAdapter
@@ -145,7 +146,11 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
// Set up the view pager's adapter // Set up the view pager's adapter
viewPagerAdapter = CanteenOfferPageAdapter( viewPagerAdapter = CanteenOfferPageAdapter(
listOf(), 0, this, viewModel.preferenceAllergen activity as FragmentActivity,
listOf(),
0,
this,
viewModel.preferenceAllergen,
) )
// Assign the adapter to the view pager // Assign the adapter to the view pager
@@ -163,7 +168,9 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
val groupedElements = offers.groupElements().distinct() val groupedElements = offers.groupElements().distinct()
// Find all dates stored in the database // Find all dates stored in the database
val newDates = /*groupedElements.map { it.date }.distinct() TODO */viewModel.getDates() // val newDates = /*groupedElements.map { it.date }.distinct() TODO */viewModel.getDates()
val newDates = groupedElements.map { it.date }.distinct()
// Update the date count stored in this fragment // Update the date count stored in this fragment
dateSize = newDates.size dateSize = newDates.size
@@ -0,0 +1,71 @@
package com.denizk0461.studip.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.denizk0461.studip.adapter.CanteenOfferItemAdapter
import com.denizk0461.studip.adapter.StudIPEventPageAdapter
import com.denizk0461.studip.databinding.RecyclerViewBinding
import com.denizk0461.studip.model.CanteenOfferGroup
/**
* Fragment that is instantiated by [StudIPEventPageAdapter] to display individual days' pages and
* their events.
*
* @param offers all offers for a given canteen, grouped by category (not filtered by day at
* this point)
* @param onClickListener for managing click and long press events
* @param displayAllergens whether the user wants allergens to be marked
* @param currentDay current day
*/
class CanteenPageFragment(
private var offers: List<CanteenOfferGroup>,
private val onClickListener: CanteenOfferItemAdapter.OnClickListener,
private val displayAllergens: Boolean,
private val currentDay: Int,
) : AppFragment() {
// Nullable view binding reference
private var _binding: RecyclerViewBinding? = null
/*
* Non-null reference to the view binding. This property is only valid between onCreateView and
* onDestroyView.
*/
private val binding get() = _binding!!
// Instantiate the view binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = RecyclerViewBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerView.apply {
// Set page to scroll vertically
layoutManager = LinearLayoutManager(
context, LinearLayoutManager.VERTICAL, false
)
/*
* Create new adapter for every page. Attribute position denotes day that will be set up
* by the newly created adapter (0 = Monday, 4 = Friday)
* TODO check if the filtered list is empty, and tell the user if it is
*/
val o = offers.filter { it.dateId == currentDay }
adapter = CanteenOfferItemAdapter(
o,
onClickListener,
displayAllergens,
)
// Animate creation of new page
scheduleLayoutAnimation()
}
}
}
@@ -1,9 +1,12 @@
package com.denizk0461.studip.fragment package com.denizk0461.studip.fragment
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.util.Log
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.fragment.app.FragmentActivity
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.denizk0461.studip.R import com.denizk0461.studip.R
import com.denizk0461.studip.adapter.StudIPEventItemAdapter import com.denizk0461.studip.adapter.StudIPEventItemAdapter
@@ -39,13 +42,12 @@ class EventFragment : AppFragment() {
private var dayOfWeek: Int = 0 private var dayOfWeek: Int = 0
// Titles for the view pager's tabs // Titles for the view pager's tabs
private val dayStrings = listOf( private lateinit var weekdays: Array<String>
R.string.monday,
R.string.tuesday, // private var viewPagerScrollPosition: Parcelable? = null
R.string.wednesday, private var viewPagerPosition = -1
R.string.thursday,
R.string.friday, private var hasFragmentStarted = false
)
// Instantiate the view binding // Instantiate the view binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@@ -56,40 +58,45 @@ class EventFragment : AppFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
/* weekdays = resources.getStringArray(R.array.weekdays)
* Determine the current day.
* TODO expand to weekend // Determine the current day
*/
dayOfWeek = when (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) { dayOfWeek = when (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) {
Calendar.TUESDAY -> 1 Calendar.TUESDAY -> 1
Calendar.WEDNESDAY -> 2 Calendar.WEDNESDAY -> 2
Calendar.THURSDAY -> 3 Calendar.THURSDAY -> 3
Calendar.FRIDAY -> 4 Calendar.FRIDAY -> 4
else -> 0 Calendar.SATURDAY -> 5
Calendar.SUNDAY -> 6
else -> 0 // assume Monday
} }
// Set up the view pager's adapter // Set up the view pager's adapter
viewPagerAdapter = StudIPEventPageAdapter(listOf(), object : StudIPEventItemAdapter.OnClickListener { viewPagerAdapter =
override fun onClick(event: StudIPEvent) { StudIPEventPageAdapter(activity as FragmentActivity, listOf(), object : StudIPEventItemAdapter.OnClickListener {
// TODO implement functionality or delete override fun onClick(event: StudIPEvent) {
} // TODO implement functionality or delete
override fun onLongClick(event: StudIPEvent): Boolean { }
// Open a bottom sheet to edit the event override fun onLongClick(event: StudIPEvent): Boolean {
openBottomSheet(ScheduleUpdateSheet(event, onUpdate = { eventToUpdate -> // Save the current page of the ViewPager
viewModel.update(eventToUpdate) viewPagerPosition = binding.viewPager.currentItem
}, onDelete = { eventToDelete ->
viewModel.delete(eventToDelete) // Open a bottom sheet to edit the event
})) openBottomSheet(ScheduleUpdateSheet(event, onUpdate = { eventToUpdate ->
return true viewModel.update(eventToUpdate)
} }, onDelete = { eventToDelete ->
}) viewModel.delete(eventToDelete)
}))
return true
}
})
// Assign the adapter to the view pager // Assign the adapter to the view pager
binding.viewPager.adapter = viewPagerAdapter binding.viewPager.adapter = viewPagerAdapter
// Create and attach the object mediating the tabs for the view pager // Create and attach the object mediating the tabs for the view pager
TabLayoutMediator(binding.dayTabLayout, binding.viewPager) { tab, position -> TabLayoutMediator(binding.dayTabLayout, binding.viewPager) { tab, position ->
tab.text = getString(dayStrings[position]) tab.text = weekdays[position]
}.attach() }.attach()
// Set up LiveData observer to refresh the view on update // Set up LiveData observer to refresh the view on update
@@ -97,16 +104,53 @@ class EventFragment : AppFragment() {
// Update the item list in the view pager's adapter // Update the item list in the view pager's adapter
viewPagerAdapter.setNewItems(events) viewPagerAdapter.setNewItems(events)
// Scroll to the current day if (!hasFragmentStarted) {
switchToCurrentDayView() // Scroll to the current day, if no page has been stored to be scrolled to
switchToCurrentDayView()
hasFragmentStarted = true
// } else {
// /*
// * If a page was previously saved, scroll to that one instead. This is meant to
// * prevent jumping from
// */
// binding.viewPager.currentItem = viewPagerPosition
}
} }
} }
override fun onPause() {
// viewPagerPosition = binding.viewPager.currentItem
// Log.d("AAA?", "onpause: $viewPagerPosition")
super.onPause()
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
binding.viewPager.currentItem = viewPagerPosition
Log.d("AAA?", "onresume: pos: $viewPagerPosition; vpp: ${binding.viewPager.currentItem}")
// Scroll to the current day // Scroll to the current day
switchToCurrentDayView() // switchToCurrentDayView()
}
/**
* Called when the layout changes (e.g. device rotation) but NOT when the fragment is selected
* from the bottom navigation view.
*/
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.d("AAA?", "${binding.viewPager.currentItem}")
outState.putInt(
"viewPagerCurrentPage",
binding.viewPager.currentItem
)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
val restoredPage = savedInstanceState?.getInt("viewPagerCurrentPage") ?: 0
viewPagerPosition = restoredPage
} }
// Invalidate the view binding // Invalidate the view binding
@@ -0,0 +1,60 @@
package com.denizk0461.studip.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.denizk0461.studip.adapter.StudIPEventPageAdapter
import com.denizk0461.studip.adapter.StudIPEventItemAdapter
import com.denizk0461.studip.databinding.RecyclerViewBinding
import com.denizk0461.studip.model.StudIPEvent
/**
* Fragment that is instantiated by [StudIPEventPageAdapter] to display individual days' pages and
* their events.
*
* @param events list of all events (still not filtered by day at this point)
* @param currentDay current day that is used to show only events of a given day (0 = Monday,
* 4 = Friday)
* @param onClickListener used for listening to clicks and long presses
*/
class EventPageFragment(
private val events: List<StudIPEvent>,
private val currentDay: Int,
private val onClickListener: StudIPEventItemAdapter.OnClickListener,
) : AppFragment() {
// Nullable view binding reference
private var _binding: RecyclerViewBinding? = null
/*
* Non-null reference to the view binding. This property is only valid between onCreateView and
* onDestroyView.
*/
private val binding get() = _binding!!
// Instantiate the view binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = RecyclerViewBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerView.apply {
// Set page to scroll vertically
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
/*
* Create new adapter for every page. Attribute position denotes day that will be set up
* by the newly created adapter (0 = Monday, 4 = Friday)
*/
adapter = StudIPEventItemAdapter(events, currentDay, onClickListener) // TODO check if empty
// Animate creation of new page
scheduleLayoutAnimation()
}
}
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/page_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/layout_animation_fall_down">
</androidx.recyclerview.widget.RecyclerView>
</androidx.core.widget.NestedScrollView>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:layoutAnimation="@anim/layout_animation_fall_down"/>
+10 -3
View File
@@ -9,6 +9,16 @@
<string name="saturday">Samstag</string> <string name="saturday">Samstag</string>
<string name="sunday">Sonntag</string> <string name="sunday">Sonntag</string>
<string-array name="weekdays">
<item>Montag</item>
<item>Dienstag</item>
<item>Mittwoch</item>
<item>Donnerstag</item>
<item>Freitag</item>
<item>Samstag</item>
<item>Sonntag</item>
</string-array>
<string name="fetch_error_snack">Ein Fehler ist aufgetreten!</string> <string name="fetch_error_snack">Ein Fehler ist aufgetreten!</string>
<string name="save">Speichern</string> <string name="save">Speichern</string>
@@ -121,9 +131,6 @@
<string name="settings_highlight_title">Markiere meinen nächsten Kurs</string> <string name="settings_highlight_title">Markiere meinen nächsten Kurs</string>
<string name="settings_highlight_subtitle">Funktioniert nur wenn Kurse sich nicht überschneiden!</string> <string name="settings_highlight_subtitle">Funktioniert nur wenn Kurse sich nicht überschneiden!</string>
<string name="settings_quarter_title">Nutze akademisches Viertel</string>
<string name="settings_quarter_subtitle">Wo es passt</string>
<string name="settings_allergens_title">Markiere Angebote mit Allergenen mit einem Stern*</string> <string name="settings_allergens_title">Markiere Angebote mit Allergenen mit einem Stern*</string>
<string name="settings_allergens_subtitle">Wird auch für Zusatzstoffe genutzt</string> <string name="settings_allergens_subtitle">Wird auch für Zusatzstoffe genutzt</string>
+10
View File
@@ -9,6 +9,16 @@
<string name="saturday">Saturday</string> <string name="saturday">Saturday</string>
<string name="sunday">Sunday</string> <string name="sunday">Sunday</string>
<string-array name="weekdays">
<item>Monday</item>
<item>Tuesday</item>
<item>Wednesday</item>
<item>Thursday</item>
<item>Friday</item>
<item>Saturday</item>
<item>Sunday</item>
</string-array>
<string name="fetch_error_snack">Something went wrong!</string> <string name="fetch_error_snack">Something went wrong!</string>
<string name="save">Save</string> <string name="save">Save</string>