diff --git a/README.md b/README.md index 2ea3e65a0..1ca531084 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,49 @@ # Simple Calendar Logo -Simple Calendar 2023 is a highly customizable, offline monthly calendar app for Android. Have an agenda planner in your pocket, designed to do exactly what a personal tiny schedule planner should do in 2023. No complicated features, unnecessary permissions, or ads! -***Supports syncing events via Google Calendar +Note: this is a fork of Simple Mobile Tools's Simple Calendar Pro that has been modified to build +on current Android SDKs. The original Calendar app relied upon a Duolingo component, rtl-viewpager, that +modified ViewPager. Android subsequently released a ViewPager2 that eliminated the need for the Duolingo +component, and the Duolingo component was deprecated and then discontinued. As a result, the basic +repository no longer builds a working app. + +This fork replaces the ViewPager and Duolingo rtl-viewpager component dependencies with calls to ViewPager2, +and so it will now build with current Android SDKs for current Android versions. These modifications +were implemented using the Android SDK AI assistant harness together with the free version of Google's Gemma 3 Flash. + +** Summary of Changes: ** +1. +Global Exclusion: The com.github.duolingo:rtl-viewpager library is already excluded from the  Simple-Commons dependency in the implementation block of your  app/build.gradle.kts. This ensures it is not included in any build type (debug, release, etc.) or flavor (core, fdroid, prepaid). +2. +Full Migration to ViewPager2: The codebase has been fully updated to use ViewPager2 and FragmentStateAdapter. I verified that all holders ( DayFragmentsHolder.kt,  WeekFragmentsHolder.kt, etc.) and their respective adapters have been migrated. +3. +R8 Build Fix: Since the Simple-Commons library still contains internal references to RtlViewPager, +R8 (the minifier) would previously fail when building release variants (like fdroidRelease) because +the class was missing from the classpath. I fixed this by adding the following rule to your  proguard-rules.pro: +-dontwarn com.duolingo.open.rtlviewpager.RtlViewPager + +This tells R8 to ignore the missing class warnings, allowing the build to succeed while ensuring the +unused library code is completely stripped out of the final APK. +This project is now clean of the rtl-viewpager dependency across all variants. + +Original README resumes below. + +Simple Calendar is a highly customizable, offline monthly calendar app for Android. Have an agenda planner in your pocket, designed to do exactly what a personal tiny schedule planner should do. No complicated features, unnecessary permissions, or ads! +*Supports syncing events via Google Calendar* Download now! Daily Digital Schedule App: Take Control of Your Time -Whether you are looking for a work calendar for business, a day planner, an appointment scheduler, or organization and scheduling of single and recurring events like birthdays, anniversary, appointment reminder, or anything else, Simple Calendar 2023 makes it easy to stay organized. The calendar widget has an incredible variety of customization options: customize event reminders, notification appearance, tiny calendar reminders widget, and overall appearance. +Whether you are looking for a work calendar for business, a day planner, an appointment scheduler, or organization and scheduling of single and recurring events like birthdays, anniversaries, appointment reminders, or anything else, Simple Calendar makes it easy to stay organized. The calendar widget has an incredible variety of customization options: customize event reminders, notification appearance, tiny calendar reminder widget, and overall appearance. Schedule Planner: Plan Your Day -Appointment scheduler, monthly planner, and family organizer in one! Check your upcoming agenda, schedule business meetings, and events & book appointments easily. Reminders will keep you on time and informed on your daily schedule app. This 2023 calendar widget is remarkably easy to use. You can even view everything as a simple list of events rather than in a monthly view, so you know exactly what’s coming up in your life and how to organize and plan your agenda. +Appointment scheduler, monthly planner, and family organizer in one! Check your upcoming agenda, schedule business meetings, and events & book appointments easily. Reminders will keep you on time and informed on your daily schedule app. This calendar widget is remarkably easy to use. You can even view everything as a simple list of events rather than in a monthly view, so you know exactly what’s coming up in your life and how to organize and plan your agenda. -Simple Calendar 2023 Features +Simple Calendar Features ✔️ The Best User Experience ➕ No ads or annoying popups, truly great user experience! -➕ No internet access is needed, giving you more privacy, security, and stability +➕ No internet access is required by default, giving you more privacy, security, and stability ✔️ Flexibility for Your Productivity ➕ Calendar Widget supports exporting & importing events via .ics files @@ -29,15 +56,15 @@ Simple Calendar 2023 Features ➕ Calendar widget - Colorful Calendars and customizable themes ➕ Open source tiny calendar, translated into 30+ languages ➕ Plan your day with others - ability to share events fast on social media, emails, etc -➕ Family Organizer - with hasslefree event duplication, organization, and time management +➕ Family Organizer - with hassle-free event duplication, organization, and time management ✔️ Organization and Time Management ➕ Day planner - the agenda planner will help you to organize your day ➕ Weekly planner - staying ahead of your busy weekly schedule has never been easier ➕ Itinerary manager - business calendar shared between teams at work ➕ Appointment scheduler - organize and maintain your agenda with ease -➕ Planning app - easy to use personal event, appointment reminder, and schedule planner -➕ Plan your day - manage your day with this android schedule planner, event & family organizer +➕ Planning app - easy to use personal events, appointment reminders, and schedule planners +➕ Plan your day - manage your day with this Android schedule planner, event & family organizer ✔️ #1 Calendar App ➕ Import holidays, contact birthdays, and anniversaries easily @@ -46,7 +73,7 @@ Simple Calendar 2023 Features ➕ Quick business calendar, or personal digital agenda ➕ Quickly switch between daily, weekly, monthly, yearly & event views -DOWNLOAD SIMPLE CALENDAR PLANNER – OFFLINE SCHEDULE AND AGENDA PLANNER WITH NO ADS! PLAN YOUR 2023 TIMETABLE! +DOWNLOAD SIMPLE CALENDAR PLANNER – OFFLINE SCHEDULE AND AGENDA PLANNER WITH NO ADS! PLAN YOUR TIMETABLE! Get it on F-Droid diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9762e9bbf..50ac43e5e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,6 +61,7 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + signingConfig = signingConfigs.getByName("debug") if (keystorePropertiesFile.exists()) { signingConfig = signingConfigs.getByName("release") } @@ -97,10 +98,13 @@ android { } dependencies { - implementation(libs.simple.mobile.tools.commons) + implementation(libs.simple.mobile.tools.commons) { + exclude(group = "com.github.duolingo", module = "rtl-viewpager") + } implementation(libs.androidx.multidex) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.swiperefreshlayout) + implementation(libs.androidx.viewpager2) implementation(libs.androidx.print) implementation(libs.bundles.room) ksp(libs.androidx.room.compiler) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ad7d60487..995e79487 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1 +1,3 @@ -keep class com.simplemobiletools.calendar.models.** { *; } + +-dontwarn com.duolingo.open.rtlviewpager.RtlViewPager diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt index a1659a25e..03d66e056 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt @@ -1,6 +1,5 @@ package com.simplemobiletools.calendar.pro.activities -import android.app.Activity import android.app.DatePickerDialog import android.app.TimePickerDialog import android.content.Intent @@ -8,6 +7,7 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.net.Uri +import android.os.Build import android.os.Bundle import android.provider.CalendarContract.Attendees import android.provider.CalendarContract.Colors @@ -21,6 +21,7 @@ import android.view.WindowManager import android.view.inputmethod.EditorInfo import android.widget.ImageView import android.widget.RelativeLayout +import androidx.activity.result.contract.ActivityResultContracts import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.MaterialTimePicker.INPUT_MODE_CLOCK import com.google.android.material.timepicker.TimeFormat @@ -44,12 +45,12 @@ import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.views.MyAutoCompleteTextView import org.joda.time.DateTime import org.joda.time.DateTimeZone +import java.io.Serializable import java.util.TimeZone import java.util.regex.Pattern class EventActivity : SimpleActivity() { private val LAT_LON_PATTERN = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)([,;])\\s*[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)\$" - private val SELECT_TIME_ZONE_INTENT = 1 private var mIsAllDayEvent = false private var mReminder1Minutes = REMINDER_OFF @@ -82,7 +83,14 @@ class EventActivity : SimpleActivity() { private lateinit var mEventStartDateTime: DateTime private lateinit var mEventEndDateTime: DateTime private lateinit var mEvent: Event - + private val selectTimeZoneLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + result.data?.getSerializableExtraCompat(TIME_ZONE)?.let { timeZone -> + mEvent.timeZone = timeZone.zoneName + updateTimeZoneText() + } + } + } private val binding by viewBinding(ActivityEventBinding::inflate) override fun onCreate(savedInstanceState: Bundle?) { @@ -139,11 +147,17 @@ class EventActivity : SimpleActivity() { if (it) { saveCurrentEvent() } else { - super.onBackPressed() + //super.onBackPressed() + // Source - https://stackoverflow.com/a/73934895 + // Posted by Jaydeep Khambhayta, modified by community. See post 'Timeline' for change history + // Retrieved 2026-09-18, License - CC BY-SA 4.0 + onBackPressedDispatcher.onBackPressed() + } } } else { - super.onBackPressed() + // super.onBackPressed() + onBackPressedDispatcher.onBackPressed() } } @@ -193,7 +207,7 @@ class EventActivity : SimpleActivity() { } savedInstanceState.apply { - mEvent = getSerializable(EVENT) as Event + getSerializableCompat(EVENT)?.let { mEvent = it } mEventStartDateTime = Formatter.getDateTimeFromTS(getLong(START_TS)) mEventEndDateTime = Formatter.getDateTimeFromTS(getLong(END_TS)) mEvent.timeZone = getString(TIME_ZONE) ?: TimeZone.getDefault().id @@ -232,15 +246,6 @@ class EventActivity : SimpleActivity() { updateActionBarTitle() } - override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { - if (requestCode == SELECT_TIME_ZONE_INTENT && resultCode == Activity.RESULT_OK && resultData?.hasExtra(TIME_ZONE) == true) { - val timeZone = resultData.getSerializableExtra(TIME_ZONE) as MyTimeZone - mEvent.timeZone = timeZone.zoneName - updateTimeZoneText() - } - super.onActivityResult(requestCode, resultCode, resultData) - } - private fun gotEvent(savedInstanceState: Bundle?, localEventType: EventType?, event: Event?) = binding.apply { if (localEventType == null || localEventType.caldavCalendarId != 0) { config.lastUsedLocalEventTypeId = REGULAR_EVENT_TYPE_ID @@ -1600,7 +1605,7 @@ class EventActivity : SimpleActivity() { hideKeyboard() Intent(this, SelectTimeZoneActivity::class.java).apply { putExtra(CURRENT_TIME_ZONE, mEvent.getTimeZoneString()) - startActivityForResult(this, SELECT_TIME_ZONE_INTENT) + selectTimeZoneLauncher.launch(this) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt index d8ba5db5e..5855bb95f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt @@ -3,16 +3,21 @@ package com.simplemobiletools.calendar.pro.activities import android.annotation.SuppressLint import android.content.Intent import android.content.pm.ShortcutInfo +import android.graphics.PorterDuff 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.Toast +import androidx.activity.OnBackPressedCallback +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.lifecycle.lifecycleScope import com.simplemobiletools.calendar.pro.BuildConfig import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.adapters.EventListAdapter @@ -42,16 +47,19 @@ import com.simplemobiletools.commons.models.Release import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.views.MyLinearLayoutManager import com.simplemobiletools.commons.views.MyRecyclerView +import kotlinx.coroutines.* import org.joda.time.DateTime import org.joda.time.DateTimeZone import java.text.SimpleDateFormat import java.util.* +import kotlin.time.Duration.Companion.milliseconds class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private var showCalDAVRefreshToast = false private var mShouldFilterBeVisible = false private var mLatestSearchQuery = "" + private var searchJob: Job? = null private var shouldGoToTodayBeVisible = false private var goToTodayButton: MenuItem? = null private var currentFragments = ArrayList() @@ -85,6 +93,26 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { refreshMenuItems() updateMaterialActivityViews(binding.mainCoordinator, binding.mainHolder, useTransparentNavigation = false, useTopSearchMenu = true) + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (binding.mainMenu.isSearchOpen) { + closeSearch() + } else { + binding.swipeRefreshLayout.isRefreshing = false + checkSwipeRefreshAvailability() + when { + binding.fabExtendedOverlay.isVisible() -> hideExtendedFab() + currentFragments.size > 1 -> removeTopFragment() + else -> { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + isEnabled = true + } + } + } + } + }) + checkWhatsNewDialog() binding.calendarFab.beVisibleIf(config.storedView != YEARLY_VIEW && config.storedView != WEEKLY_VIEW) binding.calendarFab.setOnClickListener { @@ -92,7 +120,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { if (binding.fabExtendedOverlay.isVisible()) { openNewEvent() - Handler().postDelayed({ + binding.calendarFab.postDelayed({ hideExtendedFab() }, 300) } else { @@ -112,7 +140,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { binding.fabTaskIcon.setOnClickListener { openNewTask() - Handler().postDelayed({ + binding.fabTaskIcon.postDelayed({ hideExtendedFab() }, 300) } @@ -278,18 +306,9 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { } } + @Deprecated("Deprecated in Java") override fun onBackPressed() { - if (binding.mainMenu.isSearchOpen) { - closeSearch() - } else { - binding.swipeRefreshLayout.isRefreshing = false - checkSwipeRefreshAvailability() - when { - binding.fabExtendedOverlay.isVisible() -> hideExtendedFab() - currentFragments.size > 1 -> removeTopFragment() - else -> super.onBackPressed() - } - } + super.onBackPressed() } override fun onNewIntent(intent: Intent?) { @@ -383,9 +402,9 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { @SuppressLint("NewApi") private fun getNewEventShortcut(appIconColor: Int): ShortcutInfo { val newEvent = getString(R.string.new_event) - val newEventDrawable = resources.getDrawable(R.drawable.shortcut_event, theme) - (newEventDrawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_event_background).applyColorFilter(appIconColor) - val newEventBitmap = newEventDrawable.convertToBitmap() + val newEventDrawable = ResourcesCompat.getDrawable(resources, R.drawable.shortcut_event, theme) + (newEventDrawable as? LayerDrawable)?.findDrawableByLayerId(R.id.shortcut_event_background)?.applyColorFilter(appIconColor) + val newEventBitmap = newEventDrawable?.convertToBitmap() ?: Icon.createWithResource(this, R.drawable.shortcut_event).loadDrawable(this)!!.convertToBitmap() val newEventIntent = Intent(this, SplashActivity::class.java) newEventIntent.action = SHORTCUT_NEW_EVENT @@ -400,9 +419,9 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { @SuppressLint("NewApi") private fun getNewTaskShortcut(appIconColor: Int): ShortcutInfo { val newTask = getString(R.string.new_task) - val newTaskDrawable = resources.getDrawable(R.drawable.shortcut_task, theme) - (newTaskDrawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_task_background).applyColorFilter(appIconColor) - val newTaskBitmap = newTaskDrawable.convertToBitmap() + val newTaskDrawable = ResourcesCompat.getDrawable(resources, R.drawable.shortcut_task, theme) + (newTaskDrawable as? LayerDrawable)?.findDrawableByLayerId(R.id.shortcut_task_background)?.applyColorFilter(appIconColor) + val newTaskBitmap = newTaskDrawable?.convertToBitmap() ?: Icon.createWithResource(this, R.drawable.shortcut_task).loadDrawable(this)!!.convertToBitmap() val newTaskIntent = Intent(this, SplashActivity::class.java) newTaskIntent.action = SHORTCUT_NEW_TASK return ShortcutInfo.Builder(this, "new_task") @@ -572,19 +591,18 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private fun addHolidays() { val items = getHolidayRadioItems() RadioGroupDialog(this, items) { selectedHoliday -> - SetRemindersDialog(this, OTHER_EVENT) { - val reminders = it + SetRemindersDialog(this, OTHER_EVENT) { reminders -> toast(com.simplemobiletools.commons.R.string.importing) - ensureBackgroundThread { + lifecycleScope.launch(Dispatchers.IO) { val holidays = getString(R.string.holidays) var eventTypeId = eventsHelper.getEventTypeIdWithClass(HOLIDAY_EVENT) if (eventTypeId == -1L) { eventTypeId = eventsHelper.createPredefinedEventType(holidays, R.color.default_holidays_color, HOLIDAY_EVENT, true) } - val result = IcsImporter(this).importEvents(selectedHoliday as String, eventTypeId, 0, false, reminders) + val result = IcsImporter(this@MainActivity).importEvents(selectedHoliday as String, eventTypeId, 0, false, reminders) handleParseResult(result) if (result != ImportResult.IMPORT_FAIL) { - runOnUiThread { + withContext(Dispatchers.Main) { updateViewPager() setupQuickFilter() } @@ -597,23 +615,22 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private fun tryAddBirthdays() { handlePermission(PERMISSION_READ_CONTACTS) { if (it) { - SetRemindersDialog(this, BIRTHDAY_EVENT) { - val reminders = it - val privateCursor = getMyContactsCursor(false, false) + SetRemindersDialog(this, BIRTHDAY_EVENT) { reminders -> + val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = false) - ensureBackgroundThread { - val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) + lifecycleScope.launch(Dispatchers.IO) { + val privateContacts = MyContactsContentProvider.getSimpleContacts(this@MainActivity, privateCursor) addPrivateEvents(true, privateContacts, reminders) { eventsFound, eventsAdded -> - addContactEvents(true, reminders, eventsFound, eventsAdded) { - when { - it > 0 -> { - toast(R.string.birthdays_added) - updateViewPager() - setupQuickFilter() - } - - it == -1 -> toast(R.string.no_new_birthdays) - else -> toast(R.string.no_birthdays) + addContactEvents(true, reminders, eventsFound, eventsAdded) { result -> + val toastId = when { + result > 0 -> R.string.birthdays_added + result == -1 -> R.string.no_new_birthdays + else -> R.string.no_birthdays + } + toast(toastId) + if (result > 0) { + updateViewPager() + setupQuickFilter() } } } @@ -628,23 +645,22 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private fun tryAddAnniversaries() { handlePermission(PERMISSION_READ_CONTACTS) { if (it) { - SetRemindersDialog(this, ANNIVERSARY_EVENT) { - val reminders = it - val privateCursor = getMyContactsCursor(false, false) + SetRemindersDialog(this, ANNIVERSARY_EVENT) { reminders -> + val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = false) - ensureBackgroundThread { - val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) + lifecycleScope.launch(Dispatchers.IO) { + val privateContacts = MyContactsContentProvider.getSimpleContacts(this@MainActivity, privateCursor) addPrivateEvents(false, privateContacts, reminders) { eventsFound, eventsAdded -> - addContactEvents(false, reminders, eventsFound, eventsAdded) { - when { - it > 0 -> { - toast(R.string.anniversaries_added) - updateViewPager() - setupQuickFilter() - } - - it == -1 -> toast(R.string.no_new_anniversaries) - else -> toast(R.string.no_anniversaries) + addContactEvents(false, reminders, eventsFound, eventsAdded) { result -> + val toastId = when { + result > 0 -> R.string.anniversaries_added + result == -1 -> R.string.no_new_anniversaries + else -> R.string.no_anniversaries + } + toast(toastId) + if (result > 0) { + updateViewPager() + setupQuickFilter() } } } @@ -661,10 +677,10 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { return } - val privateCursor = getMyContactsCursor(false, false) + val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = false) - ensureBackgroundThread { - val privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor) + lifecycleScope.launch(Dispatchers.IO) { + val privateContacts = MyContactsContentProvider.getSimpleContacts(this@MainActivity, privateCursor) if (config.addBirthdaysAutomatically) { addPrivateEvents(true, privateContacts, config.birthdayReminders) { eventsFound, eventsAdded -> addContactEvents(true, config.birthdayReminders, eventsFound, eventsAdded) { @@ -720,10 +736,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { val dateFormats = getDateFormats() val yearDateFormats = getDateFormatsWithYear() val existingEvents = if (birthdays) eventsDB.getBirthdays() else eventsDB.getAnniversaries() - val importIDs = HashMap() - existingEvents.forEach { - importIDs[it.importId] = it.startTS - } + val importIDs = existingEvents.associateBy({ it.importId }, { it.startTS }).toMutableMap() val eventTypeId = if (birthdays) eventsHelper.getLocalBirthdaysEventTypeId() else eventsHelper.getAnniversariesEventTypeId() val source = if (birthdays) SOURCE_CONTACT_BIRTHDAY else SOURCE_CONTACT_ANNIVERSARY @@ -736,7 +749,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { for (format in dateFormats) { try { val formatter = SimpleDateFormat(format, Locale.getDefault()) - val date = formatter.parse(startDate) + val date = formatter.parse(startDate) ?: continue val flags = if (format in yearDateFormats) { FLAG_ALL_DAY } else { @@ -751,20 +764,12 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { repeatInterval = YEAR, repeatRule = REPEAT_SAME_DAY, eventType = eventTypeId, source = source, lastUpdated = lastUpdated ) - val importIDsToDelete = ArrayList() - for ((key, value) in importIDs) { - if (key == contactId && value != timestamp) { - val deleted = eventsDB.deleteBirthdayAnniversary(source, key) - if (deleted == 1) { - importIDsToDelete.add(key) - } + importIDs.filter { it.key == contactId && it.value != timestamp }.keys.forEach { key -> + if (eventsDB.deleteBirthdayAnniversary(source, key) == 1) { + importIDs.remove(key) } } - importIDsToDelete.forEach { - importIDs.remove(it) - } - eventsFound++ if (!importIDs.containsKey(contactId)) { // avoid adding duplicate birthdays/anniversaries @@ -803,10 +808,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { val source = if (birthdays) SOURCE_CONTACT_BIRTHDAY else SOURCE_CONTACT_ANNIVERSARY val existingEvents = if (birthdays) eventsDB.getBirthdays() else eventsDB.getAnniversaries() - val importIDs = HashMap() - existingEvents.forEach { - importIDs[it.importId] = it.startTS - } + val importIDs = existingEvents.associateBy({ it.importId }, { it.startTS }).toMutableMap() contacts.forEach { contact -> val events = if (birthdays) contact.birthdays else contact.anniversaries @@ -819,12 +821,13 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { } val formatter = SimpleDateFormat(format, Locale.getDefault()) - val date = formatter.parse(birthdayAnniversary) - if (date.year < 70) { - date.year = 70 + val date = formatter.parse(birthdayAnniversary) ?: return@forEach + val calendar = Calendar.getInstance().apply { time = date } + if (calendar.get(Calendar.YEAR) < 1970) { + calendar.set(Calendar.YEAR, 1970) } - val timestamp = date.time / 1000L + val timestamp = calendar.timeInMillis / 1000L val lastUpdated = System.currentTimeMillis() val event = Event( null, timestamp, timestamp, contact.name, reminder1Minutes = reminders[0], reminder2Minutes = reminders[1], @@ -832,20 +835,12 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { repeatInterval = YEAR, repeatRule = REPEAT_SAME_DAY, eventType = eventTypeId, source = source, lastUpdated = lastUpdated ) - val importIDsToDelete = ArrayList() - for ((key, value) in importIDs) { - if (key == contact.contactId.toString() && value != timestamp) { - val deleted = eventsDB.deleteBirthdayAnniversary(source, key) - if (deleted == 1) { - importIDsToDelete.add(key) - } + importIDs.filter { it.key == contact.contactId.toString() && it.value != timestamp }.keys.forEach { key -> + if (eventsDB.deleteBirthdayAnniversary(source, key) == 1) { + importIDs.remove(key) } } - importIDsToDelete.forEach { - importIDs.remove(it) - } - eventsFound++ if (!importIDs.containsKey(contact.contactId.toString())) { eventsHelper.insertEvent(event, false, false) { @@ -1078,6 +1073,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private fun searchQueryChanged(text: String) { mLatestSearchQuery = text + searchJob?.cancel() if (text.isNotEmpty() && binding.searchHolder.isGone()) { binding.searchHolder.fadeIn() @@ -1094,32 +1090,36 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { binding.searchPlaceholder.setText(placeholderTextId) binding.searchPlaceholder2.beVisibleIf(text.length == 1) + if (text.length >= 2) { - if (binding.searchResultsList.adapter == null) { - minFetchedSearchTS = DateTime().minusYears(2).seconds() - maxFetchedSearchTS = DateTime().plusYears(2).seconds() - } + searchJob = lifecycleScope.launch { + delay(300.milliseconds) // debounce + if (binding.searchResultsList.adapter == null) { + minFetchedSearchTS = DateTime().minusYears(2).seconds() + maxFetchedSearchTS = DateTime().plusYears(2).seconds() + } - eventsHelper.getEvents(minFetchedSearchTS, maxFetchedSearchTS, searchQuery = text) { events -> - if (text == mLatestSearchQuery) { - // if we have less than MIN_EVENTS_THRESHOLD events, search again by extending the time span - showSearchResultEvents(events, INITIAL_EVENTS) + eventsHelper.getEvents(minFetchedSearchTS, maxFetchedSearchTS, searchQuery = text) { events -> + if (text == mLatestSearchQuery) { + showSearchResultEvents(events, INITIAL_EVENTS) - if (events.size < MIN_EVENTS_TRESHOLD) { - minFetchedSearchTS = 0L - maxFetchedSearchTS = MAX_SEARCH_YEAR + if (events.size < MIN_EVENTS_TRESHOLD) { + minFetchedSearchTS = 0L + maxFetchedSearchTS = MAX_SEARCH_YEAR - eventsHelper.getEvents(minFetchedSearchTS, maxFetchedSearchTS, searchQuery = text) { events -> - events.forEach { event -> - try { - if (searchResultEvents.firstOrNull { it.id == event.id && it.startTS == event.startTS } == null) { - searchResultEvents.add(0, event) + eventsHelper.getEvents(minFetchedSearchTS, maxFetchedSearchTS, searchQuery = text) { extendedEvents -> + val newEvents = ArrayList() + extendedEvents.forEach { event -> + if (searchResultEvents.none { it.id == event.id && it.startTS == event.startTS }) { + newEvents.add(event) } - } catch (ignored: ConcurrentModificationException) { } - } - showSearchResultEvents(searchResultEvents, INITIAL_EVENTS) + if (newEvents.isNotEmpty()) { + searchResultEvents.addAll(0, newEvents) + showSearchResultEvents(searchResultEvents, INITIAL_EVENTS) + } + } } } } @@ -1132,18 +1132,13 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private fun showSearchResultEvents(events: ArrayList, updateStatus: Int) { val currentSearchQuery = binding.mainMenu.getCurrentQuery() - val filtered = try { - events.filter { - it.title.contains(currentSearchQuery, true) || it.location.contains(currentSearchQuery, true) || it.description.contains( - currentSearchQuery, - true - ) - } - } catch (e: ConcurrentModificationException) { - return + val filtered = events.filter { + it.title.contains(currentSearchQuery, true) || + it.location.contains(currentSearchQuery, true) || + it.description.contains(currentSearchQuery, true) } - searchResultEvents = filtered.toMutableList() as ArrayList + searchResultEvents = ArrayList(filtered) runOnUiThread { binding.searchResultsList.beVisibleIf(filtered.isNotEmpty()) binding.searchPlaceholder.beVisibleIf(filtered.isEmpty()) @@ -1174,17 +1169,23 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { } } else { (currAdapter as EventListAdapter).updateListItems(listItems) - if (updateStatus == UPDATE_TOP) { - val item = listItems.indexOfFirst { it == bottomItemAtRefresh } - if (item != -1) { - binding.searchResultsList.scrollToPosition(item) + when (updateStatus) { + UPDATE_TOP -> { + val item = listItems.indexOfFirst { it == bottomItemAtRefresh } + if (item != -1) { + binding.searchResultsList.scrollToPosition(item) + } } - } else if (updateStatus == UPDATE_BOTTOM) { - binding.searchResultsList.smoothScrollBy(0, resources.getDimension(R.dimen.endless_scroll_move_height).toInt()) - } else { - val firstNonPastSectionIndex = listItems.indexOfFirst { it is ListSectionDay && !it.isPastSection } - if (firstNonPastSectionIndex != -1) { - binding.searchResultsList.scrollToPosition(firstNonPastSectionIndex) + + UPDATE_BOTTOM -> { + binding.searchResultsList.smoothScrollBy(0, resources.getDimension(R.dimen.endless_scroll_move_height).toInt()) + } + + else -> { + val firstNonPastSectionIndex = listItems.indexOfFirst { it is ListSectionDay && !it.isPastSection } + if (firstNonPastSectionIndex != -1) { + binding.searchResultsList.scrollToPosition(firstNonPastSectionIndex) + } } } } @@ -1201,17 +1202,20 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { val oldMinFetchedTS = minFetchedSearchTS - 1 minFetchedSearchTS -= FETCH_INTERVAL - eventsHelper.getEvents(minFetchedSearchTS, oldMinFetchedTS, searchQuery = mLatestSearchQuery) { events -> - events.forEach { event -> - try { - if (searchResultEvents.firstOrNull { it.id == event.id && it.startTS == event.startTS } == null) { - searchResultEvents.add(0, event) + lifecycleScope.launch { + eventsHelper.getEvents(minFetchedSearchTS, oldMinFetchedTS, searchQuery = mLatestSearchQuery) { events -> + val newEvents = ArrayList() + events.forEach { event -> + if (searchResultEvents.none { it.id == event.id && it.startTS == event.startTS }) { + newEvents.add(event) } - } catch (ignored: ConcurrentModificationException) { } - } - showSearchResultEvents(searchResultEvents, UPDATE_TOP) + if (newEvents.isNotEmpty()) { + searchResultEvents.addAll(0, newEvents) + showSearchResultEvents(searchResultEvents, UPDATE_TOP) + } + } } } @@ -1222,17 +1226,20 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { val oldMaxFetchedTS = maxFetchedSearchTS + 1 maxFetchedSearchTS += FETCH_INTERVAL - eventsHelper.getEvents(oldMaxFetchedTS, maxFetchedSearchTS, searchQuery = mLatestSearchQuery) { events -> - events.forEach { event -> - try { - if (searchResultEvents.firstOrNull { it.id == event.id && it.startTS == event.startTS } == null) { - searchResultEvents.add(0, event) + lifecycleScope.launch { + eventsHelper.getEvents(oldMaxFetchedTS, maxFetchedSearchTS, searchQuery = mLatestSearchQuery) { events -> + val newEvents = ArrayList() + events.forEach { event -> + if (searchResultEvents.none { it.id == event.id && it.startTS == event.startTS }) { + newEvents.add(event) } - } catch (ignored: ConcurrentModificationException) { } - } - showSearchResultEvents(searchResultEvents, UPDATE_BOTTOM) + if (newEvents.isNotEmpty()) { + searchResultEvents.addAll(newEvents) + showSearchResultEvents(searchResultEvents, UPDATE_BOTTOM) + } + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SimpleActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SimpleActivity.kt index dc4fac394..c2b79a839 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SimpleActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SimpleActivity.kt @@ -3,6 +3,7 @@ package com.simplemobiletools.calendar.pro.activities import android.content.Context import android.database.ContentObserver import android.os.Handler +import android.os.Looper import android.provider.CalendarContract import androidx.core.app.NotificationManagerCompat import com.simplemobiletools.calendar.pro.R @@ -16,7 +17,7 @@ import com.simplemobiletools.commons.helpers.ensureBackgroundThread open class SimpleActivity : BaseSimpleActivity() { val CALDAV_REFRESH_DELAY = 3000L - val calDAVRefreshHandler = Handler() + val calDAVRefreshHandler = Handler(Looper.getMainLooper()) var calDAVRefreshCallback: (() -> Unit)? = null override fun getAppIconIDs() = arrayListOf( @@ -54,7 +55,7 @@ open class SimpleActivity : BaseSimpleActivity() { } // caldav refresh content observer triggers multiple times in a row at updating, so call the callback only a few seconds after the (hopefully) last one - private val calDAVSyncObserver = object : ContentObserver(Handler()) { + private val calDAVSyncObserver = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean) { super.onChange(selfChange) if (!selfChange) { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyDayPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyDayPagerAdapter.kt index 812683121..23e54c633 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyDayPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyDayPagerAdapter.kt @@ -1,21 +1,18 @@ package com.simplemobiletools.calendar.pro.adapters import android.os.Bundle -import android.util.SparseArray import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter import com.simplemobiletools.calendar.pro.fragments.DayFragment import com.simplemobiletools.calendar.pro.helpers.DAY_CODE import com.simplemobiletools.calendar.pro.interfaces.NavigationListener -class MyDayPagerAdapter(fm: FragmentManager, private val mCodes: List, private val mListener: NavigationListener) : - FragmentStatePagerAdapter(fm) { - private val mFragments = SparseArray() +class MyDayPagerAdapter(fragment: Fragment, private val mCodes: List, private val mListener: NavigationListener) : + FragmentStateAdapter(fragment) { - override fun getCount() = mCodes.size + override fun getItemCount() = mCodes.size - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { val bundle = Bundle() val code = mCodes[position] bundle.putString(DAY_CODE, code) @@ -23,18 +20,18 @@ class MyDayPagerAdapter(fm: FragmentManager, private val mCodes: List, p val fragment = DayFragment() fragment.arguments = bundle fragment.mListener = mListener - - mFragments.put(position, fragment) return fragment } - fun updateCalendars(pos: Int) { + fun updateCalendars(pos: Int, fragment: Fragment) { for (i in -1..1) { - mFragments[pos + i]?.updateCalendar() + val f = fragment.childFragmentManager.findFragmentByTag("f${pos + i}") as? DayFragment + f?.updateCalendar() } } - fun printCurrentView(pos: Int) { - mFragments[pos].printCurrentView() + fun printCurrentView(pos: Int, fragment: Fragment) { + val f = fragment.childFragmentManager.findFragmentByTag("f$pos") as? DayFragment + f?.printCurrentView() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyMonthDayPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyMonthDayPagerAdapter.kt index 020701c81..09c0e7268 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyMonthDayPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyMonthDayPagerAdapter.kt @@ -1,20 +1,18 @@ package com.simplemobiletools.calendar.pro.adapters import android.os.Bundle -import android.util.SparseArray import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter import com.simplemobiletools.calendar.pro.fragments.MonthDayFragment import com.simplemobiletools.calendar.pro.helpers.DAY_CODE import com.simplemobiletools.calendar.pro.interfaces.NavigationListener -class MyMonthDayPagerAdapter(fm: FragmentManager, private val mCodes: List, private val mListener: NavigationListener) : FragmentStatePagerAdapter(fm) { - private val mFragments = SparseArray() +class MyMonthDayPagerAdapter(fragment: Fragment, private val mCodes: List, private val mListener: NavigationListener) : + FragmentStateAdapter(fragment) { - override fun getCount() = mCodes.size + override fun getItemCount() = mCodes.size - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { val bundle = Bundle() val code = mCodes[position] bundle.putString(DAY_CODE, code) @@ -22,20 +20,23 @@ class MyMonthDayPagerAdapter(fm: FragmentManager, private val mCodes: List, private val mListener: NavigationListener) : FragmentStatePagerAdapter(fm) { - private val mFragments = SparseArray() +class MyMonthPagerAdapter(fragment: Fragment, private val mCodes: List, private val mListener: NavigationListener) : + FragmentStateAdapter(fragment) { - override fun getCount() = mCodes.size + override fun getItemCount() = mCodes.size - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { val bundle = Bundle() val code = mCodes[position] bundle.putString(DAY_CODE, code) @@ -22,18 +20,18 @@ class MyMonthPagerAdapter(fm: FragmentManager, private val mCodes: List, val fragment = MonthFragment() fragment.arguments = bundle fragment.listener = mListener - - mFragments.put(position, fragment) return fragment } - fun updateCalendars(pos: Int) { + fun updateCalendars(pos: Int, fragment: Fragment) { for (i in -1..1) { - mFragments[pos + i]?.updateCalendar() + val f = fragment.childFragmentManager.findFragmentByTag("f${pos + i}") as? MonthFragment + f?.updateCalendar() } } - fun printCurrentView(pos: Int) { - mFragments[pos].printCurrentView() + fun printCurrentView(pos: Int, fragment: Fragment) { + val f = fragment.childFragmentManager.findFragmentByTag("f$pos") as? MonthFragment + f?.printCurrentView() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyWeekPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyWeekPagerAdapter.kt index 66de13650..538241a61 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyWeekPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyWeekPagerAdapter.kt @@ -1,21 +1,18 @@ package com.simplemobiletools.calendar.pro.adapters import android.os.Bundle -import android.util.SparseArray import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter import com.simplemobiletools.calendar.pro.fragments.WeekFragment import com.simplemobiletools.calendar.pro.helpers.WEEK_START_TIMESTAMP import com.simplemobiletools.calendar.pro.interfaces.WeekFragmentListener -class MyWeekPagerAdapter(fm: FragmentManager, private val mWeekTimestamps: List, private val mListener: WeekFragmentListener) : - FragmentStatePagerAdapter(fm) { - private val mFragments = SparseArray() +class MyWeekPagerAdapter(fragment: Fragment, private val mWeekTimestamps: List, private val mListener: WeekFragmentListener) : + FragmentStateAdapter(fragment) { - override fun getCount() = mWeekTimestamps.size + override fun getItemCount() = mWeekTimestamps.size - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { val bundle = Bundle() val weekTimestamp = mWeekTimestamps[position] bundle.putLong(WEEK_START_TIMESTAMP, weekTimestamp) @@ -23,28 +20,34 @@ class MyWeekPagerAdapter(fm: FragmentManager, private val mWeekTimestamps: List< val fragment = WeekFragment() fragment.arguments = bundle fragment.listener = mListener - - mFragments.put(position, fragment) return fragment } - fun updateScrollY(pos: Int, y: Int) { - mFragments[pos - 1]?.updateScrollY(y) - mFragments[pos + 1]?.updateScrollY(y) + fun updateScrollY(pos: Int, y: Int, fragment: Fragment) { + for (i in -1..1) { + if (i == 0) continue + val f = fragment.childFragmentManager.findFragmentByTag("f${pos + i}") as? WeekFragment + f?.updateScrollY(y) + } } - fun updateCalendars(pos: Int) { + fun updateCalendars(pos: Int, fragment: Fragment) { for (i in -1..1) { - mFragments[pos + i]?.updateCalendar() + val f = fragment.childFragmentManager.findFragmentByTag("f${pos + i}") as? WeekFragment + f?.updateCalendar() } } - fun updateNotVisibleScaleLevel(pos: Int) { - mFragments[pos - 1]?.updateNotVisibleViewScaleLevel() - mFragments[pos + 1]?.updateNotVisibleViewScaleLevel() + fun updateNotVisibleScaleLevel(pos: Int, fragment: Fragment) { + for (i in -1..1) { + if (i == 0) continue + val f = fragment.childFragmentManager.findFragmentByTag("f${pos + i}") as? WeekFragment + f?.updateNotVisibleViewScaleLevel() + } } - fun togglePrintMode(pos: Int) { - mFragments[pos].togglePrintMode() + fun togglePrintMode(pos: Int, fragment: Fragment) { + val f = fragment.childFragmentManager.findFragmentByTag("f$pos") as? WeekFragment + f?.togglePrintMode() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyYearPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyYearPagerAdapter.kt index 750d8f8c3..57863604d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyYearPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyYearPagerAdapter.kt @@ -1,20 +1,18 @@ package com.simplemobiletools.calendar.pro.adapters import android.os.Bundle -import android.util.SparseArray import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter import com.simplemobiletools.calendar.pro.fragments.YearFragment import com.simplemobiletools.calendar.pro.helpers.YEAR_LABEL import com.simplemobiletools.calendar.pro.interfaces.NavigationListener -class MyYearPagerAdapter(fm: FragmentManager, val mYears: List, private val mListener: NavigationListener) : FragmentStatePagerAdapter(fm) { - private val mFragments = SparseArray() +class MyYearPagerAdapter(fragment: Fragment, val mYears: List, private val mListener: NavigationListener) : + FragmentStateAdapter(fragment) { - override fun getCount() = mYears.size + override fun getItemCount() = mYears.size - override fun getItem(position: Int): Fragment { + override fun createFragment(position: Int): Fragment { val bundle = Bundle() val year = mYears[position] bundle.putInt(YEAR_LABEL, year) @@ -22,18 +20,18 @@ class MyYearPagerAdapter(fm: FragmentManager, val mYears: List, private val val fragment = YearFragment() fragment.arguments = bundle fragment.listener = mListener - - mFragments.put(position, fragment) return fragment } - fun updateCalendars(pos: Int) { + fun updateCalendars(pos: Int, fragment: Fragment) { for (i in -1..1) { - mFragments[pos + i]?.updateCalendar() + val f = fragment.childFragmentManager.findFragmentByTag("f${pos + i}") as? YearFragment + f?.updateCalendar() } } - fun printCurrentView(pos: Int) { - mFragments[pos].printCurrentView() + fun printCurrentView(pos: Int, fragment: Fragment) { + val f = fragment.childFragmentManager.findFragmentByTag("f$pos") as? YearFragment + f?.printCurrentView() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Bundle.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Bundle.kt new file mode 100644 index 000000000..03f17a964 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Bundle.kt @@ -0,0 +1,24 @@ +package com.simplemobiletools.calendar.pro.extensions + +import android.os.Build +import android.os.Bundle +import android.os.Parcelable +import java.io.Serializable + +inline fun Bundle.getSerializableCompat(key: String): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getSerializable(key, T::class.java) + } else { + @Suppress("DEPRECATION") + getSerializable(key) as? T + } +} + +inline fun Bundle.getParcelableArrayListCompat(key: String): ArrayList? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getParcelableArrayList(key, T::class.java) + } else { + @Suppress("DEPRECATION") + getParcelableArrayList(key) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Intent.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Intent.kt new file mode 100644 index 000000000..ff238b132 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Intent.kt @@ -0,0 +1,14 @@ +package com.simplemobiletools.calendar.pro.extensions + +import android.content.Intent +import android.os.Build +import java.io.Serializable + +inline fun Intent.getSerializableExtraCompat(key: String): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getSerializableExtra(key, T::class.java) + } else { + @Suppress("DEPRECATION") + getSerializableExtra(key) as? T + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt index ffabf66b3..095612439 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt @@ -6,7 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.DatePicker -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.widget.ViewPager2 import com.simplemobiletools.calendar.pro.activities.MainActivity import com.simplemobiletools.calendar.pro.adapters.MyDayPagerAdapter import com.simplemobiletools.calendar.pro.databinding.FragmentDaysHolderBinding @@ -17,13 +17,12 @@ import com.simplemobiletools.calendar.pro.interfaces.NavigationListener import com.simplemobiletools.commons.extensions.getAlertDialogBuilder import com.simplemobiletools.commons.extensions.getProperBackgroundColor import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.commons.views.MyViewPager import org.joda.time.DateTime class DayFragmentsHolder : MyFragmentHolder(), NavigationListener { private val PREFILLED_DAYS = 251 - private lateinit var viewPager: MyViewPager + private lateinit var viewPager: ViewPager2 private var defaultDailyPage = 0 private var todayDayCode = "" private var currentDayCode = "" @@ -48,18 +47,13 @@ class DayFragmentsHolder : MyFragmentHolder(), NavigationListener { private fun setupFragment() { val codes = getDays(currentDayCode) - val dailyAdapter = MyDayPagerAdapter(requireActivity().supportFragmentManager, codes, this) + val dailyAdapter = MyDayPagerAdapter(this, codes, this) defaultDailyPage = codes.size / 2 viewPager.apply { adapter = dailyAdapter - addOnPageChangeListener(object : ViewPager.OnPageChangeListener { - override fun onPageScrollStateChanged(state: Int) { - } - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - } - + offscreenPageLimit = 1 + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { currentDayCode = codes[position] val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() @@ -69,7 +63,7 @@ class DayFragmentsHolder : MyFragmentHolder(), NavigationListener { } } }) - currentItem = defaultDailyPage + setCurrentItem(defaultDailyPage, false) } } @@ -126,7 +120,7 @@ class DayFragmentsHolder : MyFragmentHolder(), NavigationListener { } override fun refreshEvents() { - (viewPager.adapter as? MyDayPagerAdapter)?.updateCalendars(viewPager.currentItem) + (viewPager.adapter as? MyDayPagerAdapter)?.updateCalendars(viewPager.currentItem, this) } override fun shouldGoToTodayBeVisible() = currentDayCode != todayDayCode @@ -134,7 +128,7 @@ class DayFragmentsHolder : MyFragmentHolder(), NavigationListener { override fun getNewEventDayCode() = currentDayCode override fun printView() { - (viewPager.adapter as? MyDayPagerAdapter)?.printCurrentView(viewPager.currentItem) + (viewPager.adapter as? MyDayPagerAdapter)?.printCurrentView(viewPager.currentItem, this) } override fun getCurrentDate(): DateTime? { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthDayFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthDayFragmentsHolder.kt index 82e2093a4..10335d373 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthDayFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthDayFragmentsHolder.kt @@ -7,7 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.DatePicker -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.widget.ViewPager2 import com.simplemobiletools.calendar.pro.activities.MainActivity import com.simplemobiletools.calendar.pro.adapters.MyMonthDayPagerAdapter import com.simplemobiletools.calendar.pro.databinding.FragmentMonthsDaysHolderBinding @@ -20,13 +20,12 @@ import com.simplemobiletools.commons.extensions.beGone import com.simplemobiletools.commons.extensions.getAlertDialogBuilder import com.simplemobiletools.commons.extensions.getProperBackgroundColor import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.commons.views.MyViewPager import org.joda.time.DateTime class MonthDayFragmentsHolder : MyFragmentHolder(), NavigationListener { private val PREFILLED_MONTHS = 251 - private lateinit var viewPager: MyViewPager + private lateinit var viewPager: ViewPager2 private var defaultMonthlyPage = 0 private var todayDayCode = "" private var currentDayCode = "" @@ -51,16 +50,13 @@ class MonthDayFragmentsHolder : MyFragmentHolder(), NavigationListener { private fun setupFragment() { val codes = getMonths(currentDayCode) - val monthlyDailyAdapter = MyMonthDayPagerAdapter(requireActivity().supportFragmentManager, codes, this) + val monthlyDailyAdapter = MyMonthDayPagerAdapter(this, codes, this) defaultMonthlyPage = codes.size / 2 viewPager.apply { adapter = monthlyDailyAdapter - addOnPageChangeListener(object : ViewPager.OnPageChangeListener { - override fun onPageScrollStateChanged(state: Int) {} - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} - + offscreenPageLimit = 1 + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { currentDayCode = codes[position] val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() @@ -70,7 +66,7 @@ class MonthDayFragmentsHolder : MyFragmentHolder(), NavigationListener { } } }) - currentItem = defaultMonthlyPage + setCurrentItem(defaultMonthlyPage, false) } } @@ -129,12 +125,12 @@ class MonthDayFragmentsHolder : MyFragmentHolder(), NavigationListener { } override fun refreshEvents() { - (viewPager.adapter as? MyMonthDayPagerAdapter)?.updateCalendars(viewPager.currentItem) + (viewPager.adapter as? MyMonthDayPagerAdapter)?.updateCalendars(viewPager.currentItem, this) } override fun shouldGoToTodayBeVisible() = currentDayCode.getMonthCode() != todayDayCode.getMonthCode() - override fun getNewEventDayCode() = (viewPager.adapter as? MyMonthDayPagerAdapter)?.getNewEventDayCode(viewPager.currentItem) + override fun getNewEventDayCode() = (viewPager.adapter as? MyMonthDayPagerAdapter)?.getNewEventDayCode(viewPager.currentItem, this) ?: if (shouldGoToTodayBeVisible()) { currentDayCode } else { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt index e177811e4..d3b42c486 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt @@ -7,7 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.DatePicker -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.widget.ViewPager2 import com.simplemobiletools.calendar.pro.activities.MainActivity import com.simplemobiletools.calendar.pro.adapters.MyMonthPagerAdapter import com.simplemobiletools.calendar.pro.databinding.FragmentMonthsHolderBinding @@ -20,13 +20,12 @@ import com.simplemobiletools.commons.extensions.beGone import com.simplemobiletools.commons.extensions.getAlertDialogBuilder import com.simplemobiletools.commons.extensions.getProperBackgroundColor import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.commons.views.MyViewPager import org.joda.time.DateTime class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener { private val PREFILLED_MONTHS = 251 - private lateinit var viewPager: MyViewPager + private lateinit var viewPager: ViewPager2 private var defaultMonthlyPage = 0 private var todayDayCode = "" private var currentDayCode = "" @@ -51,18 +50,13 @@ class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener { private fun setupFragment() { val codes = getMonths(currentDayCode) - val monthlyAdapter = MyMonthPagerAdapter(requireActivity().supportFragmentManager, codes, this) + val monthlyAdapter = MyMonthPagerAdapter(this, codes, this) defaultMonthlyPage = codes.size / 2 viewPager.apply { adapter = monthlyAdapter - addOnPageChangeListener(object : ViewPager.OnPageChangeListener { - override fun onPageScrollStateChanged(state: Int) { - } - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - } - + offscreenPageLimit = 1 + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { currentDayCode = codes[position] val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() @@ -72,7 +66,7 @@ class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener { } } }) - currentItem = defaultMonthlyPage + setCurrentItem(defaultMonthlyPage, false) } } @@ -131,7 +125,7 @@ class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener { } override fun refreshEvents() { - (viewPager.adapter as? MyMonthPagerAdapter)?.updateCalendars(viewPager.currentItem) + (viewPager.adapter as? MyMonthPagerAdapter)?.updateCalendars(viewPager.currentItem, this) } override fun shouldGoToTodayBeVisible() = currentDayCode.getMonthCode() != todayDayCode.getMonthCode() @@ -139,7 +133,7 @@ class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener { override fun getNewEventDayCode() = if (shouldGoToTodayBeVisible()) currentDayCode else todayDayCode override fun printView() { - (viewPager.adapter as? MyMonthPagerAdapter)?.printCurrentView(viewPager.currentItem) + (viewPager.adapter as? MyMonthPagerAdapter)?.printCurrentView(viewPager.currentItem, this) } override fun getCurrentDate(): DateTime? { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt index d4301190c..b34e12337 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt @@ -10,7 +10,7 @@ import android.view.ViewGroup import android.widget.DatePicker import android.widget.RelativeLayout import android.widget.TextView -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.widget.ViewPager2 import com.simplemobiletools.calendar.pro.activities.MainActivity import com.simplemobiletools.calendar.pro.adapters.MyWeekPagerAdapter import com.simplemobiletools.calendar.pro.databinding.FragmentWeekHolderBinding @@ -23,7 +23,6 @@ import com.simplemobiletools.calendar.pro.interfaces.WeekFragmentListener import com.simplemobiletools.calendar.pro.views.MyScrollView import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.WEEK_SECONDS -import com.simplemobiletools.commons.views.MyViewPager import org.joda.time.DateTime class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { @@ -31,7 +30,7 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { private val MAX_SEEKBAR_VALUE = 14 private lateinit var binding: FragmentWeekHolderBinding - private lateinit var viewPager: MyViewPager + private lateinit var viewPager: ViewPager2 private var defaultWeeklyPage = 0 private var thisWeekTS = 0L private var currentWeekTS = 0L @@ -96,17 +95,14 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { private fun setupWeeklyViewPager() { val weekTSs = getWeekTimestamps(currentWeekTS) - val weeklyAdapter = MyWeekPagerAdapter(requireActivity().supportFragmentManager, weekTSs, this) + val weeklyAdapter = MyWeekPagerAdapter(this, weekTSs, this) defaultWeeklyPage = weekTSs.size / 2 viewPager.apply { adapter = weeklyAdapter - addOnPageChangeListener(object : ViewPager.OnPageChangeListener { - override fun onPageScrollStateChanged(state: Int) {} - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} - + offscreenPageLimit = 1 + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { currentWeekTS = weekTSs[position] val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() @@ -118,13 +114,13 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { setupWeeklyActionbarTitle(weekTSs[position]) } }) - currentItem = defaultWeeklyPage + setCurrentItem(defaultWeeklyPage, false) } binding.weekViewHoursScrollview.setOnScrollviewListener(object : MyScrollView.ScrollViewListener { override fun onScrollChanged(scrollView: MyScrollView, x: Int, y: Int, oldx: Int, oldy: Int) { weekScrollY = y - weeklyAdapter.updateScrollY(viewPager.currentItem, y) + weeklyAdapter.updateScrollY(viewPager.currentItem, y, this@WeekFragmentsHolder) } }) } @@ -223,7 +219,7 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { } override fun refreshEvents() { - (viewPager.adapter as? MyWeekPagerAdapter)?.updateCalendars(viewPager.currentItem) + (viewPager.adapter as? MyWeekPagerAdapter)?.updateCalendars(viewPager.currentItem, this) } override fun shouldGoToTodayBeVisible() = currentWeekTS != thisWeekTS @@ -262,7 +258,7 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { } binding.weekViewHoursHolder.setPadding(0, 0, 0, rowHeight) - (viewPager.adapter as? MyWeekPagerAdapter)?.updateNotVisibleScaleLevel(viewPager.currentItem) + (viewPager.adapter as? MyWeekPagerAdapter)?.updateNotVisibleScaleLevel(viewPager.currentItem, this) } override fun getFullFragmentHeight() = @@ -278,7 +274,7 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { weekViewWeekNumber.setTextColor(lightTextColor) weekViewMonthLabel.setTextColor(lightTextColor) root.background = ColorDrawable(Color.WHITE) - (viewPager.adapter as? MyWeekPagerAdapter)?.togglePrintMode(viewPager.currentItem) + (viewPager.adapter as? MyWeekPagerAdapter)?.togglePrintMode(viewPager.currentItem, this@WeekFragmentsHolder) Handler().postDelayed({ requireContext().printBitmap(binding.weekViewHolder.getViewBitmap()) @@ -291,7 +287,7 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { weekViewMonthLabel.setTextColor(requireContext().getProperTextColor()) addHours() root.background = ColorDrawable(requireContext().getProperBackgroundColor()) - (viewPager.adapter as? MyWeekPagerAdapter)?.togglePrintMode(viewPager.currentItem) + (viewPager.adapter as? MyWeekPagerAdapter)?.togglePrintMode(viewPager.currentItem, this@WeekFragmentsHolder) }, 1000) }, 1000) } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt index ebf0471ef..0d286504b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt @@ -7,7 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.DatePicker -import androidx.viewpager.widget.ViewPager +import androidx.viewpager2.widget.ViewPager2 import com.simplemobiletools.calendar.pro.activities.MainActivity import com.simplemobiletools.calendar.pro.adapters.MyYearPagerAdapter import com.simplemobiletools.calendar.pro.databinding.FragmentYearsHolderBinding @@ -19,13 +19,12 @@ import com.simplemobiletools.commons.extensions.beGone import com.simplemobiletools.commons.extensions.getAlertDialogBuilder import com.simplemobiletools.commons.extensions.getProperBackgroundColor import com.simplemobiletools.commons.extensions.setupDialogStuff -import com.simplemobiletools.commons.views.MyViewPager import org.joda.time.DateTime class YearFragmentsHolder : MyFragmentHolder(), NavigationListener { private val PREFILLED_YEARS = 61 - private lateinit var viewPager: MyViewPager + private lateinit var viewPager: ViewPager2 private var defaultYearlyPage = 0 private var todayYear = 0 private var currentYear = 0 @@ -51,16 +50,13 @@ class YearFragmentsHolder : MyFragmentHolder(), NavigationListener { private fun setupFragment() { val years = getYears(currentYear) - val yearlyAdapter = MyYearPagerAdapter(requireActivity().supportFragmentManager, years, this) + val yearlyAdapter = MyYearPagerAdapter(this, years, this) defaultYearlyPage = years.size / 2 viewPager.apply { adapter = yearlyAdapter - addOnPageChangeListener(object : ViewPager.OnPageChangeListener { - override fun onPageScrollStateChanged(state: Int) {} - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} - + offscreenPageLimit = 1 + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { currentYear = years[position] val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() @@ -70,7 +66,7 @@ class YearFragmentsHolder : MyFragmentHolder(), NavigationListener { } } }) - currentItem = defaultYearlyPage + setCurrentItem(defaultYearlyPage, false) } } @@ -124,7 +120,7 @@ class YearFragmentsHolder : MyFragmentHolder(), NavigationListener { } override fun refreshEvents() { - (viewPager.adapter as? MyYearPagerAdapter)?.updateCalendars(viewPager.currentItem) + (viewPager.adapter as? MyYearPagerAdapter)?.updateCalendars(viewPager.currentItem, this) } override fun shouldGoToTodayBeVisible() = currentYear != todayYear @@ -132,7 +128,7 @@ class YearFragmentsHolder : MyFragmentHolder(), NavigationListener { override fun getNewEventDayCode() = Formatter.getTodayCode() override fun printView() { - (viewPager.adapter as? MyYearPagerAdapter)?.printCurrentView(viewPager.currentItem) + (viewPager.adapter as? MyYearPagerAdapter)?.printCurrentView(viewPager.currentItem, this) } override fun getCurrentDate() = null diff --git a/app/src/main/res/layout/activity_day.xml b/app/src/main/res/layout/activity_day.xml index 125e7b5cc..7848b50be 100644 --- a/app/src/main/res/layout/activity_day.xml +++ b/app/src/main/res/layout/activity_day.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - -