diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..7925f4518
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,48 @@
+name: Build calendar APK
+
+on:
+ pull_request:
+ branches: [main]
+ push:
+ branches: [feature/business-calendar-month-view]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-java@v4
+ with:
+ java-version: 17
+ distribution: "temurin"
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v4
+
+ - name: Restore signing keystore
+ env:
+ SIGNING_STORE_B64: ${{ secrets.SIGNING_STORE_B64 }}
+ SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
+ SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
+ run: |
+ if [ -z "$SIGNING_STORE_B64" ]; then
+ echo "No signing secret present; using default debug signing."
+ exit 0
+ fi
+ mkdir -p debug-signing
+ echo "$SIGNING_STORE_B64" | base64 --decode > debug-signing/calendar-release.p12
+ printf 'storeFile=debug-signing/calendar-release.p12\nstorePassword=%s\nkeyAlias=calendar\nkeyPassword=%s\nstoreType=pkcs12\n' "$SIGNING_STORE_PASSWORD" "$SIGNING_KEY_PASSWORD" > keystore.properties
+
+ - name: Build debug APK
+ run: ./gradlew assembleFossDebug
+
+ - name: Upload debug APK
+ uses: actions/upload-artifact@v4
+ with:
+ name: calendar-foss-debug
+ path: app/build/outputs/apk/foss/debug/*.apk
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 72ae8c3bc..bf9412a97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@
/captures
keystore.jks
keystore.properties
+/debug-signing/
fastlane/fastlane.json
fastlane/report.xml
.kotlin/
+build-apk/
diff --git a/ACCESS-CHECK.md b/ACCESS-CHECK.md
new file mode 100644
index 000000000..a1ccfb155
--- /dev/null
+++ b/ACCESS-CHECK.md
@@ -0,0 +1,2 @@
+# Access check
+Written by Cleo at 2026-10-27T22:47:16+11:00 to confirm push rights.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 265ecb738..1a073e6c1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -47,8 +47,9 @@ android {
register("release") {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
- storeFile = file(keystoreProperties.getProperty("storeFile"))
+ storeFile = rootProject.file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
+ storeType = keystoreProperties.getProperty("storeType", "jks")
}
} else if (hasSigningVars()) {
register("release") {
@@ -70,6 +71,9 @@ android {
buildTypes {
debug {
applicationIdSuffix = ".debug"
+ if (keystorePropertiesFile.exists() || hasSigningVars()) {
+ signingConfig = signingConfigs.getByName("release")
+ }
}
release {
isMinifyEnabled = true
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4e4a2cd94..326c69ee7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -64,8 +64,169 @@
+ android:theme="@style/SplashTheme">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/kotlin/org/fossify/calendar/activities/MainActivity.kt b/app/src/main/kotlin/org/fossify/calendar/activities/MainActivity.kt
index 7b9964b61..dd3db044d 100644
--- a/app/src/main/kotlin/org/fossify/calendar/activities/MainActivity.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/activities/MainActivity.kt
@@ -6,12 +6,14 @@ import android.content.pm.ShortcutInfo
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Icon
import android.graphics.drawable.LayerDrawable
+import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.provider.ContactsContract.CommonDataKinds
import android.provider.ContactsContract.Contacts
import android.provider.ContactsContract.Data
import android.view.MenuItem
+import android.widget.RelativeLayout
import android.widget.Toast
import org.fossify.calendar.R
import org.fossify.calendar.adapters.EventListAdapter
@@ -50,6 +52,9 @@ import org.fossify.calendar.helpers.EVENT_OCCURRENCE_TS
import org.fossify.calendar.helpers.FETCH_INTERVAL
import org.fossify.calendar.helpers.FLAG_ALL_DAY
import org.fossify.calendar.helpers.FLAG_MISSING_YEAR
+import org.fossify.calendar.helpers.FOLD_POSTURE_COVER
+import org.fossify.calendar.helpers.FOLD_POSTURE_OPEN
+import org.fossify.calendar.helpers.FOLD_POSTURE_UNSUPPORTED
import org.fossify.calendar.helpers.Formatter
import org.fossify.calendar.helpers.Formatter.DAYCODE_PATTERN
import org.fossify.calendar.helpers.HOLIDAY_EVENT
@@ -114,6 +119,7 @@ import org.fossify.commons.extensions.launchMoreAppsFromUsIntent
import org.fossify.commons.extensions.queryCursor
import org.fossify.commons.extensions.shortcutManager
import org.fossify.commons.extensions.showErrorToast
+import org.fossify.commons.extensions.showKeyboard
import org.fossify.commons.extensions.toast
import org.fossify.commons.extensions.updateTextColors
import org.fossify.commons.extensions.viewBinding
@@ -140,6 +146,7 @@ import java.text.SimpleDateFormat
import java.util.Locale
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.FragmentManager
+import kotlin.math.abs
class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
override var isSearchBarEnabled = true
@@ -162,12 +169,14 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
private var mStoredHighlightWeekends = false
private var mStoredStartWeekWithCurrentDay = false
private var mStoredHighlightWeekendsColor = 0
+ private var mStoredMonthViewFontSize = 0
// search results have endless scrolling, so reaching the top/bottom fetches further results
private var minFetchedSearchTS = 0L
private var maxFetchedSearchTS = 0L
private var searchResultEvents = ArrayList()
private var bottomItemAtRefresh: ListItem? = null
+ private var mOpeningFromIntent = false
private val binding by viewBinding(ActivityMainBinding::inflate)
@@ -176,6 +185,13 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
setContentView(binding.root)
appLaunched(org.fossify.calendar.BuildConfig.APPLICATION_ID)
setupOptionsMenu()
+ binding.mainMenu.requireToolbar().setOnClickListener {
+ showGoToDateDialog()
+ }
+ binding.appBarMonthLabel.setOnClickListener {
+ showGoToDateDialog()
+ }
+ centerAppBarMonthLabel()
refreshMenuItems()
setupEdgeToEdge(
padBottomImeAndSystem = listOf(binding.searchHolder, binding.quickCalendarFilter),
@@ -229,7 +245,9 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
checkIsViewIntent()
- if (!checkIsOpenIntent() && savedInstanceState == null) {
+ mOpeningFromIntent = checkIsOpenIntent()
+ if (!mOpeningFromIntent && savedInstanceState == null) {
+ setupDefaultViewForFoldable()
updateViewPager()
}
@@ -254,9 +272,13 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
override fun onResume() {
super.onResume()
+ if (!mOpeningFromIntent) {
+ setupDefaultViewForFoldableIfNeeded()
+ }
if (mStoredTextColor != getProperTextColor() || mStoredBackgroundColor != getProperBackgroundColor() || mStoredPrimaryColor != getProperPrimaryColor()
|| mStoredDayCode != Formatter.getTodayCode() || mStoredDimPastEvents != config.dimPastEvents || mStoredDimCompletedTasks != config.dimCompletedTasks
|| mStoredHighlightWeekends != config.highlightWeekends || mStoredHighlightWeekendsColor != config.highlightWeekendsColor
+ || mStoredMonthViewFontSize != config.monthViewFontSize
) {
updateViewPager()
}
@@ -343,6 +365,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
mainMenu.requireToolbar().inflateMenu(R.menu.menu_main)
mainMenu.toggleHideOnScroll(false)
mainMenu.setupMenu()
+ setupSearchBar()
mainMenu.onSearchTextChangedListener = { text ->
searchQueryChanged(text)
@@ -372,6 +395,59 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
}
}
+ private fun setupSearchBar() {
+ val searchMenu = binding.mainMenu
+ searchMenu.binding.apply {
+ topToolbarSearch.beGone()
+
+ val iconParams = topToolbarSearchIcon.layoutParams as RelativeLayout.LayoutParams
+ iconParams.addRule(RelativeLayout.START_OF, topToolbar.id)
+ topToolbarSearchIcon.layoutParams = iconParams
+
+ searchMenu.onSearchOpenListener = {
+ topToolbarSearch.beVisible()
+ this@MainActivity.binding.appBarMonthLabel.beGone()
+ }
+
+ searchMenu.onSearchClosedListener = {
+ topToolbarSearch.beGone()
+ this@MainActivity.binding.appBarMonthLabel.beVisible()
+ }
+
+ topToolbarSearchIcon.setOnClickListener {
+ when {
+ searchMenu.isSearchOpen -> searchMenu.closeSearch()
+
+ searchMenu.useArrowIcon && searchMenu.onNavigateBackClickListener != null ->
+ searchMenu.onNavigateBackClickListener!!.invoke()
+
+ else -> {
+ topToolbarSearch.beVisible()
+ this@MainActivity.showKeyboard(topToolbarSearch)
+ }
+ }
+ }
+ }
+ }
+
+ fun setToolbarTitle(text: String) {
+ this@MainActivity.binding.appBarMonthLabel.text = text
+ binding.mainMenu.requireToolbar().contentDescription = text
+ centerAppBarMonthLabel()
+ }
+
+ private fun centerAppBarMonthLabel() {
+ val label = binding.appBarMonthLabel
+ label.post {
+ if (label.isVisible()) {
+ val menu = binding.mainMenu
+ val pill = menu.binding.toolbarContainer
+ val pillCenterY = menu.top + menu.binding.searchBarContainer.top + pill.top + pill.height / 2
+ label.translationY = (pillCenterY - (label.top + label.height / 2)).toFloat()
+ }
+ }
+ }
+
override fun onBackPressedCompat(): Boolean {
return if (binding.mainMenu.isSearchOpen) {
closeSearch()
@@ -415,6 +491,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
mStoredHighlightWeekendsColor = highlightWeekendsColor
mStoredMidnightSpan = showMidnightSpanningEventsAtTop
mStoredStartWeekWithCurrentDay = startWeekWithCurrentDay
+ mStoredMonthViewFontSize = monthViewFontSize
}
mStoredDayCode = Formatter.getTodayCode()
}
@@ -1087,6 +1164,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
}
private fun updateView(view: Int) {
+ config.viewSelectionWasCustomized = true
binding.calendarFab.beVisibleIf(view != YEARLY_VIEW && view != WEEKLY_VIEW)
val dateCode = getDateCodeToDisplay(view)
config.storedView = view
@@ -1098,6 +1176,65 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
}
}
+ private fun setupDefaultViewForFoldable() {
+ val posture = getCurrentFoldPosture()
+ if (posture == FOLD_POSTURE_UNSUPPORTED) {
+ return
+ }
+
+ config.viewSelectionWasCustomized = false
+ config.storedView = if (posture == FOLD_POSTURE_COVER) EVENTS_LIST_VIEW else MONTHLY_VIEW
+ config.lastFoldPosture = posture
+ }
+
+ private fun setupDefaultViewForFoldableIfNeeded() {
+ val posture = getCurrentFoldPosture()
+ if (posture == FOLD_POSTURE_UNSUPPORTED || config.lastFoldPosture == posture) {
+ return
+ }
+
+ val previousView = config.storedView
+ config.viewSelectionWasCustomized = false
+ config.storedView = if (posture == FOLD_POSTURE_COVER) EVENTS_LIST_VIEW else MONTHLY_VIEW
+ config.lastFoldPosture = posture
+
+ if (previousView != config.storedView) {
+ binding.calendarFab.beVisibleIf(config.storedView != YEARLY_VIEW && config.storedView != WEEKLY_VIEW)
+ checkSwipeRefreshAvailability()
+ refreshMenuItems()
+ updateViewPager()
+ }
+ }
+
+ private fun getCurrentFoldPosture(): Int {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return FOLD_POSTURE_UNSUPPORTED
+ }
+
+ val smallestScreenWidthDp = resources.configuration.smallestScreenWidthDp
+ val lastSwdp = config.lastSwdp
+ if (lastSwdp == 0) {
+ config.lastSwdp = smallestScreenWidthDp
+ val maxWidth = windowManager.maximumWindowMetrics.bounds.width().toFloat()
+ val currentWidth = windowManager.currentWindowMetrics.bounds.width().toFloat()
+ if (maxWidth > currentWidth * 1.3f) {
+ return FOLD_POSTURE_COVER
+ }
+ return FOLD_POSTURE_UNSUPPORTED
+ }
+
+ if (abs(smallestScreenWidthDp - lastSwdp) > 250) {
+ config.lastSwdp = smallestScreenWidthDp
+ return if (smallestScreenWidthDp < 500) FOLD_POSTURE_COVER else FOLD_POSTURE_OPEN
+ }
+
+ if (config.lastFoldPosture != FOLD_POSTURE_UNSUPPORTED) {
+ return if (smallestScreenWidthDp < 500) FOLD_POSTURE_COVER else FOLD_POSTURE_OPEN
+ }
+
+ return FOLD_POSTURE_UNSUPPORTED
+ }
+
private fun getDateCodeToDisplay(newView: Int): String? {
val fragment = getCurrentFragment() ?: return null
val currentView = fragment.viewType
@@ -1231,6 +1368,25 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
showBackNavigationArrow()
}
+ fun openEventListFromMonthly(dateTime: DateTime) {
+ if (getCurrentFragment() is EventListFragment) {
+ return
+ }
+
+ val fragment = EventListFragment()
+ val bundle = Bundle()
+ bundle.putString(DAY_CODE, Formatter.getDayCodeFromDateTime(dateTime))
+ fragment.arguments = bundle
+ try {
+ supportFragmentManager.beginTransaction().replace(R.id.fragments_holder, fragment)
+ .addToBackStack(null)
+ .commit()
+ supportFragmentManager.executePendingTransactions()
+ showBackNavigationArrow()
+ } catch (e: Exception) {
+ }
+ }
+
fun openDayFromMonthly(dateTime: DateTime) {
if (getCurrentFragment() is DayFragmentsHolder) {
return
diff --git a/app/src/main/kotlin/org/fossify/calendar/activities/SettingsActivity.kt b/app/src/main/kotlin/org/fossify/calendar/activities/SettingsActivity.kt
index ebb569eb0..704837b5e 100644
--- a/app/src/main/kotlin/org/fossify/calendar/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/activities/SettingsActivity.kt
@@ -16,6 +16,7 @@ import org.fossify.calendar.dialogs.ManageAutomaticBackupsDialog
import org.fossify.calendar.dialogs.ManageSyncedCalendarsDialog
import org.fossify.calendar.dialogs.SelectCalendarDialog
import org.fossify.calendar.dialogs.SelectCalendarsDialog
+import org.fossify.calendar.dialogs.ReorderCalendarsDialog
import org.fossify.calendar.extensions.calDAVHelper
import org.fossify.calendar.extensions.calendarsDB
import org.fossify.calendar.extensions.cancelScheduledAutomaticBackup
@@ -62,6 +63,7 @@ import org.fossify.calendar.helpers.SHOW_GRID
import org.fossify.calendar.helpers.SHOW_MIDNIGHT_SPANNING_EVENTS_AT_TOP
import org.fossify.calendar.helpers.START_WEEKLY_AT
import org.fossify.calendar.helpers.START_WEEK_WITH_CURRENT_DAY
+import org.fossify.calendar.helpers.TODAY_CIRCLE_COLOR
import org.fossify.calendar.helpers.USE_PREVIOUS_EVENT_REMINDERS
import org.fossify.calendar.helpers.VIBRATE
import org.fossify.calendar.helpers.WEEKLY_VIEW
@@ -184,16 +186,19 @@ class SettingsActivity : SimpleActivity() {
setupLanguage()
setupManageCalendars()
setupManageQuickFilterCalendars()
+ setupReorderQuickFilterCalendars()
setupHourFormat()
setupAllowCreatingTasks()
setupStartWeekOn()
setupHighlightWeekends()
setupHighlightWeekendsColor()
+ setupTodayCircleColor()
setupDeleteAllEvents()
setupDisplayDescription()
setupReplaceDescription()
setupWeekNumbers()
setupShowGrid()
+ setupMonthViewFontSize()
setupWeeklyStart()
setupMidnightSpanEvents()
setupAllowCustomizeDayCount()
@@ -484,6 +489,40 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupReorderQuickFilterCalendars() = binding.apply {
+ settingsReorderQuickFilterCalendarsHolder.setOnClickListener {
+ showReorderQuickFilterPicker()
+ }
+
+ eventsHelper.getCalendars(this@SettingsActivity, false) {
+ settingsReorderQuickFilterCalendarsHolder.beGoneIf(it.size < 2)
+ }
+ }
+
+ private fun showReorderQuickFilterPicker() {
+ eventsHelper.getCalendars(this, false) { calendars ->
+ val quickFilterIds = config.quickFilterCalendars.mapNotNull { it.toLongOrNull() }.toHashSet()
+ val orderedIds = config.quickFilterCalendarsOrder
+ val quickFilterCalendars = ArrayList()
+
+ orderedIds.forEach { id ->
+ calendars.firstOrNull { it.id == id }?.let { quickFilterCalendars.add(it) }
+ }
+
+ calendars
+ .filter { calendar ->
+ val id = calendar.id
+ id != null && id in quickFilterIds && id !in orderedIds
+ }
+ .sortedBy { it.title.lowercase() }
+ .forEach { quickFilterCalendars.add(it) }
+
+ ReorderCalendarsDialog(this@SettingsActivity, quickFilterCalendars) {
+ config.quickFilterCalendarsOrder = it
+ }
+ }
+ }
+
private fun setupStartWeekOn() = binding.apply {
val items = arrayListOf(
RadioItem(DateTimeConstants.SUNDAY, getString(org.fossify.commons.R.string.sunday)),
@@ -508,6 +547,23 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupTodayCircleColor() = binding.apply {
+ val currentColor =
+ if (config.todayCircleColor == 0) getProperPrimaryColor() else config.todayCircleColor
+ settingsTodayCircleColor.setFillWithStroke(currentColor, getProperBackgroundColor())
+ settingsTodayCircleColorHolder.setOnClickListener {
+ ColorPickerDialog(
+ activity = this@SettingsActivity,
+ color = currentColor
+ ) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ config.todayCircleColor = color
+ settingsTodayCircleColor.setFillWithStroke(color, getProperBackgroundColor())
+ }
+ }
+ }
+ }
+
private fun setupHighlightWeekends() = binding.apply {
settingsHighlightWeekends.isChecked = config.highlightWeekends
settingsHighlightWeekendsColorHolder.beVisibleIf(config.highlightWeekends)
@@ -625,6 +681,30 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupMonthViewFontSize() = binding.apply {
+ settingsMonthFontSize.text = getMonthViewFontSizeText()
+ settingsMonthFontSizeHolder.setOnClickListener {
+ val items = arrayListOf(
+ RadioItem(FONT_SIZE_SMALL, getString(org.fossify.commons.R.string.small)),
+ RadioItem(FONT_SIZE_MEDIUM, getString(org.fossify.commons.R.string.medium)),
+ RadioItem(FONT_SIZE_LARGE, getString(org.fossify.commons.R.string.large)),
+ RadioItem(FONT_SIZE_EXTRA_LARGE, getString(org.fossify.commons.R.string.extra_large))
+ )
+
+ RadioGroupDialog(this@SettingsActivity, items, config.monthViewFontSize) {
+ config.monthViewFontSize = it as Int
+ settingsMonthFontSize.text = getMonthViewFontSizeText()
+ }
+ }
+ }
+
+ private fun getMonthViewFontSizeText(): String = when (config.monthViewFontSize) {
+ FONT_SIZE_SMALL -> getString(org.fossify.commons.R.string.small)
+ FONT_SIZE_LARGE -> getString(org.fossify.commons.R.string.large)
+ FONT_SIZE_EXTRA_LARGE -> getString(org.fossify.commons.R.string.extra_large)
+ else -> getString(org.fossify.commons.R.string.medium)
+ }
+
@Deprecated("Not used on Oreo+ devices")
private fun setupReminderSound() = binding.apply {
settingsReminderSoundHolder.beGoneIf(isOreoPlus())
@@ -1156,6 +1236,7 @@ class SettingsActivity : SimpleActivity() {
put(FIRST_DAY_OF_WEEK, config.firstDayOfWeek)
put(HIGHLIGHT_WEEKENDS, config.highlightWeekends)
put(HIGHLIGHT_WEEKENDS_COLOR, config.highlightWeekendsColor)
+ put(TODAY_CIRCLE_COLOR, config.todayCircleColor)
put(ALLOW_CREATING_TASKS, config.allowCreatingTasks)
}
@@ -1272,6 +1353,7 @@ class SettingsActivity : SimpleActivity() {
FIRST_DAY_OF_WEEK -> config.firstDayOfWeek = value.toInt()
HIGHLIGHT_WEEKENDS -> config.highlightWeekends = value.toBoolean()
HIGHLIGHT_WEEKENDS_COLOR -> config.highlightWeekendsColor = value.toInt()
+ TODAY_CIRCLE_COLOR -> config.todayCircleColor = value.toInt()
ALLOW_CREATING_TASKS -> config.allowCreatingTasks = value.toBoolean()
}
}
diff --git a/app/src/main/kotlin/org/fossify/calendar/activities/SimpleActivity.kt b/app/src/main/kotlin/org/fossify/calendar/activities/SimpleActivity.kt
index 59ada6a03..ccf6593dc 100644
--- a/app/src/main/kotlin/org/fossify/calendar/activities/SimpleActivity.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/activities/SimpleActivity.kt
@@ -24,27 +24,7 @@ open class SimpleActivity : BaseSimpleActivity() {
val calDAVRefreshHandler = Handler()
var calDAVRefreshCallback: (() -> Unit)? = null
- override fun getAppIconIDs() = arrayListOf(
- R.mipmap.ic_launcher_red,
- R.mipmap.ic_launcher_pink,
- R.mipmap.ic_launcher_purple,
- R.mipmap.ic_launcher_deep_purple,
- R.mipmap.ic_launcher_indigo,
- R.mipmap.ic_launcher_blue,
- R.mipmap.ic_launcher_light_blue,
- R.mipmap.ic_launcher_cyan,
- R.mipmap.ic_launcher_teal,
- R.mipmap.ic_launcher,
- R.mipmap.ic_launcher_light_green,
- R.mipmap.ic_launcher_lime,
- R.mipmap.ic_launcher_yellow,
- R.mipmap.ic_launcher_amber,
- R.mipmap.ic_launcher_orange,
- R.mipmap.ic_launcher_deep_orange,
- R.mipmap.ic_launcher_brown,
- R.mipmap.ic_launcher_blue_grey,
- R.mipmap.ic_launcher_grey_black
- )
+ override fun getAppIconIDs() = arrayListOf().apply { repeat(19) { add(R.drawable.ic_launcher_foreground) } }
override fun getAppLauncherName() = getString(R.string.app_launcher_name)
diff --git a/app/src/main/kotlin/org/fossify/calendar/adapters/QuickFilterCalendarAdapter.kt b/app/src/main/kotlin/org/fossify/calendar/adapters/QuickFilterCalendarAdapter.kt
index 1ea6a43ca..fa6a851b6 100644
--- a/app/src/main/kotlin/org/fossify/calendar/adapters/QuickFilterCalendarAdapter.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/adapters/QuickFilterCalendarAdapter.kt
@@ -46,7 +46,15 @@ class QuickFilterCalendarAdapter(
}
}
- quickFilterCalendars.sortBy { it.title.lowercase() }
+ val orderIndexes = activity.config.quickFilterCalendarsOrder
+ .mapIndexed { index, id -> id to index }
+ .toMap()
+ quickFilterCalendars.sortWith(
+ compareBy(
+ { orderIndexes[it.id] ?: Int.MAX_VALUE },
+ { it.title.lowercase() }
+ )
+ )
}
private fun toggleItemSelection(select: Boolean, calendar: CalendarEntity, pos: Int) {
diff --git a/app/src/main/kotlin/org/fossify/calendar/adapters/ReorderCalendarsAdapter.kt b/app/src/main/kotlin/org/fossify/calendar/adapters/ReorderCalendarsAdapter.kt
new file mode 100644
index 000000000..8e93a5c1d
--- /dev/null
+++ b/app/src/main/kotlin/org/fossify/calendar/adapters/ReorderCalendarsAdapter.kt
@@ -0,0 +1,49 @@
+package org.fossify.calendar.adapters
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import org.fossify.calendar.activities.SimpleActivity
+import org.fossify.calendar.databinding.ItemReorderCalendarBinding
+import org.fossify.calendar.models.CalendarEntity
+import org.fossify.commons.extensions.getProperBackgroundColor
+import org.fossify.commons.extensions.getProperTextColor
+import org.fossify.commons.extensions.setFillWithStroke
+
+class ReorderCalendarsAdapter(
+ val activity: SimpleActivity,
+ val calendars: ArrayList,
+ val onStartDrag: (RecyclerView.ViewHolder) -> Unit
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalendarViewHolder {
+ return CalendarViewHolder(
+ binding = ItemReorderCalendarBinding.inflate(activity.layoutInflater, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: CalendarViewHolder, position: Int) =
+ holder.bindView(calendar = calendars[position])
+
+ override fun getItemCount() = calendars.size
+
+ inner class CalendarViewHolder(val binding: ItemReorderCalendarBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ fun bindView(calendar: CalendarEntity) {
+ binding.apply {
+ reorderCalendarTitle.text = calendar.getDisplayTitle()
+ reorderCalendarTitle.setTextColor(activity.getProperTextColor())
+ reorderCalendarColor.setFillWithStroke(
+ calendar.color,
+ activity.getProperBackgroundColor()
+ )
+ reorderCalendarDragHandle.setOnTouchListener { _, event ->
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ onStartDrag(this@CalendarViewHolder)
+ }
+ false
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/fossify/calendar/dialogs/ReorderCalendarsDialog.kt b/app/src/main/kotlin/org/fossify/calendar/dialogs/ReorderCalendarsDialog.kt
new file mode 100644
index 000000000..6614e2c77
--- /dev/null
+++ b/app/src/main/kotlin/org/fossify/calendar/dialogs/ReorderCalendarsDialog.kt
@@ -0,0 +1,74 @@
+package org.fossify.calendar.dialogs
+
+import androidx.appcompat.app.AlertDialog
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.RecyclerView
+import org.fossify.calendar.activities.SimpleActivity
+import org.fossify.calendar.adapters.ReorderCalendarsAdapter
+import org.fossify.calendar.databinding.DialogReorderCalendarsBinding
+import org.fossify.calendar.models.CalendarEntity
+import org.fossify.commons.extensions.getAlertDialogBuilder
+import org.fossify.commons.extensions.setupDialogStuff
+import org.fossify.commons.extensions.viewBinding
+
+class ReorderCalendarsDialog(
+ val activity: SimpleActivity,
+ calendars: List,
+ val callback: (List) -> Unit
+) {
+ private var dialog: AlertDialog? = null
+ private lateinit var itemTouchHelper: ItemTouchHelper
+ private val binding by activity.viewBinding(DialogReorderCalendarsBinding::inflate)
+ private val orderedCalendars = ArrayList(calendars)
+
+ init {
+ val adapter = ReorderCalendarsAdapter(activity, orderedCalendars) { holder ->
+ itemTouchHelper.startDrag(holder)
+ }
+ binding.reorderCalendarsList.adapter = adapter
+
+ itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
+ override fun getMovementFlags(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder
+ ): Int = makeMovementFlags(ItemTouchHelper.ACTION_STATE_DRAG, 0)
+
+ override fun onMove(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder
+ ): Boolean {
+ val fromPosition = viewHolder.bindingAdapterPosition
+ val toPosition = target.bindingAdapterPosition
+ if (fromPosition < 0 || toPosition < 0) {
+ return false
+ }
+
+ orderedCalendars.add(toPosition, orderedCalendars.removeAt(fromPosition))
+ adapter.notifyItemMoved(fromPosition, toPosition)
+ return true
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
+
+ override fun isLongPressDragEnabled() = false
+
+ override fun isItemViewSwipeEnabled() = false
+ })
+ itemTouchHelper.attachToRecyclerView(binding.reorderCalendarsList)
+
+ activity.getAlertDialogBuilder()
+ .setPositiveButton(org.fossify.commons.R.string.ok) { _, _ -> confirmReorder() }
+ .setNegativeButton(org.fossify.commons.R.string.cancel, null)
+ .apply {
+ activity.setupDialogStuff(binding.root, this) { alertDialog ->
+ dialog = alertDialog
+ }
+ }
+ }
+
+ private fun confirmReorder() {
+ callback(orderedCalendars.mapNotNull { it.id })
+ dialog?.dismiss()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt b/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt
index f9dd6af1e..f21910852 100644
--- a/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/extensions/Context.kt
@@ -834,7 +834,8 @@ fun Context.addDayEvents(
fun Context.getEventListItems(
events: List,
addSectionDays: Boolean = true,
- addSectionMonths: Boolean = true
+ addSectionMonths: Boolean = true,
+ forcedDayCode: String? = null
): ArrayList {
val listItems = ArrayList(events.size)
val replaceDescription = config.replaceDescription
@@ -860,7 +861,7 @@ fun Context.getEventListItems(
val todayCode = Formatter.getDayCodeFromTS(now)
sorted.forEach {
- val code = Formatter.getDayCodeFromTS(it.startTS)
+ val code = forcedDayCode ?: Formatter.getDayCodeFromTS(it.startTS)
if (addSectionMonths) {
val monthLabel = Formatter.getLongMonthYear(this, code)
if (monthLabel != prevMonthLabel) {
@@ -873,7 +874,12 @@ fun Context.getEventListItems(
if (code != prevCode && addSectionDays) {
val day = Formatter.getDateDayTitle(code)
val isToday = code == todayCode
- val listSectionDay = ListSectionDay(day, code, isToday, !isToday && it.startTS < now)
+ val isPastSection = if (forcedDayCode != null) {
+ !isToday && Formatter.getDayStartTS(code) < now
+ } else {
+ !isToday && it.startTS < now
+ }
+ val listSectionDay = ListSectionDay(day, code, isToday, isPastSection)
listItems.add(listSectionDay)
prevCode = code
}
diff --git a/app/src/main/kotlin/org/fossify/calendar/fragments/DayFragmentsHolder.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/DayFragmentsHolder.kt
index 67231c049..97b0fc1ff 100644
--- a/app/src/main/kotlin/org/fossify/calendar/fragments/DayFragmentsHolder.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/fragments/DayFragmentsHolder.kt
@@ -62,6 +62,7 @@ class DayFragmentsHolder : MyFragmentHolder(), NavigationListener {
override fun onPageSelected(position: Int) {
currentDayCode = codes[position]
+ (activity as? MainActivity)?.setToolbarTitle(Formatter.getDayTitle(requireContext(), codes[position]))
val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible()
if (isGoToTodayVisible != shouldGoToTodayBeVisible) {
(activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible)
@@ -70,6 +71,7 @@ class DayFragmentsHolder : MyFragmentHolder(), NavigationListener {
}
})
currentItem = defaultDailyPage
+ (activity as? MainActivity)?.setToolbarTitle(Formatter.getDayTitle(requireContext(), codes[defaultDailyPage]))
}
}
diff --git a/app/src/main/kotlin/org/fossify/calendar/fragments/EventListFragment.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/EventListFragment.kt
index 5ee1b7792..d3bd3e0d2 100644
--- a/app/src/main/kotlin/org/fossify/calendar/fragments/EventListFragment.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/fragments/EventListFragment.kt
@@ -21,6 +21,7 @@ import org.fossify.calendar.extensions.getViewBitmap
import org.fossify.calendar.extensions.launchNewEventIntent
import org.fossify.calendar.extensions.printBitmap
import org.fossify.calendar.extensions.seconds
+import org.fossify.calendar.helpers.DAY_CODE
import org.fossify.calendar.helpers.EVENTS_LIST_VIEW
import org.fossify.calendar.helpers.FETCH_INTERVAL
import org.fossify.calendar.helpers.Formatter
@@ -28,10 +29,12 @@ import org.fossify.calendar.helpers.INITIAL_EVENTS
import org.fossify.calendar.helpers.MIN_EVENTS_TRESHOLD
import org.fossify.calendar.helpers.UPDATE_BOTTOM
import org.fossify.calendar.helpers.UPDATE_TOP
+import org.fossify.calendar.helpers.getNowSeconds
import org.fossify.calendar.models.Event
import org.fossify.calendar.models.ListEvent
import org.fossify.calendar.models.ListItem
import org.fossify.calendar.models.ListSectionDay
+import org.fossify.calendar.models.ListSectionMonth
import org.fossify.commons.extensions.areSystemAnimationsEnabled
import org.fossify.commons.extensions.beGoneIf
import org.fossify.commons.extensions.beVisibleIf
@@ -47,6 +50,9 @@ import org.fossify.commons.interfaces.RefreshRecyclerViewListener
import org.fossify.commons.views.MyLinearLayoutManager
import org.fossify.commons.views.MyRecyclerView
import org.joda.time.DateTime
+import kotlin.math.max
+import kotlin.math.min
+
class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
private var mEvents = ArrayList()
@@ -58,6 +64,10 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
private var use24HourFormat = false
+ private var mDayCode = ""
+
+ private var mToolbarTitle = ""
+
private lateinit var binding: FragmentEventListBinding
override val viewType = EVENTS_LIST_VIEW
@@ -80,12 +90,16 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
}
use24HourFormat = requireContext().config.use24HourFormat
+ mDayCode = arguments?.getString(DAY_CODE) ?: ""
return binding.root
}
override fun onResume() {
super.onResume()
checkEvents()
+ val defaultTitle = Formatter.getLongMonthYear(requireContext(), Formatter.getTodayCode())
+ mToolbarTitle = defaultTitle
+ (activity as? MainActivity)?.setToolbarTitle(defaultTitle)
val use24Hour = requireContext().config.use24HourFormat
if (use24Hour != use24HourFormat) {
use24HourFormat = use24Hour
@@ -102,9 +116,11 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
private fun checkEvents() {
if (!wereInitialEventsAdded) {
- minFetchedTS =
- DateTime().minusMinutes(requireContext().config.displayPastEvents).seconds()
- maxFetchedTS = DateTime().plusMonths(6).seconds()
+ val now = DateTime()
+ val defaultMin = now.minusMinutes(requireContext().config.displayPastEvents).seconds()
+ val defaultMax = now.plusMonths(6).seconds()
+ minFetchedTS = if (mDayCode.isEmpty()) defaultMin else min(defaultMin, Formatter.getDayStartTS(mDayCode))
+ maxFetchedTS = if (mDayCode.isEmpty()) defaultMax else max(defaultMax, Formatter.getDayEndTS(mDayCode))
}
requireContext().eventsHelper.getEvents(minFetchedTS, maxFetchedTS) { events ->
@@ -155,6 +171,11 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
}
}.apply {
binding.calendarEventsList.adapter = this
+ if (mDayCode.isNotEmpty()) {
+ scrollToAnchorDay()
+ } else {
+ scrollToNextAppointment()
+ }
}
if (requireContext().areSystemAnimationsEnabled) {
@@ -174,6 +195,11 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
binding.calendarEventsList.addOnScrollListener(object :
RecyclerView.OnScrollListener() {
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ super.onScrolled(recyclerView, dx, dy)
+ updateToolbarMonth()
+ }
+
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!hasBeenScrolled) {
@@ -199,6 +225,35 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
}
}
checkPlaceholderVisibility()
+ updateToolbarMonth()
+ }
+ }
+
+ private fun updateToolbarMonth() {
+ val adapter = binding.calendarEventsList.adapter as? EventListAdapter ?: return
+ val layoutManager = binding.calendarEventsList.layoutManager as? LinearLayoutManager ?: return
+ val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()
+ if (firstVisiblePosition == RecyclerView.NO_POSITION || firstVisiblePosition >= adapter.listItems.size) {
+ return
+ }
+
+ var monthTitle = ""
+ val firstItem = adapter.listItems.getOrNull(firstVisiblePosition)
+ if (firstItem is ListSectionMonth) {
+ monthTitle = firstItem.title
+ } else {
+ for (i in firstVisiblePosition downTo 0) {
+ val item = adapter.listItems.getOrNull(i)
+ if (item is ListSectionDay) {
+ monthTitle = Formatter.getLongMonthYear(requireContext(), item.code)
+ break
+ }
+ }
+ }
+
+ if (monthTitle.isNotEmpty() && monthTitle != mToolbarTitle) {
+ mToolbarTitle = monthTitle
+ (activity as? MainActivity)?.setToolbarTitle(monthTitle)
}
}
@@ -220,6 +275,74 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
}
}
+ private fun scrollToAnchorDay() {
+ val adapter = binding.calendarEventsList.adapter as? EventListAdapter ?: return
+ val targetIndex = adapter.listItems.indexOfFirst { it is ListSectionDay && it.code >= mDayCode }
+ if (targetIndex != -1) {
+ binding.calendarEventsList.post {
+ (binding.calendarEventsList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
+ targetIndex,
+ 0
+ )
+ binding.calendarEventsList.onGlobalLayout {
+ hasBeenScrolled = false
+ (activity as? MainActivity)?.refreshItems()
+ (activity as? MainActivity)?.refreshMenuItems()
+ }
+ }
+ }
+ }
+
+ private fun scrollToNextAppointment() {
+ val adapter = binding.calendarEventsList.adapter as? EventListAdapter ?: return
+ val now = getNowSeconds()
+ // Only consider events that STARTED TODAY or later. The fetch window includes
+ // multi-day events from previous days (e.g. a parking booking that started
+ // days ago but ends tonight): those sort ABOVE today's sections, so picking
+ // them scrolled the list into yesterday instead of to the next meeting.
+ val todayStartTS = Formatter.getDayStartTS(Formatter.getTodayCode())
+ val upcoming = adapter.listItems.filterIsInstance().filter { it.endTS > now }
+ val pick = upcoming.firstOrNull { it.startTS >= todayStartTS && !it.isAllDay }
+ ?: upcoming.firstOrNull { it.startTS >= todayStartTS }
+ ?: upcoming.firstOrNull { !it.isAllDay }
+ ?: upcoming.firstOrNull()
+ val targetIndex = if (pick != null) {
+ adapter.listItems.indexOfFirst { it === pick }
+ } else {
+ -1
+ }
+
+ // Fallback: if nothing has endTS > now (e.g. shifted timestamps), at least
+ // jump to today's section instead of leaving the list at the top of yesterday.
+ val scrollIndex = if (targetIndex != -1) {
+ targetIndex
+ } else {
+ adapter.listItems.indexOfFirst { it is ListSectionDay && !it.isPastSection }
+ }
+
+ if (scrollIndex != -1) {
+ binding.calendarEventsList.post {
+ (binding.calendarEventsList.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
+ scrollIndex,
+ 0
+ )
+ binding.calendarEventsList.onGlobalLayout {
+ // Re-assert after layout: state restoration or the layout animation
+ // can win over the post() scroll and dump the list back to the top.
+ val firstVisible = (binding.calendarEventsList.layoutManager as? LinearLayoutManager)
+ ?.findFirstVisibleItemPosition() ?: RecyclerView.NO_POSITION
+ if (firstVisible != scrollIndex && !hasBeenScrolled) {
+ (binding.calendarEventsList.layoutManager as LinearLayoutManager)
+ .scrollToPositionWithOffset(scrollIndex, 0)
+ }
+ hasBeenScrolled = false
+ (activity as? MainActivity)?.refreshItems()
+ (activity as? MainActivity)?.refreshMenuItems()
+ }
+ }
+ }
+ }
+
private fun fetchPreviousPeriod() {
val lastPosition =
(binding.calendarEventsList.layoutManager as MyLinearLayoutManager).findLastVisibleItemPosition()
@@ -258,6 +381,13 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
}
override fun goToToday() {
+ // In the simple all-events list, "Today" means current time: reuse the snap
+ // so it lands on what is running now or the next appointment. The snap's own
+ // fallback still jumps to the top of today's section when nothing is upcoming.
+ if (mDayCode.isEmpty()) {
+ scrollToNextAppointment()
+ return
+ }
val listItems = requireContext().getEventListItems(mEvents)
val firstNonPastSectionIndex =
listItems.indexOfFirst { it is ListSectionDay && !it.isPastSection }
@@ -282,7 +412,7 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener {
override fun shouldGoToTodayBeVisible() = hasBeenScrolled
- override fun getNewEventDayCode() = Formatter.getTodayCode()
+ override fun getNewEventDayCode() = mDayCode.ifEmpty { Formatter.getTodayCode() }
override fun printView() {
binding.apply {
diff --git a/app/src/main/kotlin/org/fossify/calendar/fragments/MonthDayFragmentsHolder.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/MonthDayFragmentsHolder.kt
index b33add80a..0b85eeac8 100644
--- a/app/src/main/kotlin/org/fossify/calendar/fragments/MonthDayFragmentsHolder.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/fragments/MonthDayFragmentsHolder.kt
@@ -63,6 +63,7 @@ class MonthDayFragmentsHolder : MyFragmentHolder(), NavigationListener {
override fun onPageSelected(position: Int) {
currentDayCode = codes[position]
+ updateMonthTitle(codes[position])
val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible()
if (isGoToTodayVisible != shouldGoToTodayBeVisible) {
(activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible)
@@ -71,9 +72,14 @@ class MonthDayFragmentsHolder : MyFragmentHolder(), NavigationListener {
}
})
currentItem = defaultMonthlyPage
+ updateMonthTitle(codes[defaultMonthlyPage])
}
}
+ private fun updateMonthTitle(code: String) {
+ (activity as? MainActivity)?.setToolbarTitle(Formatter.getMonthTitle(requireContext(), Formatter.getDateTimeFromCode(code)))
+ }
+
private fun getMonths(code: String): List {
val months = ArrayList(PREFILLED_MONTHS)
val today = Formatter.getDateTimeFromCode(code).withDayOfMonth(1)
diff --git a/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragment.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragment.kt
index 4fcba53b0..df9e3d62c 100644
--- a/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragment.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragment.kt
@@ -1,16 +1,13 @@
package org.fossify.calendar.fragments
import android.content.Context
-import android.content.res.Resources
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
-import org.fossify.calendar.R
import org.fossify.calendar.activities.MainActivity
import org.fossify.calendar.databinding.FragmentMonthBinding
-import org.fossify.calendar.databinding.TopNavigationBinding
import org.fossify.calendar.extensions.config
import org.fossify.calendar.extensions.getViewBitmap
import org.fossify.calendar.extensions.printBitmap
@@ -21,37 +18,27 @@ import org.fossify.calendar.helpers.MonthlyCalendarImpl
import org.fossify.calendar.interfaces.MonthlyCalendar
import org.fossify.calendar.interfaces.NavigationListener
import org.fossify.calendar.models.DayMonthly
-import org.fossify.commons.extensions.applyColorFilter
-import org.fossify.commons.extensions.beGone
-import org.fossify.commons.extensions.beVisible
-import org.fossify.commons.extensions.getProperTextColor
import org.joda.time.DateTime
class MonthFragment : Fragment(), MonthlyCalendar {
- private var mTextColor = 0
+ var listener: NavigationListener? = null
+
private var mShowWeekNumbers = false
private var mDayCode = ""
private var mPackageName = ""
private var mLastHash = 0L
private var mCalendar: MonthlyCalendarImpl? = null
- var listener: NavigationListener? = null
-
- private lateinit var mRes: Resources
private lateinit var mConfig: Config
private lateinit var binding: FragmentMonthBinding
- private lateinit var topNavigationBinding: TopNavigationBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentMonthBinding.inflate(inflater, container, false)
- topNavigationBinding = TopNavigationBinding.bind(binding.root)
- mRes = resources
mPackageName = requireActivity().packageName
mDayCode = requireArguments().getString(DAY_CODE)!!
mConfig = requireContext().config
storeStateVariables()
- setupButtons()
mCalendar = MonthlyCalendarImpl(this, requireContext())
return binding.root
@@ -96,74 +83,21 @@ class MonthFragment : Fragment(), MonthlyCalendar {
mLastHash = newHash
activity?.runOnUiThread {
- topNavigationBinding.topValue.apply {
- text = month
- contentDescription = text
-
- if (activity != null) {
- setTextColor(requireActivity().getProperTextColor())
- }
- }
updateDays(days)
}
}
- private fun setupButtons() {
- mTextColor = requireContext().getProperTextColor()
-
- topNavigationBinding.topLeftArrow.apply {
- applyColorFilter(mTextColor)
- background = null
- setOnClickListener {
- listener?.goLeft()
- }
-
- val pointerLeft = requireContext().getDrawable(org.fossify.commons.R.drawable.ic_chevron_left_vector)
- pointerLeft?.isAutoMirrored = true
- setImageDrawable(pointerLeft)
- contentDescription = getString(R.string.accessibility_previous_month)
- }
-
- topNavigationBinding.topRightArrow.apply {
- applyColorFilter(mTextColor)
- background = null
- setOnClickListener {
- listener?.goRight()
- }
-
- val pointerRight = requireContext().getDrawable(org.fossify.commons.R.drawable.ic_chevron_right_vector)
- pointerRight?.isAutoMirrored = true
- setImageDrawable(pointerRight)
- contentDescription = getString(R.string.accessibility_next_month)
- }
-
- topNavigationBinding.topValue.apply {
- setTextColor(requireContext().getProperTextColor())
- setOnClickListener {
- (activity as MainActivity).showGoToDateDialog()
- }
- }
- }
-
private fun updateDays(days: ArrayList) {
binding.monthViewWrapper.updateDays(days, true) {
- (activity as MainActivity).openDayFromMonthly(Formatter.getDateTimeFromCode(it.code))
+ (activity as MainActivity).openEventListFromMonthly(Formatter.getDateTimeFromCode(it.code))
}
}
fun printCurrentView() {
- topNavigationBinding.apply {
- topLeftArrow.beGone()
- topRightArrow.beGone()
- topValue.setTextColor(resources.getColor(org.fossify.commons.R.color.theme_light_text_color))
- binding.monthViewWrapper.togglePrintMode()
-
- requireContext().printBitmap(binding.monthCalendarHolder.getViewBitmap())
-
- topLeftArrow.beVisible()
- topRightArrow.beVisible()
- topValue.setTextColor(requireContext().getProperTextColor())
- binding.monthViewWrapper.togglePrintMode()
- }
+ binding.monthViewWrapper.togglePrintMode()
+
+ requireContext().printBitmap(binding.monthCalendarHolder.getViewBitmap())
+
+ binding.monthViewWrapper.togglePrintMode()
}
}
diff --git a/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragmentsHolder.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragmentsHolder.kt
index 1096cdfcd..99b3c4224 100644
--- a/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragmentsHolder.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/fragments/MonthFragmentsHolder.kt
@@ -65,6 +65,7 @@ class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener {
override fun onPageSelected(position: Int) {
currentDayCode = codes[position]
+ updateMonthTitle(codes[position])
val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible()
if (isGoToTodayVisible != shouldGoToTodayBeVisible) {
(activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible)
@@ -73,9 +74,14 @@ class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener {
}
})
currentItem = defaultMonthlyPage
+ updateMonthTitle(codes[defaultMonthlyPage])
}
}
+ private fun updateMonthTitle(code: String) {
+ (activity as? MainActivity)?.setToolbarTitle(Formatter.getMonthTitle(requireContext(), Formatter.getDateTimeFromCode(code)))
+ }
+
private fun getMonths(code: String): List {
val months = ArrayList(PREFILLED_MONTHS)
val today = Formatter.getDateTimeFromCode(code).withDayOfMonth(1)
diff --git a/app/src/main/kotlin/org/fossify/calendar/fragments/YearFragmentsHolder.kt b/app/src/main/kotlin/org/fossify/calendar/fragments/YearFragmentsHolder.kt
index e91fb6351..206af77f5 100644
--- a/app/src/main/kotlin/org/fossify/calendar/fragments/YearFragmentsHolder.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/fragments/YearFragmentsHolder.kt
@@ -63,6 +63,7 @@ class YearFragmentsHolder : MyFragmentHolder(), NavigationListener {
override fun onPageSelected(position: Int) {
currentYear = years[position]
+ (activity as? MainActivity)?.setToolbarTitle(currentYear.toString())
val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible()
if (isGoToTodayVisible != shouldGoToTodayBeVisible) {
(activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible)
@@ -71,6 +72,7 @@ class YearFragmentsHolder : MyFragmentHolder(), NavigationListener {
}
})
currentItem = defaultYearlyPage
+ (activity as? MainActivity)?.setToolbarTitle(years[defaultYearlyPage].toString())
}
}
diff --git a/app/src/main/kotlin/org/fossify/calendar/helpers/Config.kt b/app/src/main/kotlin/org/fossify/calendar/helpers/Config.kt
index 488929489..ff39dde31 100644
--- a/app/src/main/kotlin/org/fossify/calendar/helpers/Config.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/helpers/Config.kt
@@ -9,6 +9,7 @@ import org.fossify.calendar.extensions.scheduleCalDAVSync
import org.fossify.commons.extensions.getDefaultAlarmTitle
import org.fossify.commons.helpers.BaseConfig
import org.fossify.commons.helpers.DAY_MINUTES
+import org.fossify.commons.helpers.FONT_SIZE_MEDIUM
import org.fossify.commons.helpers.YEAR_SECONDS
class Config(context: Context) : BaseConfig(context) {
@@ -73,6 +74,18 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getInt(VIEW, MONTHLY_VIEW)
set(view) = prefs.edit().putInt(VIEW, view).apply()
+ var viewSelectionWasCustomized: Boolean
+ get() = prefs.getBoolean(VIEW_CUSTOMIZED, false)
+ set(customized) = prefs.edit().putBoolean(VIEW_CUSTOMIZED, customized).apply()
+
+ var lastFoldPosture: Int
+ get() = prefs.getInt(LAST_FOLD_POSTURE, FOLD_POSTURE_UNSUPPORTED)
+ set(posture) = prefs.edit().putInt(LAST_FOLD_POSTURE, posture).apply()
+
+ var lastSwdp: Int
+ get() = prefs.getInt(LAST_SWDP, 0)
+ set(swdp) = prefs.edit().putInt(LAST_SWDP, swdp).apply()
+
var lastEventReminderMinutes1: Int
get() = prefs.getInt(LAST_EVENT_REMINDER_MINUTES, 10)
set(lastEventReminderMinutes) = prefs.edit()
@@ -102,6 +115,14 @@ class Config(context: Context) : BaseConfig(context) {
set(quickFilterCalendars) = prefs.edit().remove(QUICK_FILTER_CALENDARS)
.putStringSet(QUICK_FILTER_CALENDARS, quickFilterCalendars).apply()
+ var quickFilterCalendarsOrder: List
+ get() = prefs.getString(QUICK_FILTER_CALENDARS_ORDER, "")!!
+ .split(',')
+ .mapNotNull { it.toLongOrNull() }
+ set(quickFilterCalendarsOrder) = prefs.edit()
+ .putString(QUICK_FILTER_CALENDARS_ORDER, quickFilterCalendarsOrder.joinToString(","))
+ .apply()
+
fun addQuickFilterCalendar(type: String) {
val currQuickFilterCalendars = HashSet(quickFilterCalendars)
currQuickFilterCalendars.add(type)
@@ -160,6 +181,10 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(SHOW_GRID, false)
set(showGrid) = prefs.edit().putBoolean(SHOW_GRID, showGrid).apply()
+ var monthViewFontSize: Int
+ get() = prefs.getInt(MONTH_VIEW_FONT_SIZE, FONT_SIZE_MEDIUM)
+ set(monthViewFontSize) = prefs.edit().putInt(MONTH_VIEW_FONT_SIZE, monthViewFontSize).apply()
+
var loopReminders: Boolean
get() = prefs.getBoolean(LOOP_REMINDERS, false)
set(loopReminders) = prefs.edit().putBoolean(LOOP_REMINDERS, loopReminders).apply()
@@ -294,6 +319,11 @@ class Config(context: Context) : BaseConfig(context) {
set(highlightWeekendsColor) = prefs.edit()
.putInt(HIGHLIGHT_WEEKENDS_COLOR, highlightWeekendsColor).apply()
+ // 0 = never picked, means "use the app's primary color" (the old behaviour)
+ var todayCircleColor: Int
+ get() = prefs.getInt(TODAY_CIRCLE_COLOR, 0)
+ set(todayCircleColor) = prefs.edit().putInt(TODAY_CIRCLE_COLOR, todayCircleColor).apply()
+
var lastUsedEventSpan: Int
get() = prefs.getInt(LAST_USED_EVENT_SPAN, YEAR_SECONDS)
set(lastUsedEventSpan) = prefs.edit().putInt(LAST_USED_EVENT_SPAN, lastUsedEventSpan)
diff --git a/app/src/main/kotlin/org/fossify/calendar/helpers/Constants.kt b/app/src/main/kotlin/org/fossify/calendar/helpers/Constants.kt
index 45ed8acab..13f9cd0bf 100644
--- a/app/src/main/kotlin/org/fossify/calendar/helpers/Constants.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/helpers/Constants.kt
@@ -94,11 +94,18 @@ const val VIBRATE = "vibrate"
const val REMINDER_SOUND_URI = "reminder_sound_uri"
const val REMINDER_SOUND_TITLE = "reminder_sound_title"
const val VIEW = "view"
+const val VIEW_CUSTOMIZED = "view_customized"
+const val LAST_FOLD_POSTURE = "last_fold_posture"
+const val LAST_SWDP = "last_swdp"
+const val FOLD_POSTURE_UNSUPPORTED = -1
+const val FOLD_POSTURE_OPEN = 0
+const val FOLD_POSTURE_COVER = 1
const val LAST_EVENT_REMINDER_MINUTES = "reminder_minutes"
const val LAST_EVENT_REMINDER_MINUTES_2 = "reminder_minutes_2"
const val LAST_EVENT_REMINDER_MINUTES_3 = "reminder_minutes_3"
const val DISPLAY_CALENDARS = "display_event_types"
const val QUICK_FILTER_CALENDARS = "quick_filter_event_types"
+const val QUICK_FILTER_CALENDARS_ORDER = "quick_filter_event_types_order"
const val LIST_WIDGET_VIEW_TO_OPEN = "list_widget_view_to_open"
const val CALDAV_SYNC = "caldav_sync"
const val CALDAV_SYNCED_CALENDAR_IDS = "caldav_synced_calendar_ids"
@@ -135,8 +142,10 @@ const val EXPORT_TASKS = "export_tasks"
const val EXPORT_PAST_EVENTS = "export_past_events"
const val WEEKLY_VIEW_ITEM_HEIGHT_MULTIPLIER = "weekly_view_item_height_multiplier"
const val WEEKLY_VIEW_DAYS = "weekly_view_days"
+const val MONTH_VIEW_FONT_SIZE = "month_view_font_size"
const val HIGHLIGHT_WEEKENDS = "highlight_weekends"
const val HIGHLIGHT_WEEKENDS_COLOR = "highlight_weekends_color"
+const val TODAY_CIRCLE_COLOR = "today_circle_color"
const val LAST_USED_EVENT_SPAN = "last_used_event_span"
const val ALLOW_CREATING_TASKS = "allow_creating_tasks"
const val WAS_FILTERED_OUT_WARNING_SHOWN = "was_filtered_out_warning_shown"
diff --git a/app/src/main/kotlin/org/fossify/calendar/helpers/Formatter.kt b/app/src/main/kotlin/org/fossify/calendar/helpers/Formatter.kt
index 3bbcdefe2..08ec2e9ef 100644
--- a/app/src/main/kotlin/org/fossify/calendar/helpers/Formatter.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/helpers/Formatter.kt
@@ -111,6 +111,12 @@ object Formatter {
fun getShortMonthName(context: Context, id: Int) = context.resources.getStringArray(org.fossify.commons.R.array.months_short)[id - 1]
+ fun getMonthTitle(context: Context, dateTime: DateTime): String {
+ val month = getMonthName(context, dateTime.monthOfYear)
+ val targetYear = dateTime.toString(YEAR_PATTERN)
+ return "$month $targetYear"
+ }
+
fun getHourPattern(context: Context) = if (context.config.use24HourFormat) PATTERN_HOURS_24 else PATTERN_HOURS_12
fun getTimePattern(context: Context) = if (context.config.use24HourFormat) PATTERN_TIME_24 else PATTERN_TIME_12
diff --git a/app/src/main/kotlin/org/fossify/calendar/views/MonthView.kt b/app/src/main/kotlin/org/fossify/calendar/views/MonthView.kt
index bf41c1d49..7b83b70f8 100644
--- a/app/src/main/kotlin/org/fossify/calendar/views/MonthView.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/views/MonthView.kt
@@ -5,32 +5,36 @@ import android.graphics.*
import android.text.TextPaint
import android.text.TextUtils
import android.util.AttributeSet
-import android.util.SparseIntArray
import android.view.View
import org.fossify.calendar.R
import org.fossify.calendar.extensions.*
import org.fossify.calendar.helpers.COLUMN_COUNT
-import org.fossify.calendar.helpers.Formatter
import org.fossify.calendar.helpers.ROW_COUNT
import org.fossify.calendar.models.DayMonthly
import org.fossify.calendar.models.Event
-import org.fossify.calendar.models.MonthViewEvent
import org.fossify.commons.extensions.*
+import org.fossify.commons.helpers.FONT_SIZE_EXTRA_LARGE
+import org.fossify.commons.helpers.FONT_SIZE_LARGE
+import org.fossify.commons.helpers.FONT_SIZE_SMALL
import org.fossify.commons.helpers.FontHelper
-import org.fossify.commons.helpers.HIGHER_ALPHA
import org.fossify.commons.helpers.LOWER_ALPHA
import org.fossify.commons.helpers.MEDIUM_ALPHA
import org.joda.time.DateTime
-import org.joda.time.Days
import kotlin.math.max
import kotlin.math.min
// used in the Monthly view fragment, 1 view per screen
class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(context, attrs, defStyle) {
+ private class RowEvent(val event: Event) {
+ var firstCol = 0
+ var lastCol = 0
+ }
+
companion object {
- private const val BG_CORNER_RADIUS = 8f
private const val EVENT_DOT_COLUMN_COUNT = 3
private const val EVENT_DOT_ROW_COUNT = 1
+ private const val FONT_SCALE_FLOOR = 0.85f
+ private const val FONT_SCALE_SAFETY = 0.97f
}
private var textPaint: Paint
@@ -39,17 +43,21 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
private var circleStrokePaint: Paint
private var plusTextPaint: Paint
private var eventDotPaint: Paint
+ private var eventStripPaint: Paint
private var config = context.config
private var dayWidth = 0f
- private var dayHeight = 0f
private var primaryColor = 0
private var textColor = 0
private var weekendsTextColor = 0
private var weekDaysLetterHeight = 0
+ private var normalTextSize = 0
+ private var baseNormalTextSize = 0
+ private var defaultNormalTextSize = 0
private var eventTitleHeight = 0
+ private var baseEventTitleHeight = 0
+ private var defaultEventTitleHeight = 0
private var currDayOfWeek = 0
private var smallPadding = 0
- private var maxEventsPerDay = 0
private var horizontalOffset = 0
private var showWeekNumbers = false
private var dimPastEvents = true
@@ -57,14 +65,23 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
private var highlightWeekends = false
private var isPrintVersion = false
private var isMonthDayView = false
- private var allEvents = ArrayList()
- private var bgRectF = RectF()
+ private var contentHeight = 0
+ private var minimumContentHeight = 0
private var dayTextRect = Rect()
private var dayLetters = ArrayList()
private var days = ArrayList()
- private var dayVerticalOffsets = SparseIntArray()
+ private var rowHeights = IntArray(ROW_COUNT)
private var selectedDayCoords = Point(-1, -1)
+ private val dayNumberHeight: Int
+ get() = (normalTextSize * 1.4f).toInt()
+
+ private val contentTop: Int
+ get() = smallPadding + normalTextSize
+
+ private val eventLineStep: Int
+ get() = eventTitleHeight + smallPadding * 2
+
constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
init {
@@ -77,8 +94,10 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
highlightWeekends = config.highlightWeekends
smallPadding = resources.displayMetrics.density.toInt()
- val normalTextSize = resources.getDimensionPixelSize(org.fossify.commons.R.dimen.normal_text_size)
- weekDaysLetterHeight = normalTextSize * 2
+ baseNormalTextSize = resources.getDimensionPixelSize(R.dimen.month_view_text_size)
+ normalTextSize = (baseNormalTextSize * getMonthViewFontSizeScale()).toInt()
+ defaultNormalTextSize = normalTextSize
+ weekDaysLetterHeight = normalTextSize
textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = textColor
@@ -106,8 +125,10 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
color = primaryColor
}
- val smallerTextSize = resources.getDimensionPixelSize(org.fossify.commons.R.dimen.smaller_text_size)
- eventTitleHeight = smallerTextSize
+ val smallerTextSize = resources.getDimensionPixelSize(R.dimen.month_view_event_text_size)
+ baseEventTitleHeight = smallerTextSize
+ defaultEventTitleHeight = (baseEventTitleHeight * getMonthViewFontSizeScale()).toInt()
+ eventTitleHeight = defaultEventTitleHeight
eventTitlePaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
color = textColor
textSize = smallerTextSize.toFloat()
@@ -115,69 +136,158 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
typeface = FontHelper.getTypeface(context)
}
+ eventStripPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ }
+
initWeekDayLetters()
setupCurrentDayOfWeekIndex()
}
+ // the wrapper uses this so that even an empty month still fills the whole screen
+ fun setMinimumContentHeight(value: Int) {
+ if (minimumContentHeight != value) {
+ minimumContentHeight = value
+ requestLayout()
+ }
+ }
+
+ // total height of all 6 rows plus the weekday header
+ fun getContentHeight() = contentHeight
+
+ fun getRowHeight(row: Int): Int = rowHeights[row]
+
+ fun getRowTop(row: Int): Int {
+ var top = weekDaysLetterHeight
+ for (i in 0 until row) {
+ top += rowHeights[i]
+ }
+ return top
+ }
+
fun updateDays(newDays: ArrayList, isMonthDayView: Boolean) {
this.isMonthDayView = isMonthDayView
days = newDays
showWeekNumbers = config.showWeekNumbers
horizontalOffset = context.getWeekNumberWidth()
+ normalTextSize = (baseNormalTextSize * getMonthViewFontSizeScale()).toInt()
+ defaultNormalTextSize = normalTextSize
+ eventTitleHeight = (baseEventTitleHeight * getMonthViewFontSizeScale()).toInt()
+ defaultEventTitleHeight = eventTitleHeight
initWeekDayLetters()
setupCurrentDayOfWeekIndex()
- groupAllEvents()
+ computeContentHeight()
invalidate()
}
- private fun groupAllEvents() {
- days.forEach { day ->
- val dayIndexOnMonthView = day.indexOnMonthView
-
- day.dayEvents.forEach { event ->
- // make sure we properly handle events lasting multiple days and repeating ones
- val validDayEvent = isDayValid(event, day.code)
- val lastEvent = allEvents.lastOrNull { it.id == event.id }
- val notYetAddedOrIsRepeatingEvent = lastEvent == null || lastEvent.endTS <= event.startTS
-
- // handle overlapping repeating events e.g. an event that lasts 3 days, but repeats every 2 days has a one day overlap
- val canOverlap = event.endTS - event.startTS > event.repeatInterval
- val shouldAddEvent = notYetAddedOrIsRepeatingEvent || canOverlap && (lastEvent.startTS < event.startTS)
-
- if (shouldAddEvent && !validDayEvent) {
- val daysCnt = getEventLastingDaysCount(event)
-
- val monthViewEvent = MonthViewEvent(
- id = event.id!!,
- title = event.title,
- startTS = event.startTS,
- endTS = event.endTS,
- color = event.color,
- startDayIndex = dayIndexOnMonthView,
- daysCnt = daysCnt,
- originalStartDayIndex = dayIndexOnMonthView,
- isAllDay = event.getIsAllDay(),
- isPastEvent = event.isPastEvent,
- isTask = event.isTask(),
- isTaskCompleted = event.isTaskCompleted(),
- isAttendeeInviteDeclined = event.isAttendeeInviteDeclined(),
- isEventCanceled = event.isEventCanceled()
- )
- allEvents.add(monthViewEvent)
- }
+ private fun getMonthViewFontSizeScale(): Float = when (config.monthViewFontSize) {
+ FONT_SIZE_SMALL -> 0.85f
+ FONT_SIZE_LARGE -> 1.15f
+ FONT_SIZE_EXTRA_LARGE -> 1.3f
+ else -> 1f
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val width = MeasureSpec.getSize(widthMeasureSpec)
+ val height = MeasureSpec.getSize(heightMeasureSpec)
+ dayWidth = (width - horizontalOffset) / COLUMN_COUNT.toFloat()
+
+ computeContentHeight()
+
+ val desiredHeight = if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
+ max(contentHeight, height)
+ } else {
+ contentHeight
+ }
+ setMeasuredDimension(width, desiredHeight)
+ }
+
+ private fun computeContentHeight() {
+ if (days.isEmpty()) {
+ contentHeight = minimumContentHeight
+ return
+ }
+
+ if (isMonthDayView) {
+ normalTextSize = defaultNormalTextSize
+ eventTitleHeight = defaultEventTitleHeight
+ syncTextSizes()
+ val headerless = max(0, minimumContentHeight - weekDaysLetterHeight)
+ val rawRowHeight = headerless / ROW_COUNT
+ for (y in 0 until ROW_COUNT) {
+ rowHeights[y] = rawRowHeight
}
+ contentHeight = minimumContentHeight
+ return
+ }
+
+ var rowSlots = IntArray(ROW_COUNT)
+ for (y in 0 until ROW_COUNT) {
+ rowSlots[y] = getRowSlots(y)
+ }
+
+ applyFontScale(rowSlots)
+ layoutRows()
+ }
+
+ // shrink the effective text sizes so that the busiest week fits into one uniform row height
+ private fun applyFontScale(rowSlots: IntArray) {
+ normalTextSize = defaultNormalTextSize
+ eventTitleHeight = defaultEventTitleHeight
+ weekDaysLetterHeight = defaultNormalTextSize
+ textPaint.textSize = normalTextSize.toFloat()
+ plusTextPaint.textSize = normalTextSize.toFloat()
+ eventTitlePaint.textSize = eventTitleHeight.toFloat()
+
+ val available = max(0, minimumContentHeight - weekDaysLetterHeight)
+ val uniformRowHeight = available / ROW_COUNT
+ val fullDayNumberHeight = defaultNormalTextSize + smallPadding
+ val fullLineStep = defaultEventTitleHeight + smallPadding * 2
+ var maxRowSlots = 0
+ for (y in 0 until ROW_COUNT) {
+ maxRowSlots = max(maxRowSlots, rowSlots[y])
}
- allEvents = allEvents.asSequence().sortedWith(
- compareBy({ -it.daysCnt }, { !it.isAllDay }, { it.startTS }, { it.endTS }, { it.startDayIndex }, { it.title })
- ).toMutableList() as ArrayList
+ val fullRowHeight = fullDayNumberHeight + maxRowSlots * fullLineStep + smallPadding * 2
+ if (fullRowHeight <= uniformRowHeight) {
+ return
+ }
+
+ val fixedCost = maxRowSlots * smallPadding * 2 + smallPadding * 2
+ val variableCost = defaultNormalTextSize + smallPadding + defaultEventTitleHeight * maxRowSlots
+ val rawScale = (uniformRowHeight - fixedCost) / variableCost
+ val scale = (rawScale * FONT_SCALE_SAFETY).coerceIn(FONT_SCALE_FLOOR, 1f)
+
+ normalTextSize = (defaultNormalTextSize * scale).toInt()
+ eventTitleHeight = (defaultEventTitleHeight * scale).toInt()
+ syncTextSizes()
+ }
+
+ // every week row gets the same height, filling the whole screen evenly
+ private fun layoutRows() {
+ val available = max(0, minimumContentHeight - weekDaysLetterHeight)
+ var remaining = available
+ val base = remaining / ROW_COUNT
+ for (y in 0 until ROW_COUNT) {
+ rowHeights[y] = base
+ remaining -= base
+ }
+ if (remaining > 0) {
+ rowHeights[ROW_COUNT - 1] += remaining
+ }
+
+ contentHeight = minimumContentHeight
+ }
+
+ private fun syncTextSizes() {
+ weekDaysLetterHeight = normalTextSize
+ textPaint.textSize = normalTextSize.toFloat()
+ plusTextPaint.textSize = normalTextSize.toFloat()
+ eventTitlePaint.textSize = eventTitleHeight.toFloat()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- dayVerticalOffsets.clear()
- measureDaySize(canvas)
-
if (config.showGrid && !isMonthDayView) {
drawGrid(canvas)
}
@@ -189,94 +299,53 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
var curId = 0
for (y in 0 until ROW_COUNT) {
+ val rowTop = getRowTop(y)
+ if (!isMonthDayView) {
+ drawMonthEvents(canvas, y, rowTop)
+ }
+
for (x in 0 until COLUMN_COUNT) {
val day = days.getOrNull(curId)
if (day != null) {
val dayNumber = day.value.toString()
- val textPaint = getTextPaint(day)
- textPaint.getTextBounds(dayNumber, 0, dayNumber.length, dayTextRect)
- dayVerticalOffsets.put(day.indexOnMonthView, dayVerticalOffsets[day.indexOnMonthView] + weekDaysLetterHeight)
- val verticalOffset = dayVerticalOffsets[day.indexOnMonthView]
+ val numberPaint = getTextPaint(day)
+ numberPaint.textAlign = Paint.Align.LEFT
+ numberPaint.getTextBounds(dayNumber, 0, dayNumber.length, dayTextRect)
val xPos = x * dayWidth + horizontalOffset
- val yPos = y * dayHeight + verticalOffset
- val textY = yPos + textPaint.textSize
- val xPosCenter = xPos + dayWidth / 2
+ val numberX = xPos + smallPadding
+ val numberBaseline = rowTop + smallPadding + numberPaint.textSize
+ val circleCenterX = numberX + dayTextRect.width() / 2f
+ val circleCenterY = numberBaseline - dayTextRect.height() / 2f
val isDaySelected = selectedDayCoords.x != -1 && x == selectedDayCoords.x && y == selectedDayCoords.y
- if (isDaySelected) {
- canvas.drawCircle(
- xPosCenter,
- textY - dayTextRect.height() / 2,
- textPaint.textSize * 0.8f,
- circleStrokePaint
- )
- if (day.isToday) {
- textPaint.color = textColor
- }
- } else if (day.isToday && !isPrintVersion) {
- canvas.drawCircle(
- xPosCenter,
- textY - dayTextRect.height() / 2,
- textPaint.textSize * 0.8f,
- getCirclePaint(day)
- )
- }
-
- // mark days with a dot for each event
- if (isMonthDayView && !isDaySelected && !day.isToday && day.dayEvents.isNotEmpty()) {
- val height = dayTextRect.height() * 1.25f
- val eventCount = day.dayEvents.size
- val dotRadius = textPaint.textSize * 0.2f
- val stepSize = dotRadius * 2.5f
- val columnCount = EVENT_DOT_COLUMN_COUNT
-
- val dayEventsSorted = day.dayEvents
- .asSequence()
- .sortedWith(
- comparator = compareBy({ it.startTS }, { it.endTS }, { it.title })
- )
- .distinctBy { it.color }
-
- var xDot: Float
- var yDot = yPos + height + textPaint.textSize / 2
- var indexInRow: Int
-
- val dotCount = dayEventsSorted.count()
- for ((index, event) in dayEventsSorted.withIndex()) {
- indexInRow = index % columnCount
- xDot = xPosCenter + stepSize * (indexInRow - (min(dotCount, columnCount)) / 2)
- if (dotCount % 2 == 0) { // center even number of dots
- xDot += stepSize / 2
- }
-
- if (index > 0 && indexInRow == 0) { // next row of dots
- yDot += stepSize
+ when {
+ isDaySelected -> {
+ val strokePaint =
+ if (day.isToday && config.todayCircleColor != 0) {
+ Paint(circleStrokePaint).apply { color = config.todayCircleColor }
+ } else {
+ circleStrokePaint
+ }
+ canvas.drawCircle(circleCenterX, circleCenterY, numberPaint.textSize * 0.8f, strokePaint)
+ if (day.isToday) {
+ numberPaint.color = textColor
}
+ }
- // Always show a + sign if the event count exceeds columnCount.
- if (eventCount - 1 != index && index >= columnCount * EVENT_DOT_ROW_COUNT - 1) {
- plusTextPaint.textSize = stepSize * 1.5f
- canvas.drawText("+", xDot, yDot + dotRadius * 1.2f, plusTextPaint)
- break
- } else {
- val paint = eventDotPaint.apply { color = event.color }
- canvas.drawCircle(xDot, yDot, dotRadius, paint)
- }
+ day.isToday && !isPrintVersion -> {
+ canvas.drawCircle(circleCenterX, circleCenterY, numberPaint.textSize * 0.8f, getCirclePaint(day))
}
}
- canvas.drawText(dayNumber, xPosCenter, textY, textPaint)
- dayVerticalOffsets.put(day.indexOnMonthView, (verticalOffset + textPaint.textSize * 2).toInt())
+ canvas.drawText(dayNumber, numberX, numberBaseline, numberPaint)
+
+ if (isMonthDayView && !isDaySelected && !day.isToday && day.dayEvents.isNotEmpty()) {
+ drawEventDots(canvas, day, xPos, rowTop)
+ }
}
curId++
}
}
-
- if (!isMonthDayView) {
- for (event in allEvents) {
- drawEvent(event, canvas)
- }
- }
}
private fun drawGrid(canvas: Canvas) {
@@ -292,7 +361,7 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
// horizontal lines
canvas.drawLine(0f, 0f, canvas.width.toFloat(), 0f, gridPaint)
for (i in 0 until ROW_COUNT) {
- canvas.drawLine(0f, i * dayHeight + weekDaysLetterHeight, canvas.width.toFloat(), i * dayHeight + weekDaysLetterHeight, gridPaint)
+ canvas.drawLine(0f, getRowTop(i).toFloat(), canvas.width.toFloat(), getRowTop(i).toFloat(), gridPaint)
}
canvas.drawLine(0f, canvas.height.toFloat(), canvas.width.toFloat(), canvas.height.toFloat(), gridPaint)
}
@@ -306,7 +375,7 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
} else if (highlightWeekends && context.isWeekendIndex(i)) {
weekDayLetterPaint = getColoredPaint(weekendsTextColor)
}
- canvas.drawText(dayLetters[i], xPos, weekDaysLetterHeight * 0.7f, weekDayLetterPaint)
+ canvas.drawText(dayLetters[i], xPos, weekDaysLetterHeight * 0.8f, weekDayLetterPaint)
}
}
@@ -322,99 +391,204 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
val id = "$weekOfYear:"
val horizontalMarginFactor = 0.5f
val xPos = horizontalOffset * horizontalMarginFactor
- val yPos = i * dayHeight + weekDaysLetterHeight
- canvas.drawText(id, xPos, yPos + textPaint.textSize, weekNumberPaint)
+ canvas.drawText(id, xPos, getRowTop(i) + textPaint.textSize, weekNumberPaint)
}
}
- private fun measureDaySize(canvas: Canvas) {
- dayWidth = (canvas.width - horizontalOffset) / 7f
- dayHeight = (canvas.height - weekDaysLetterHeight) / ROW_COUNT.toFloat()
- val availableHeightForEvents = dayHeight.toInt() - weekDaysLetterHeight
- maxEventsPerDay = availableHeightForEvents / eventTitleHeight
- }
-
- private fun drawEvent(event: MonthViewEvent, canvas: Canvas) {
- var verticalOffset = 0
- for (i in 0 until min(event.daysCnt, 7 - event.startDayIndex % 7)) {
- verticalOffset = max(verticalOffset, dayVerticalOffsets[event.startDayIndex + i])
- }
- val xPos = event.startDayIndex % 7 * dayWidth + horizontalOffset
- val yPos = (event.startDayIndex / 7) * dayHeight
+ private fun drawEventDots(canvas: Canvas, day: DayMonthly, xPos: Float, rowTop: Int) {
val xPosCenter = xPos + dayWidth / 2
+ val height = dayTextRect.height() * 1.25f
+ val eventCount = day.dayEvents.size
+ val dotRadius = textPaint.textSize * 0.2f
+ val stepSize = dotRadius * 2.5f
+ val columnCount = EVENT_DOT_COLUMN_COUNT
+
+ val dayEventsSorted = day.dayEvents
+ .asSequence()
+ .sortedWith(
+ comparator = compareBy({ it.startTS }, { it.endTS }, { it.title })
+ )
+ .distinctBy { it.color }
+
+ var xDot: Float
+ var yDot = rowTop + dayNumberHeight + height + textPaint.textSize / 2
+ var indexInRow: Int
+
+ val dotCount = dayEventsSorted.count()
+ for ((index, event) in dayEventsSorted.withIndex()) {
+ indexInRow = index % columnCount
+ xDot = xPosCenter + stepSize * (indexInRow - (min(dotCount, columnCount)) / 2)
+ if (dotCount % 2 == 0) { // center even number of dots
+ xDot += stepSize / 2
+ }
- if (verticalOffset - eventTitleHeight * 2 > dayHeight) {
- val paint = getTextPaint(days[event.startDayIndex])
- paint.color = textColor
- canvas.drawText("...", xPosCenter, yPos + verticalOffset - eventTitleHeight / 2, paint)
- return
- }
+ if (index > 0 && indexInRow == 0) { // next row of dots
+ yDot += stepSize
+ }
- // event background rectangle
- val backgroundY = yPos + verticalOffset
- val bgLeft = xPos + smallPadding
- val bgTop = backgroundY + smallPadding - eventTitleHeight
- var bgRight = xPos - smallPadding + dayWidth * event.daysCnt
- val bgBottom = backgroundY + smallPadding * 2
- if (bgRight > canvas.width.toFloat()) {
- bgRight = canvas.width.toFloat() - smallPadding
- val newStartDayIndex = (event.startDayIndex / 7 + 1) * 7
- if (newStartDayIndex < 42) {
- val newEvent = event.copy(startDayIndex = newStartDayIndex, daysCnt = event.daysCnt - (newStartDayIndex - event.startDayIndex))
- drawEvent(newEvent, canvas)
+ // Always show a + sign if the event count exceeds columnCount.
+ if (eventCount - 1 != index && index >= columnCount * EVENT_DOT_ROW_COUNT - 1) {
+ plusTextPaint.textSize = stepSize * 1.5f
+ canvas.drawText("+", xDot, yDot + dotRadius * 1.2f, plusTextPaint)
+ break
+ } else {
+ val paint = eventDotPaint.apply { color = event.color }
+ canvas.drawCircle(xDot, yDot, dotRadius, paint)
}
}
+ }
- bgRectF.set(bgLeft, bgTop, bgRight, bgBottom)
- canvas.drawRoundRect(bgRectF, BG_CORNER_RADIUS, BG_CORNER_RADIUS, getEventBackgroundColor(event))
-
- val specificEventTitlePaint = getEventTitlePaint(event)
- var taskIconWidth = 0
- if (event.isTask) {
- val taskIcon = resources.getColoredDrawableWithColor(R.drawable.ic_task_vector, specificEventTitlePaint.color).mutate()
- val taskIconY = yPos.toInt() + verticalOffset - eventTitleHeight + smallPadding * 2
- taskIcon.setBounds(xPos.toInt() + smallPadding * 2, taskIconY, xPos.toInt() + eventTitleHeight + smallPadding * 2, taskIconY + eventTitleHeight)
- taskIcon.draw(canvas)
- taskIconWidth += eventTitleHeight + smallPadding
+ private fun drawMonthEvents(canvas: Canvas, row: Int, rowTop: Int) {
+ val (bars, singlesPerDay) = getRowEventsStructure(row)
+ val barBands = packBarBands(bars)
+ val rowBottom = rowTop + rowHeights[row] - smallPadding
+ val barsTop = rowTop + contentTop
+
+ val bandCoverage = IntArray(COLUMN_COUNT)
+ for (i in bars.indices) {
+ val lineTop = barsTop + barBands[i] * eventLineStep
+ if (lineTop + eventLineStep > rowBottom) {
+ continue
+ }
+ drawEventBar(canvas, bars[i].event, bars[i].firstCol, bars[i].lastCol, lineTop)
+ for (col in bars[i].firstCol..bars[i].lastCol) {
+ bandCoverage[col] = max(bandCoverage[col], barBands[i] + 1)
+ }
}
- drawEventTitle(event, canvas, xPos + taskIconWidth, yPos + verticalOffset, bgRight - bgLeft - smallPadding - taskIconWidth, specificEventTitlePaint)
+ for (col in 0 until COLUMN_COUNT) {
+ val daySingles = singlesPerDay[col]
+ var dayTop = barsTop + bandCoverage[col] * eventLineStep
+ var count = 0
+ while (count < daySingles.size) {
+ if (dayTop + eventLineStep > rowBottom) {
+ break
+ }
+ drawEventLine(canvas, daySingles[count].event, daySingles[count].firstCol, dayTop)
+ dayTop += eventLineStep
+ count++
+ }
+ }
+ }
- for (i in 0 until min(event.daysCnt, 7 - event.startDayIndex % 7)) {
- dayVerticalOffsets.put(event.startDayIndex + i, verticalOffset + eventTitleHeight + smallPadding * 2)
+ // how many stacked lines a row needs: the packed bar bands on top plus the busiest single day
+ private fun getRowSlots(row: Int): Int {
+ val (bars, singlesPerDay) = getRowEventsStructure(row)
+ val bandCount = if (bars.isEmpty()) 0 else (packBarBands(bars).maxOrNull()!! + 1)
+ var maxDayCount = 0
+ for (col in 0 until COLUMN_COUNT) {
+ maxDayCount = max(maxDayCount, singlesPerDay[col].size)
}
+ return bandCount + maxDayCount
}
- private fun drawEventTitle(event: MonthViewEvent, canvas: Canvas, x: Float, y: Float, availableWidth: Float, paint: Paint) {
- val ellipsized = TextUtils.ellipsize(event.title, eventTitlePaint, availableWidth - smallPadding, TextUtils.TruncateAt.END)
- canvas.drawText(event.title, 0, ellipsized.length, x + smallPadding * 2, y, paint)
+ private fun getRowEventsStructure(row: Int): Pair, Array>> {
+ val bars = ArrayList()
+ val singlesPerDay = Array(COLUMN_COUNT) { ArrayList() }
+ for (rowEvent in getRowEvents(row)) {
+ if (rowEvent.lastCol > rowEvent.firstCol) {
+ bars.add(rowEvent)
+ } else {
+ singlesPerDay[rowEvent.firstCol].add(rowEvent)
+ }
+ }
+ bars.sortWith(compareBy({ it.firstCol }, { it.lastCol }))
+ return bars to singlesPerDay
}
- private fun getTextPaint(startDay: DayMonthly): Paint {
- var paintColor = when {
- !isPrintVersion && startDay.isToday -> primaryColor.getContrastColor()
- highlightWeekends && startDay.isWeekend -> weekendsTextColor
- else -> textColor
+ private fun packBarBands(bars: List): IntArray {
+ val barBands = IntArray(bars.size)
+ val bands = ArrayList()
+ for (i in bars.indices) {
+ val bar = bars[i]
+ var placed = -1
+ for (b in 0 until bands.size) {
+ val band = bands[b]
+ var overlaps = false
+ for (c in bar.firstCol..bar.lastCol) {
+ if (band[c]) {
+ overlaps = true
+ break
+ }
+ }
+ if (!overlaps) {
+ placed = b
+ break
+ }
+ }
+ if (placed == -1) {
+ placed = bands.size
+ bands.add(BooleanArray(COLUMN_COUNT))
+ }
+ val band = bands[placed]
+ for (c in bar.firstCol..bar.lastCol) {
+ band[c] = true
+ }
+ barBands[i] = placed
}
+ return barBands
+ }
- if (!startDay.isThisMonth) {
- paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA)
+ private fun getRowEvents(row: Int): ArrayList {
+ val eventMap = LinkedHashMap()
+ for (col in 0 until COLUMN_COUNT) {
+ val day = days.getOrNull(row * COLUMN_COUNT + col) ?: continue
+ for (event in day.dayEvents) {
+ val rowEvent = eventMap[event.id]
+ if (rowEvent == null) {
+ eventMap[event.id] = RowEvent(event).apply {
+ firstCol = col
+ lastCol = col
+ }
+ } else {
+ rowEvent.lastCol = col
+ }
+ }
}
- return getColoredPaint(paintColor)
+ val rowEvents = ArrayList(eventMap.values)
+ rowEvents.sortWith(compareBy({ !it.event.getIsAllDay() }, { it.event.startTS }, { it.event.title }))
+ return rowEvents
}
- private fun getColoredPaint(color: Int): Paint {
- val curPaint = Paint(textPaint)
- curPaint.color = color
- return curPaint
+ private fun drawEventBar(canvas: Canvas, event: Event, firstCol: Int, lastCol: Int, lineTop: Int) {
+ val left = firstCol * dayWidth + horizontalOffset + smallPadding
+ val right = (lastCol + 1) * dayWidth + horizontalOffset - smallPadding
+ val top = (lineTop + smallPadding / 2).toFloat()
+ val bottom = (lineTop + eventLineStep - smallPadding).toFloat()
+
+ val barColor = getEventLineColor(event)
+ eventStripPaint.color = barColor
+ val cornerRadius = smallPadding.toFloat()
+ canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius, eventStripPaint)
+
+ val paint = getEventBarTitlePaint(barColor)
+ val baseline = (lineTop + eventTitleHeight + smallPadding / 2).toFloat()
+ val textX = firstCol * dayWidth + horizontalOffset + smallPadding * 2
+ val availableWidth = ((lastCol - firstCol + 1) * dayWidth - smallPadding * 4).coerceAtLeast(0f)
+ if (availableWidth > 0) {
+ val title = event.title.trim()
+ val ellipsized = TextUtils.ellipsize(title, eventTitlePaint, availableWidth, TextUtils.TruncateAt.END)
+ canvas.drawText(ellipsized.toString(), 0, ellipsized.length, textX, baseline, paint)
+ }
}
- private fun getEventBackgroundColor(event: MonthViewEvent): Paint {
- var paintColor = event.color
+ private fun drawEventLine(canvas: Canvas, event: Event, col: Int, lineTop: Int) {
+ val paint = getEventLineTitlePaint(event)
+ val baseline = (lineTop + eventTitleHeight + smallPadding).toFloat()
+ val textX = col * dayWidth + horizontalOffset + smallPadding
+ val availableWidth = (dayWidth - smallPadding * 2).coerceAtLeast(0f)
+ if (availableWidth > 0) {
+ val title = event.title.trim()
+ val ellipsized = TextUtils.ellipsize(title, eventTitlePaint, availableWidth, TextUtils.TruncateAt.END)
+ canvas.drawText(ellipsized.toString(), 0, ellipsized.length, textX, baseline, paint)
+ }
+ }
+ private fun getEventLineColor(event: Event): Int {
+ var paintColor = event.color
val adjustAlpha = when {
- event.isTask -> dimCompletedTasks && event.isTaskCompleted
+ event.isTask() -> dimCompletedTasks && event.isTaskCompleted()
else -> dimPastEvents && event.isPastEvent && !isPrintVersion
}
@@ -422,29 +596,46 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA)
}
- return getColoredPaint(paintColor)
+ return paintColor
}
- private fun getEventTitlePaint(event: MonthViewEvent): Paint {
- var paintColor = event.color.getContrastColor()
- val adjustAlpha = when {
- event.isTask -> dimCompletedTasks && event.isTaskCompleted
- else -> dimPastEvents && event.isPastEvent && !isPrintVersion
+ private fun getEventLineTitlePaint(event: Event): Paint {
+ val curPaint = Paint(eventTitlePaint)
+ curPaint.color = getEventLineColor(event)
+ curPaint.isStrikeThruText = event.shouldStrikeThrough()
+ return curPaint
+ }
+
+ private fun getEventBarTitlePaint(barColor: Int): Paint {
+ val curPaint = Paint(eventTitlePaint)
+ curPaint.color = barColor.getContrastColor()
+ return curPaint
+ }
+
+ private fun getTextPaint(startDay: DayMonthly): Paint {
+ var paintColor = when {
+ !isPrintVersion && startDay.isToday -> primaryColor.getContrastColor()
+ highlightWeekends && startDay.isWeekend -> weekendsTextColor
+ else -> textColor
}
- if (adjustAlpha) {
- paintColor = paintColor.adjustAlpha(HIGHER_ALPHA)
+ if (!startDay.isThisMonth) {
+ paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA)
}
- val curPaint = Paint(eventTitlePaint)
- curPaint.color = paintColor
- curPaint.isStrikeThruText = event.shouldStrikeThrough()
+ return getColoredPaint(paintColor)
+ }
+
+ private fun getColoredPaint(color: Int): Paint {
+ val curPaint = Paint(textPaint)
+ curPaint.color = color
return curPaint
}
private fun getCirclePaint(day: DayMonthly): Paint {
val curPaint = Paint(textPaint)
- var paintColor = primaryColor
+ val configuredColor = config.todayCircleColor
+ var paintColor = if (configuredColor != 0) configuredColor else primaryColor
if (!day.isThisMonth) {
paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA)
}
@@ -467,30 +658,6 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
currDayOfWeek = context.getProperDayIndexInWeek(DateTime())
}
- // take into account cases when an event starts on the previous screen, subtract those days
- private fun getEventLastingDaysCount(event: Event): Int {
- val startDateTime = Formatter.getDateTimeFromTS(event.startTS)
- val endDateTime = Formatter.getDateTimeFromTS(event.endTS)
- val code = days.first().code
- val screenStartDateTime = Formatter.getDateTimeFromCode(code).toLocalDate()
- var eventStartDateTime = Formatter.getDateTimeFromTS(startDateTime.seconds()).toLocalDate()
- val eventEndDateTime = Formatter.getDateTimeFromTS(endDateTime.seconds()).toLocalDate()
- val diff = Days.daysBetween(screenStartDateTime, eventStartDateTime).days
- if (diff < 0) {
- eventStartDateTime = screenStartDateTime
- }
-
- val isMidnight = Formatter.getDateTimeFromTS(endDateTime.seconds()) == Formatter.getDateTimeFromTS(endDateTime.seconds()).withTimeAtStartOfDay()
- val numDays = Days.daysBetween(eventStartDateTime, eventEndDateTime).days
- val daysCnt = if (numDays == 1 && isMidnight) 0 else numDays
- return daysCnt + 1
- }
-
- private fun isDayValid(event: Event, code: String): Boolean {
- val date = Formatter.getDateTimeFromCode(code)
- return event.startTS != event.endTS && Formatter.getDateTimeFromTS(event.endTS) == Formatter.getDateTimeFromTS(date.seconds()).withTimeAtStartOfDay()
- }
-
fun togglePrintMode() {
isPrintVersion = !isPrintVersion
textColor = if (isPrintVersion) {
@@ -509,4 +676,4 @@ class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(con
selectedDayCoords = Point(x, y)
invalidate()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/fossify/calendar/views/MonthViewWrapper.kt b/app/src/main/kotlin/org/fossify/calendar/views/MonthViewWrapper.kt
index 664844c4b..3fdbcedcb 100644
--- a/app/src/main/kotlin/org/fossify/calendar/views/MonthViewWrapper.kt
+++ b/app/src/main/kotlin/org/fossify/calendar/views/MonthViewWrapper.kt
@@ -3,7 +3,11 @@ package org.fossify.calendar.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.ViewConfiguration
import android.widget.FrameLayout
+import android.widget.OverScroller
import org.fossify.calendar.R
import org.fossify.calendar.databinding.MonthViewBackgroundBinding
import org.fossify.calendar.databinding.MonthViewBinding
@@ -13,7 +17,6 @@ import org.fossify.calendar.extensions.launchNewEventIntent
import org.fossify.calendar.extensions.launchNewTaskIntent
import org.fossify.calendar.helpers.COLUMN_COUNT
import org.fossify.calendar.helpers.Formatter
-import org.fossify.calendar.helpers.ROW_COUNT
import org.fossify.calendar.helpers.TYPE_EVENT
import org.fossify.calendar.helpers.TYPE_TASK
import org.fossify.calendar.models.DayMonthly
@@ -21,6 +24,7 @@ import org.fossify.commons.compose.extensions.getActivity
import org.fossify.commons.dialogs.RadioGroupDialog
import org.fossify.commons.extensions.onGlobalLayout
import org.fossify.commons.models.RadioItem
+import kotlin.math.abs
// used in the Monthly view fragment, 1 view per screen
class MonthViewWrapper(
@@ -29,9 +33,10 @@ class MonthViewWrapper(
defStyle: Int
) : FrameLayout(context, attrs, defStyle) {
private var dayWidth = 0f
- private var dayHeight = 0f
private var weekDaysLetterHeight = 0
private var horizontalOffset = 0
+ private var parentHeight = 0
+ private var contentHeight = 0
private var wereViewsAdded = false
private var isMonthDayView = true
private var days = ArrayList()
@@ -39,6 +44,13 @@ class MonthViewWrapper(
private var binding: MonthViewBinding
private var dayClickCallback: ((day: DayMonthly) -> Unit)? = null
+ private val touchSlop: Int
+ private val scroller: OverScroller
+ private var velocityTracker: VelocityTracker? = null
+ private var downRawY = 0f
+ private var lastRawY = 0f
+ private var isDragging = false
+
constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
init {
@@ -50,6 +62,11 @@ class MonthViewWrapper(
binding = MonthViewBinding.inflate(inflater, this, true)
setupHorizontalOffset()
+ clipChildren = true
+ clipToPadding = true
+ touchSlop = ViewConfiguration.get(context).scaledTouchSlop
+ scroller = OverScroller(context)
+
onGlobalLayout {
if (!wereViewsAdded && days.isNotEmpty()) {
measureSizes()
@@ -59,51 +76,152 @@ class MonthViewWrapper(
}
}
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val width = MeasureSpec.getSize(widthMeasureSpec)
+ val height = MeasureSpec.getSize(heightMeasureSpec)
+ parentHeight = height
+ dayWidth = (width - horizontalOffset) / COLUMN_COUNT.toFloat()
+
+ binding.monthView.setMinimumContentHeight(height)
+ binding.monthView.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
+ )
+ contentHeight = binding.monthView.measuredHeight
+
+ setMeasuredDimension(width, height)
+ scrollTo(scrollX, scrollY.coerceIn(0, maxScrollY()))
+ }
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
- super.onLayout(changed, left, top, right, bottom)
- measureSizes()
- var y = 0
- var x = 0
- var curLeft = dayWidth.toInt()
- val end = right + paddingRight
+ binding.monthView.layout(0, 0, right - left, contentHeight)
+ var index = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
- if (child is MonthView) {
- //ignore the MonthView layout
+ if (child === binding.monthView) {
continue
}
+ val viewX = index % COLUMN_COUNT
+ val viewY = index / COLUMN_COUNT
+ val cellWidth = dayWidth.toInt()
+ val cellHeight = binding.monthView.getRowHeight(viewY)
+ val childLeft = (viewX * dayWidth + horizontalOffset).toInt()
+ val childTop = binding.monthView.getRowTop(viewY)
+
child.measure(
- MeasureSpec.makeMeasureSpec(dayWidth.toInt(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(dayHeight.toInt(), MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(cellWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(cellHeight, MeasureSpec.EXACTLY)
)
- val childLeft = x * dayWidth + horizontalOffset - child.translationX
- val childTop = y * dayHeight + weekDaysLetterHeight - child.translationY
- val childWidth = child.measuredWidth
- val childHeight = child.measuredHeight
- val childRight = childLeft + childWidth
- val childBottom = childTop + childHeight
-
- child.layout(
- childLeft.toInt(),
- childTop.toInt(),
- childRight.toInt(),
- childBottom.toInt()
- )
+ child.layout(childLeft, childTop, childLeft + child.measuredWidth, childTop + child.measuredHeight)
+ index++
+ }
+ }
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ if (maxScrollY() == 0) {
+ return super.onInterceptTouchEvent(event)
+ }
+
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ downRawY = event.rawY
+ lastRawY = event.rawY
+ isDragging = false
+ if (!scroller.isFinished) {
+ scroller.abortAnimation()
+ }
+ velocityTracker?.recycle()
+ velocityTracker = VelocityTracker.obtain().apply { addMovement(event) }
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ velocityTracker?.addMovement(event)
+ if (!isDragging && abs(event.rawY - downRawY) > touchSlop) {
+ isDragging = true
+ }
+ return isDragging
+ }
+
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ velocityTracker?.recycle()
+ velocityTracker = null
+ isDragging = false
+ }
+ }
+ return isDragging
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (maxScrollY() == 0) {
+ return super.onTouchEvent(event)
+ }
+
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ if (velocityTracker == null) {
+ velocityTracker = VelocityTracker.obtain()
+ }
+ velocityTracker?.addMovement(event)
+ lastRawY = event.rawY
+ isDragging = false
+ if (!scroller.isFinished) {
+ scroller.abortAnimation()
+ }
+ return true
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ velocityTracker?.addMovement(event)
+ if (!isDragging && abs(event.rawY - downRawY) > touchSlop) {
+ isDragging = true
+ }
+ if (isDragging) {
+ val dy = (lastRawY - event.rawY).toInt()
+ lastRawY = event.rawY
+ if (dy != 0) {
+ scrollBy(0, dy)
+ scrollTo(scrollX, scrollY.coerceIn(0, maxScrollY()))
+ }
+ return true
+ }
+ }
+
+ MotionEvent.ACTION_UP -> {
+ velocityTracker?.addMovement(event)
+ velocityTracker?.computeCurrentVelocity(1000)
+ if (isDragging) {
+ val velocityY = velocityTracker?.yVelocity ?: 0f
+ scroller.fling(scrollX, scrollY, 0, -velocityY.toInt(), 0, 0, 0, maxScrollY())
+ invalidate()
+ }
+ velocityTracker?.recycle()
+ velocityTracker = null
+ isDragging = false
+ return true
+ }
- if (curLeft + childWidth <= end) {
- curLeft += childWidth
- x++
- } else {
- y++
- x = 0
- curLeft = childWidth
+ MotionEvent.ACTION_CANCEL -> {
+ velocityTracker?.recycle()
+ velocityTracker = null
+ isDragging = false
+ return true
}
}
+ return super.onTouchEvent(event)
}
+ override fun computeScroll() {
+ if (scroller.computeScrollOffset()) {
+ scrollTo(scrollX, scroller.currY.coerceIn(0, maxScrollY()))
+ invalidate()
+ }
+ }
+
+ private fun maxScrollY(): Int = (contentHeight - parentHeight).coerceAtLeast(0)
+
fun updateDays(
newDays: ArrayList,
addEvents: Boolean,
@@ -113,7 +231,7 @@ class MonthViewWrapper(
measureSizes()
dayClickCallback = callback
days = newDays
- if (dayWidth != 0f && dayHeight != 0f) {
+ if (dayWidth != 0f) {
addClickableBackgrounds()
}
@@ -127,7 +245,6 @@ class MonthViewWrapper(
private fun measureSizes() {
dayWidth = (width - horizontalOffset) / COLUMN_COUNT.toFloat()
- dayHeight = (height - weekDaysLetterHeight) / ROW_COUNT.toFloat()
}
private fun addClickableBackgrounds() {
@@ -137,11 +254,9 @@ class MonthViewWrapper(
days.forEachIndexed { index, day ->
addViewBackground(index % COLUMN_COUNT, index / COLUMN_COUNT, day)
}
-
}
private fun addViewBackground(viewX: Int, viewY: Int, day: DayMonthly) {
-
MonthViewBackgroundBinding.inflate(inflater, this, false).root.apply {
if (isMonthDayView) {
background = null
@@ -189,4 +304,4 @@ class MonthViewWrapper(
fun togglePrintMode() {
binding.monthView.togglePrintMode()
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-nodpi/ic_launcher_foreground.png b/app/src/main/res/drawable-nodpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..df4dda042
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/drawable/ic_drag_handle_vector.xml b/app/src/main/res/drawable/ic_drag_handle_vector.xml
new file mode 100644
index 000000000..2d27340ea
--- /dev/null
+++ b/app/src/main/res/drawable/ic_drag_handle_vector.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index 23fadeb21..a7aec6f37 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -1,3 +1,3 @@
-
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index bbf5085e3..000000000
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index eac99df0e..95f7d7177 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -10,6 +10,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -689,6 +730,29 @@
android:layout_height="wrap_content"
android:text="@string/monthly_view" />
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_month.xml b/app/src/main/res/layout/fragment_month.xml
index d89615dbd..ebaab1d70 100644
--- a/app/src/main/res/layout/fragment_month.xml
+++ b/app/src/main/res/layout/fragment_month.xml
@@ -4,12 +4,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
-
+ android:layout_alignParentTop="true" />
diff --git a/app/src/main/res/layout/item_reorder_calendar.xml b/app/src/main/res/layout/item_reorder_calendar.xml
new file mode 100644
index 000000000..ad1d40d92
--- /dev/null
+++ b/app/src/main/res/layout/item_reorder_calendar.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/top_navigation.xml b/app/src/main/res/layout/top_navigation.xml
index 299e897ef..f9b75e133 100644
--- a/app/src/main/res/layout/top_navigation.xml
+++ b/app/src/main/res/layout/top_navigation.xml
@@ -23,9 +23,9 @@
android:layout_toStartOf="@+id/top_right_arrow"
android:layout_toEndOf="@+id/top_left_arrow"
android:gravity="center"
- android:paddingTop="@dimen/normal_margin"
- android:paddingBottom="@dimen/normal_margin"
- android:textSize="@dimen/month_text_size"
+ android:paddingTop="@dimen/smaller_margin"
+ android:paddingBottom="@dimen/smaller_margin"
+ android:textSize="@dimen/month_header_text_size"
tools:text="January" />
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml
deleted file mode 100644
index c81738497..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml
deleted file mode 100644
index e5ae9a175..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml
deleted file mode 100644
index 6c1d26030..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml
deleted file mode 100644
index cc384dc69..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml
deleted file mode 100644
index 5250a44f1..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml
deleted file mode 100644
index 81ba15b52..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml
deleted file mode 100644
index c0477c1e1..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml
deleted file mode 100644
index 20f9f44a9..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml
deleted file mode 100644
index dd752f852..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml
deleted file mode 100644
index eee16d215..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml
deleted file mode 100644
index cfe22da4d..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_orange.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_orange.xml
deleted file mode 100644
index af5af0b12..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_orange.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml
deleted file mode 100644
index 00a28a52f..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml
deleted file mode 100644
index 8c55541ac..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml
deleted file mode 100644
index f500e3c15..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml
deleted file mode 100644
index 64fd7f8b0..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml
deleted file mode 100644
index 74726f57b..000000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 33ced27e1..5dfe03709 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -24,9 +24,12 @@
24dp
11sp
+ 12sp
+ 12sp
14sp
17sp
22sp
+ 15sp
8sp
@@ -36,7 +39,7 @@
40dp
16dp
- 48dp
+ 32dp
88dp
6dp
2dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 09cedfaee..7ab5b7326 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5,6 +5,7 @@
Daily view
Weekly view
Monthly view
+ Month view font size
Monthly and daily view
Yearly view
Simple event list
@@ -227,8 +228,11 @@
Other time
Highlight weekends on some views
Color of highlighted weekends
+ Color of today\'s circle in month view
Allow changing event time zones
Manage quick filter calendars
+ Reorder quick filter calendars
+ Drag the handle to change the order
Allow creating tasks
diff --git a/gradle.properties b/gradle.properties
index d5bc32cc9..de27320d5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,6 +3,6 @@ android.useAndroidX=true
org.gradle.jvmargs=-Xmx8192m
# Versioning
-VERSION_NAME=1.10.3
-VERSION_CODE=21
+VERSION_NAME=1.10.8
+VERSION_CODE=26
APP_ID=org.fossify.calendar