diff --git a/.editorconfig b/.editorconfig
index 863373360..1606d61fc 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,3 +15,9 @@ root = true
end_of_line = lf
insert_final_newline = true
charset = utf-8
+indent_style = space
+indent_size = 4
+continuation_indent_size = 8
+
+[*.xml]
+continuation_indent_size = 4
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..bef563799
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+github: [tibbi]
+patreon: tiborkaputa
+custom: ["https://www.paypal.me/SimpleMobileTools", "https://www.simplemobiletools.com/donate"]
diff --git a/.gitignore b/.gitignore
index 70ca726e9..4cd1c5eed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,10 @@
-# Temp files
-*~
-*.bak
-*.backup
-\#*
-.\#*
-*\#
-*.swp
-*.swap
-*.sav
-*.save
-*.autosav
-*.autosave
-
*.iml
+*.aab
.gradle
/local.properties
-/gradle.properties
/.idea/
.DS_Store
/build
/captures
-debug.keystore
-release.keystore
-signing.properties
+keystore.jks
+keystore.properties
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f6f12609..4c4437412 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,502 @@
Changelog
==========
+Version 6.9.4 *(2020-05-25)*
+----------------------------
+
+ * Allow landscape orientation on any device
+ * Use the nicer new app icon on lower Android versions
+ * Some UI, stability and translation improvements
+
+Version 6.9.3 *(2020-05-05)*
+----------------------------
+
+ * Added a 1x1 widget showing the current date
+ * Made all widget corners round to make them nicer
+ * Added some translation and other smaller improvements here and there
+
+Version 6.9.2 *(2020-04-17)*
+----------------------------
+
+ * Fixed some .ics file importing related glitches
+ * Corrected and added some UK holidays
+ * Added many UI and translation improvements
+
+Version 6.9.1 *(2020-03-25)*
+----------------------------
+
+ * Allow zooming the weekly view with vertical gestures
+ * Allow scrolling through the whole weeky view, use Start time only as the default time
+ * Updating the app icon
+ * Other stability, translation and UX improvements
+
+Version 6.9.0 *(2020-03-18)*
+----------------------------
+
+ * Remember the last used folder at ics exporting
+ * Do not request the Storage permission on Android 10+, use Scoped Storage
+
+Version 6.8.5 *(2020-03-08)*
+----------------------------
+
+ * Added a Go To Today menu button at the event list view too
+ * Some translation and stability improvements
+
+Version 6.8.4 *(2020-02-07)*
+----------------------------
+
+ * Added many translation and stability improvements
+
+Version 6.8.3 *(2019-12-29)*
+----------------------------
+
+ * Fixed a glitch at events repeating every X weeks
+ * Added an extra check to avoid showing reminders of deleted event repetition instances
+ * Some stability and translation improvements
+
+Version 6.8.2 *(2019-12-18)*
+----------------------------
+
+ * Improved some holidays
+ * Added a few stability and translation improvements
+
+Version 6.8.1 *(2019-12-11)*
+----------------------------
+
+ * Adding some time zone related crashfixes
+
+Version 6.8.0 *(2019-12-11)*
+----------------------------
+
+ * Added time zone support (customization has to be enabled in the app settings)
+ * Some UI improvements here and there
+ * Some stability and translation improvements
+
+Version 6.7.2 *(2019-12-02)*
+----------------------------
+
+ * Fixed some glitches at importing events from .ics files
+ * Improved the user experience of Simple Event List view
+ * Added some initial things under the hood for proper timezone support
+ * Couple stability and translation improvements
+
+Version 6.7.1 *(2019-11-18)*
+----------------------------
+
+ * Fixed a glitch at rechecking CalDAV synced calendars too often
+
+Version 6.7.0 *(2019-11-17)*
+----------------------------
+
+ * Fixed some repeating CalDAV synced events not showing up properly
+ * Improved the event sorting at the monthly widget
+ * Properly refresh CalDAV synced events in the background
+ * Some translation and stability improvements
+
+Version 6.6.5 *(2019-11-06)*
+----------------------------
+
+ * Fixed a glitch with small letters in some cases
+ * Some translation improvements
+
+Version 6.6.4 *(2019-10-27)*
+----------------------------
+
+ * Never show the Add New Event button over the yearly view
+ * Some stability and translation improvements
+
+Version 6.6.3 *(2019-10-13)*
+----------------------------
+
+ * Fixed a glitch with small letters and not being able to force English in some cases
+
+Version 6.6.2 *(2019-10-09)*
+----------------------------
+
+ * Added some stability and translation improvements
+
+Version 6.6.1 *(2019-09-17)*
+----------------------------
+
+ * Fixed a glitch with syncing all-day events via Radicale (by ddast)
+ * Use better messages at birthday/anniversary importing
+ * Fixed some UK holidays
+
+Version 6.6.0 *(2019-08-28)*
+----------------------------
+
+ * Use separate channels per event type reminders for more control
+ * Added some extra German and UK holidays
+ * Apply the Event List past limitation in the in-app view too, not just widget
+ * Fixing some glitches at importing events from .ics files
+ * Added some theming improvements
+
+Version 6.5.7 *(2019-08-07)*
+----------------------------
+
+ * Properly use the selected default event calendar, even at CalDAV synced ones
+ * Fixing invisible buttons at the date/time pickers with light theme
+ * Fixed a couple other smaller glitches and added some translation improvements
+
+Version 6.5.6 *(2019-07-26)*
+----------------------------
+
+ * Properly handle birthday and anniversary updating
+ * Fixed a widget list related glitch
+
+Version 6.5.5 *(2019-07-25)*
+----------------------------
+
+ * Added some dark theme related improvements
+ * Allow customizing the bottom navigation bar color
+ * Added a Go To Today button at the event list widget
+
+Version 6.5.4 *(2019-07-01)*
+----------------------------
+
+ * Adding some stability improvements
+
+Version 6.5.3 *(2019-06-30)*
+----------------------------
+
+ * Added some translation and stability improvements
+
+Version 6.5.2 *(2019-06-28)*
+----------------------------
+
+ * Don't show events that end at 00:00 as multi day event (by archibishop)
+ * Properly handle opening files with type application/ics
+ * Some other stability, translation and performance improvements
+
+Version 6.5.1 *(2019-06-13)*
+----------------------------
+
+ * Fixed a glitch related to CalDAV synced events reappearing after deletion
+ * Many translation improvements
+
+Version 6.5.0 *(2019-05-04)*
+----------------------------
+
+ * Show a more specific message if no new event has been found at importing
+ * Fixed an Event list widget glitch with overlapping event type indicator colors
+ * Misc smaller improvements
+
+Version 6.4.3 *(2019-04-10)*
+----------------------------
+
+ * Fixed some third party intent handling
+ * Some stability and translation improvements
+
+Version 6.4.2 *(2019-04-03)*
+----------------------------
+
+ * Added holidays in Taiwan
+ * Added an explanation dialog at upgrading from Free version
+ * Some stability and translation improvements
+
+Version 6.4.1 *(2019-03-23)*
+----------------------------
+
+ * Fixed a CalDAV sync related glitch with missing events
+ * Fixed adding reminders to birthdays and anniversaries
+
+Version 6.4.0 *(2019-03-20)*
+----------------------------
+
+ * Added email reminders and attendees in CalDAV synced events
+ * Improved CalDAV event syncing in the background
+ * Fixed some sorting related glitches
+ * Some other stability and UX improvements
+
+Version 6.3.2 *(2019-03-07)*
+----------------------------
+
+ * Added a "Go to date" to most views for easy jumping between dates
+ * Added an app shortcut for creating new events quickly, from Android 7.1+
+
+Version 6.3.1 *(2019-02-23)*
+----------------------------
+
+ * Allow adding event reminders to birthdays/anniversaries
+ * Filled content description of some views to improve the UX of visually impaired people
+ * A few more stability and UX improvements here and there
+
+Version 6.3.0 *(2019-02-14)*
+----------------------------
+
+ * Allow setting default start time/duration/event type for new events
+ * Allow exporting/importing settings
+ * Fixed a glitch with repeating events older than from year 2001
+ * Fixed some glitches related to overlapping events on the monthly and weekly view
+
+Version 6.2.2 *(2019-01-25)*
+----------------------------
+
+ * Fixed some CalDAV sync glitches
+ * Increase the visibility of dimmed events on the monthly view
+
+Version 6.2.1 *(2019-01-08)*
+----------------------------
+
+ * Fixed a CalDAV sync related glitches
+ * Fixed a glitch at showing notifications below Android Oreo
+
+Version 6.2.0 *(2019-01-07)*
+----------------------------
+
+ * Fixed a few CalDAV sync related glitches
+ * Properly handle reminder vibrations
+
+Version 6.1.2 *(2018-12-25)*
+----------------------------
+
+ * Fixed "Duplicate Event" not working
+ * Fixed a glitch with weekly view sometimes being blank
+
+Version 6.1.1 *(2018-12-19)*
+----------------------------
+
+ * Fixed one more case of CalDAV events getting duplicated
+ * Fixed the "New event" button sometimes not working properly
+ * Fixed some cases of empty weekly views at app launch
+ * Few other UX and stability improvements
+
+Version 6.1.0 *(2018-12-05)*
+----------------------------
+
+ * Many bugfixed related to CalDAV sync and event importing via .ics files
+ * Fixed a couple weekly view related glitches
+ * Open specific event details on clicking it from Event list widget
+ * Fix a glitch related to events repeating by X weeks for a long time
+ * Many other smaller fixes and performance/stability improvements
+
+Version 6.0.1 *(2018-11-18)*
+----------------------------
+
+ * Fixed some crashes and UX glitches
+
+Version 6.0.0 *(2018-11-16)*
+----------------------------
+
+ * Initial Pro version
+ * Fully rewrote the database storing events
+ * Fixed some issues related to importing events from .ics files and CalDAV sync
+
+Version 5.1.3 *(2018-11-29)*
+----------------------------
+
+ * This version of the app is no longer maintained, please upgrade to the Pro version. You can find the Upgrade button at the top of the app Settings.
+
+Version 5.1.2 *(2018-11-09)*
+----------------------------
+
+ * Couple smaller UX improvements
+
+Version 5.1.1 *(2018-10-25)*
+----------------------------
+
+ * Fixing a crash related to pull-to-refresh swiping
+
+Version 5.1.0 *(2018-10-24)*
+----------------------------
+
+ * Add optional pull-to-refresh for refreshing CalDAV events on some views (by azisuazusa)
+ * Allow setting a default view to be opened from the Event List widget (by knusprjg)
+ * Apply selected filters on all views, including widgets
+ * Allow changing any CalDAV calendars color, even if only locally
+ * Fix some glitches related to saving CalDAV events in a wrong calendar
+ * Some performance improvements related to fetching events
+ * Couple other smaller stability/ux improvements
+
+Version 5.0.1 *(2018-10-17)*
+----------------------------
+
+ * Fixed transparent date/time picker backgrounds
+
+Version 5.0.0 *(2018-10-16)*
+----------------------------
+
+ * Increased the minimal required Android OS version to 5
+ * Some translation and stability improvements
+
+Version 4.2.1 *(2018-09-22)*
+----------------------------
+
+ * Fixed some crashes related to specific invalid times in some timezones
+ * Added some holidays in Malaysia and Australia by youdly
+ * Added a new warning if the app notifications are disabled by the system
+ * Some other translation improvements and bugfixes
+
+Version 4.2.0 *(2018-09-10)*
+----------------------------
+
+ * Replaced colored event type dots with bars for better visibility
+ * Fixed some wrong reminder date data
+ * Properly highlight running all-day events
+ * Fix a glitch with CalDAV events being saved in the wrong calendar
+ * Couple other smaller UX and translation improvements
+
+Version 4.1.3 *(2018-08-06)*
+----------------------------
+
+ * Added a Go To Today button at the monthly widget
+ * Increase the allowed length of event titles, locations, descriptions
+ * Couple other stability and UX improvements
+
+Version 4.1.2 *(2018-07-14)*
+----------------------------
+
+ * Made reminder sounds more reliable on Android Oreo
+ * Properly fetch running events at reboot, notify only if needed
+ * Couple UX and stability improvements
+
+Version 4.1.1 *(2018-07-04)*
+----------------------------
+
+ * Allow customizing the audio stream used by reminders
+ * Show the time remaining till the reminder appears
+ * Couple other UX and stability improvements
+
+Version 4.1.0 *(2018-06-13)*
+----------------------------
+
+ * Make reminders on Android Oreo more reliable
+ * Allow deleting only future occurrences of repeating events
+ * Fixed some visual glitches at the weekly view
+ * Multiple CalDAV event related improvements
+
+Version 4.0.4 *(2018-05-27)*
+----------------------------
+
+ * Make sure the alarm rings properly at DND mode
+ * Improved the UK holidays and added Singapore ones
+ * Make Event list items more compact when possible
+ * Couple other UX improvements and bugfixes
+
+Version 4.0.3 *(2018-05-15)*
+----------------------------
+
+ * Fixing some widget related crashes
+
+Version 4.0.2 *(2018-05-14)*
+----------------------------
+
+ * Make sure we store the proper calendar ID at events
+
+Version 4.0.1 *(2018-05-14)*
+----------------------------
+
+ * Fix app not opening at clicking widgets
+ * Couple stability improvements
+
+Version 4.0.0 *(2018-05-10)*
+----------------------------
+
+ * Allow changing the app launcher color
+ * Allow setting reminder looping till dismissed
+ * Added a button in Settings for changing widget colors without recreating them
+ * Added optional dimming of past events
+ * Make Event List view an endless scrollview
+ * Added some more advanced yearly repetition rules
+ * Improved some country holidays
+ * Many CalDAV related improvements
+ * Many other smaller bugfixes and performance/UX improvements
+
+Version 3.4.2 *(2018-04-13)*
+----------------------------
+
+ * Hide public notification content if desired so (by fraang)
+ * Added optional grid on the monthly view
+ * Allow exporting events on SD cards
+ * Allow selecting No Sound as a reminder sound
+ * Set default event status for CalDAV events as Confirmed
+
+Version 3.4.1 *(2018-03-30)*
+----------------------------
+
+ * Reworked custom notification sound, should be more reliable
+ * Fixed some glitches related to the monthly view
+ * Misc smaller bugfixes and stability improvements
+
+Version 3.4.0 *(2018-02-28)*
+----------------------------
+
+ * Rewrote the monthly view
+ * Improved the performance at importing events from ics files
+ * Added many new country holidays
+ * Handle some new third party intents
+
+Version 3.3.2 *(2018-02-21)*
+----------------------------
+
+ * Try fixing the off-by-one issue at CalDAV syncing all-day events
+ * Couple stability improvements
+
+Version 3.3.1 *(2018-02-19)*
+----------------------------
+
+ * Improved CalDAV all-day event importing (by angelsl)
+ * Added a FAQ section with a couple initial items
+ * Once again fixed some cases of blank or duplicate views
+
+Version 3.3.0 *(2018-02-10)*
+----------------------------
+
+ * Fixed blank or duplicate views in some cases (yes, again)
+ * Fixed off-by-one day error at syncing all-day events via Nextcloud
+ * Make default filenames at export more user-friendly
+ * Improved the performance by removing some unnecessary redraws
+ * Added a toggle for switching between default snooze interval or always showing an interval picker
+
+Version 3.2.4 *(2018-02-05)*
+----------------------------
+
+ * Fixed blank screens in some cases
+ * Misc smaller improvements
+
+Version 3.2.3 *(2018-02-01)*
+----------------------------
+
+ * Fixed blank screens in some cases
+ * Make sure the Add New Event button works when opening the app from a widget
+ * Removed the "Default event reminder" from settings, remember last used values
+ * Allow selecting Snooze interval at pressing Snooze
+ * Allow disabling displaying of What's New
+ * Add a Back button at the actionmenu when opening a subview
+ * Allow deleting all events at once without reseting event types and other settings
+
+Version 3.2.2 *(2018-01-27)*
+----------------------------
+
+ * Fixed some cases of reminders not triggering
+ * Properly handle importing events with multiple lines long description
+ * Properly show the New Event button whenever appropriate
+
+Version 3.2.1 *(2018-01-22)*
+----------------------------
+
+ * Misc minor fixes
+
+Version 3.2.0 *(2018-01-22)*
+----------------------------
+
+ * Added an initial implementation of Search
+ * Fixed an off-by-one issue at syncing all-day CalDAV events
+ * Added a Daily View
+ * Allow importing events from .ics files directly in a CalDAV account
+ * Try parsing latitude and longitude coordinates from the Location field
+
+Version 3.1.0 *(2018-01-11)*
+----------------------------
+
+ * Made some CalDAV sync improvements, especially related to repeatable event exceptions
+ * Added a Map button to event location, to display the location in a third party map
+ * Handle INSERT and EDIT intent
+ * Made Dark theme the default
+ * Updated both event list and monthly widget, hopelly making them more reliable
+ * Added holidays in Australia
+ * Hopefully fixed the off-by-one error at importing/syncing all-day events/holidays
+
Version 3.0.1 *(2017-12-06)*
----------------------------
diff --git a/LICENSE b/LICENSE
index 05ae14869..f288702d2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,674 @@
- Apache License
- Version 2.0, January 2004
- https://www.apache.org/licenses/
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
- 1. Definitions.
+ Preamble
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
+ The precise terms and conditions for copying, distribution and
+modification follow.
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
+ TERMS AND CONDITIONS
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
+ 0. Definitions.
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
+ "This License" refers to version 3 of the GNU General Public License.
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
+ 1. Source Code.
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
- END OF TERMS AND CONDITIONS
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
- APPENDIX: How to apply the Apache License to your work.
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "{}"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
- Copyright 2017 SimpleMobileTools
+ The Corresponding Source for a work in source code form is that
+same work.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
+ 2. Basic Permissions.
- https://www.apache.org/licenses/LICENSE-2.0
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
index 291148cd7..67bd81b98 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,25 @@
# Simple Calendar
-
+
A simple calendar with events and a customizable widget.
-An offline calendar without any other calendar integration. You can easily create recurring events and setup reminders, it can also display week numbers.
+A simple calendar with optional CalDAV synchronization. You can easily create recurring events and setup reminders, it can also display week numbers.
-Contains a resizable 4x4 widget where you can customize the color of the text, as well as the alpha and the color of the background.
+Contains a monthly view and an event list widget where you can customize the color of the text, as well as the alpha and the color of the background.
-Contains no ads or unnecessary permissions. It is fully open-source, provides customizable colors.
+Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
The Storage permission is needed only for exporting or importing events from .ics files.
-This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com
+The Contacts permission is used only at importing contact birthdays and anniversaries.
-
-
+This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com
-
-
-
+
+
-License
--------
- Copyright 2017 SimpleMobileTools
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+
diff --git a/app/build.gradle b/app/build.gradle
index 945851f8b..7571f2274 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,29 +1,51 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'de.timfreiheit.resourceplaceholders'
+
+def keystorePropertiesFile = rootProject.file("keystore.properties")
+def keystoreProperties = new Properties()
+if (keystorePropertiesFile.exists()) {
+ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+}
android {
- compileSdkVersion 27
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
defaultConfig {
- applicationId "com.simplemobiletools.calendar"
- minSdkVersion 16
- targetSdkVersion 27
- versionCode 106
- versionName "3.0.1"
+ applicationId "com.simplemobiletools.calendar.pro"
+ minSdkVersion 21
+ targetSdkVersion 29
+ versionCode 177
+ versionName "6.9.4"
multiDexEnabled true
setProperty("archivesBaseName", "calendar")
+ vectorDrawables.useSupportLibrary = true
}
signingConfigs {
- release
+ if (keystorePropertiesFile.exists()) {
+ release {
+ keyAlias keystoreProperties['keyAlias']
+ keyPassword keystoreProperties['keyPassword']
+ storeFile file(keystoreProperties['storeFile'])
+ storePassword keystoreProperties['storePassword']
+ }
+ }
}
buildTypes {
+ debug {
+ applicationIdSuffix ".debug"
+ }
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- signingConfig signingConfigs.release
+ if (keystorePropertiesFile.exists()) {
+ signingConfig signingConfigs.release
+ }
}
}
@@ -35,38 +57,19 @@ android {
checkReleaseBuilds false
abortOnError false
}
-}
-ext {
- leakCanaryVersion = '1.5.4'
+ resourcePlaceholders {
+ files = ['xml/shortcuts.xml']
+ }
}
dependencies {
- implementation 'com.simplemobiletools:commons:3.4.12'
- implementation 'joda-time:joda-time:2.9.9'
- implementation 'com.facebook.stetho:stetho:1.5.0'
- implementation 'com.android.support:multidex:1.0.2'
- implementation 'com.google.code.gson:gson:2.8.2'
+ implementation 'com.simplemobiletools:commons:5.28.23'
+ implementation 'joda-time:joda-time:2.10.1'
+ implementation 'androidx.multidex:multidex:2.0.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
- debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
- releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
-}
-
-Properties props = new Properties()
-def propFile = new File('signing.properties')
-if (propFile.canRead()) {
- props.load(new FileInputStream(propFile))
-
- if (props != null && props.containsKey('STORE_FILE') && props.containsKey('KEY_ALIAS') && props.containsKey('PASSWORD')) {
- android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
- android.signingConfigs.release.storePassword = props['PASSWORD']
- android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
- android.signingConfigs.release.keyPassword = props['PASSWORD']
- } else {
- println 'signing.properties found but some entries are missing'
- android.buildTypes.release.signingConfig = null
- }
-} else {
- println 'signing.properties not found'
- android.buildTypes.release.signingConfig = null
+ kapt 'androidx.room:room-compiler:2.2.5'
+ implementation 'androidx.room:room-runtime:2.2.5'
+ annotationProcessor 'androidx.room:room-compiler:2.2.5'
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 31a130621..ad7d60487 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -1,7 +1 @@
-keep class com.simplemobiletools.calendar.models.** { *; }
-
-# Joda
--dontwarn org.joda.convert.**
--dontwarn org.joda.time.**
--keep class org.joda.time.** { *; }
--keep interface org.joda.time.** { *; }
diff --git a/app/src/debug/res/drawable-hdpi/leak_canary_icon.png b/app/src/debug/res/drawable-hdpi/leak_canary_icon.png
deleted file mode 100644
index e10f62503..000000000
Binary files a/app/src/debug/res/drawable-hdpi/leak_canary_icon.png and /dev/null differ
diff --git a/app/src/debug/res/drawable-xhdpi/leak_canary_icon.png b/app/src/debug/res/drawable-xhdpi/leak_canary_icon.png
deleted file mode 100644
index 462db1d63..000000000
Binary files a/app/src/debug/res/drawable-xhdpi/leak_canary_icon.png and /dev/null differ
diff --git a/app/src/debug/res/drawable-xxhdpi/leak_canary_icon.png b/app/src/debug/res/drawable-xxhdpi/leak_canary_icon.png
deleted file mode 100644
index 047eedcae..000000000
Binary files a/app/src/debug/res/drawable-xxhdpi/leak_canary_icon.png and /dev/null differ
diff --git a/app/src/debug/res/drawable-xxxhdpi/leak_canary_icon.png b/app/src/debug/res/drawable-xxxhdpi/leak_canary_icon.png
deleted file mode 100644
index 6c264e807..000000000
Binary files a/app/src/debug/res/drawable-xxxhdpi/leak_canary_icon.png and /dev/null differ
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
index c7ff04099..a3ed195d5 100644
--- a/app/src/debug/res/values/strings.xml
+++ b/app/src/debug/res/values/strings.xml
@@ -1,4 +1,4 @@
- Calendar Leaks
-
\ No newline at end of file
+ Calendar_debug
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c1ac53406..a715c50a7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,20 +1,27 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ tools:node="remove" />
+
+
+ android:launchMode="singleTask"
+ android:theme="@style/SplashTheme" />
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -60,7 +80,7 @@
android:screenOrientation="portrait"
android:theme="@style/MyWidgetConfigTheme">
-
+
@@ -69,56 +89,98 @@
android:screenOrientation="portrait"
android:theme="@style/MyWidgetConfigTheme">
-
+
+
+
+
+
+
+
+ android:parentActivityName=".activities.MainActivity" />
+ android:parentActivityName=".activities.SettingsActivity" />
+ android:parentActivityName="com.simplemobiletools.commons.activities.AboutActivity" />
+ android:name="com.simplemobiletools.commons.activities.FAQActivity"
+ android:label="@string/frequently_asked_questions"
+ android:parentActivityName="com.simplemobiletools.commons.activities.AboutActivity" />
+ android:launchMode="singleTask"
+ android:parentActivityName=".activities.MainActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:parentActivityName=".activities.MainActivity" />
+ android:parentActivityName=".activities.SettingsActivity" />
+
+
-
+
+ android:resource="@xml/widget_monthly_info" />
-
+
+ android:resource="@xml/widget_list_info" />
+
+
+
+
+
+
+
+
+ android:permission="android.permission.BIND_REMOTEVIEWS" />
-
+
-
+
-
+
+
+
+
+
-
-
+
+
+
+ android:resource="@xml/provider_paths" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/australia.ics b/app/src/main/assets/australia.ics
new file mode 100755
index 000000000..9af6a93be
--- /dev/null
+++ b/app/src/main/assets/australia.ics
@@ -0,0 +1,358 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+UID:20180101_60o30chhcgo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180126
+DTEND;VALUE=DATE:20180127
+UID:20180126_60o30chhcko30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Australia Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180507
+DTEND;VALUE=DATE:20180508
+UID:20180507_60o30chicko36e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=1MO
+SUMMARY:May Day (Northern Territory)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180312
+DTEND;VALUE=DATE:20180313
+UID:20180312_60o30chicko3ie1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2MO
+SUMMARY:Labour Day (Victoria)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180305
+DTEND;VALUE=DATE:20180306
+UID:20180305_60o30chicko3ge1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=1MO
+SUMMARY:Labour Day (Western Australia)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181001
+DTEND;VALUE=DATE:20181002
+UID:20181001_60o30chicko3ec1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=1MO
+SUMMARY:Labour Day (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180507
+DTEND;VALUE=DATE:20180508
+UID:20180507_60o30chicko38e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=1MO
+SUMMARY:Labour Day (Queensland)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180312
+DTEND;VALUE=DATE:20180313
+UID:20180312_60o30chicko62e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2MO
+SUMMARY:Eight Hours Day (Tasmania)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180425
+DTEND;VALUE=DATE:20180426
+UID:20180425_60o30chi6so32c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:ANZAC Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181001
+DTEND;VALUE=DATE:20181002
+UID:20181001_60o30chhcoo38c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=1MO
+SUMMARY:Queen's Birthday (Queensland)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180611
+DTEND;VALUE=DATE:20180612
+UID:20180611_60o30chhcoo32e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6;BYDAY=2MO
+SUMMARY:Queen's Birthday (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180212
+DTEND;VALUE=DATE:20180213
+UID:20180212_60o30e1pcko30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYDAY=2MO
+SUMMARY:Royal Hobart Regatta (Tasmania)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180312
+DTEND;VALUE=DATE:20180313
+UID:20180312_60o30chj64o30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2MO
+SUMMARY:Canberra Day (Australian Capital Territory)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180806
+DTEND;VALUE=DATE:20180807
+UID:20180806_60o30chicgo30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYDAY=1MO
+SUMMARY:Northern Territory Picnic Day (Northern Territory)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180604
+DTEND;VALUE=DATE:20180605
+UID:20180604_60o30chj60o30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6;BYDAY=1MO
+SUMMARY:Western Australia Day (Western Australia)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181105
+DTEND;VALUE=DATE:20181106
+UID:20181105_60o30chj6ko30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1MO
+SUMMARY:Recreation Day (Tasmania)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181106
+DTEND;VALUE=DATE:20181107
+UID:20181106_60o30chj6oo30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=1TU
+SUMMARY:Melbourne Cup Day (Victoria)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180806
+DTEND;VALUE=DATE:20180807
+UID:20180806_60o30chicco30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYDAY=1MO
+SUMMARY:New South Wales Bank Holiday (New South Wales)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180312
+DTEND;VALUE=DATE:20180313
+UID:20180312_60o30chicoo30e1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=2MO
+SUMMARY:Adelaide Cup (South Australia)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181007
+DTEND;VALUE=DATE:20181008
+UID:20181007_60o30c9o60o30dpl6ooj0dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=1SU
+SUMMARY:Daylight Saving Time starts
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180401
+DTEND;VALUE=DATE:20180402
+UID:20180401_60o30c9o64o30dpl6ooj0dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4;BYDAY=1SU
+SUMMARY:Daylight Saving Time ends
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180321
+DTEND;VALUE=DATE:20180322
+UID:20180321_60o30opo64o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Harmony Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181111
+DTEND;VALUE=DATE:20181112
+UID:20181111_60o30chj6so30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Remembrance Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181224
+DTEND;VALUE=DATE:20181225
+UID:20181224_60o30chi60o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Christmas Eve
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+UID:20181225_60o30chi64o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Christmas Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+UID:20181226_60o30chi68o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Boxing Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181231
+DTEND;VALUE=DATE:20190101
+UID:20181231_60o30chhcco30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:New Year's Eve
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200127
+DTEND;VALUE=DATE:20200128
+UID:20200127_60o30chhcko30e1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Australia Day observed
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+UID:20200412_60o30chi6ko36c1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Easter Sunday (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210404
+DTEND;VALUE=DATE:20210405
+UID:20210404_60o30chi6ko3ac1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Easter Sunday (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+UID:20200413_60o30chi6oo30c1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Easter Monday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210405
+DTEND;VALUE=DATE:20210406
+UID:20210405_60o30chi6oo30c1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Easter Monday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200414
+DTEND;VALUE=DATE:20200415
+UID:20200414_60o30chj6co30c1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Easter Tuesday (Tasmania)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210406
+DTEND;VALUE=DATE:20210407
+UID:20210406_60o30chj6co30c1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Easter Tuesday (Tasmania)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+UID:20200410_60o30chi6co30c1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Good Friday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210402
+DTEND;VALUE=DATE:20210403
+UID:20210402_60o30chi6co30c1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Good Friday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200411
+DTEND;VALUE=DATE:20200412
+UID:20200411_60o30chi6go32c1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Holy Saturday (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210403
+DTEND;VALUE=DATE:20210404
+UID:20210403_60o30chi6go32c1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Holy Saturday (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200601
+DTEND;VALUE=DATE:20200602
+UID:20200601_60o32dr3cgo30e1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Reconciliation Day (Australian Capital Territory)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210531
+DTEND;VALUE=DATE:20210601
+UID:20210531_60o32dr3cgo30e1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Reconciliation Day (Australian Capital Territory)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200812
+DTEND;VALUE=DATE:20200813
+UID:20200812_60o30chj6go30c1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Royal National Agricultural Show Day Queensland (Queensland)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210811
+DTEND;VALUE=DATE:20210812
+UID:20210811_60o30chj6go30c1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Royal National Agricultural Show Day Queensland (Queensland)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200928
+DTEND;VALUE=DATE:20200929
+UID:20200928_60o30chhcoo36e1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Queen's Birthday (Western Australia)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210927
+DTEND;VALUE=DATE:20210928
+UID:20210927_60o30chhcoo36e1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Queen's Birthday (Western Australia)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20201228
+DTEND;VALUE=DATE:20201229
+UID:20201228_60o30chic8o30c1g60o30dr56g@google.com
+STATUS:CONFIRMED
+SUMMARY:Christmas/Boxing Day Holiday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20211227
+DTEND;VALUE=DATE:20211228
+UID:20211227_60o30chic4o30c1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Christmas/Boxing Day Holiday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20211228
+DTEND;VALUE=DATE:20211229
+UID:20211228_60o32o9hc4o30e1g60o30dr56k@google.com
+STATUS:CONFIRMED
+SUMMARY:Christmas/Boxing Day Holiday
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/austria.ics b/app/src/main/assets/austria.ics
index 1767fafd6..955258974 100644
--- a/app/src/main/assets/austria.ics
+++ b/app/src/main/assets/austria.ics
@@ -1,1486 +1,275 @@
BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//bt.dd/NONSGML Kolab OpenERP V0.9//EN
-CALSCALE:GREGORIAN
-METHOD:PUBLISH
-BEGIN:VTIMEZONE
-TZID:Europe/Vienna
-BEGIN:DAYLIGHT
-DTSTART;VALUE=DATE-TIME:19810329T010000Z
-RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
-TZNAME:CEST
-TZOFFSETFROM:+0100
-TZOFFSETTO:+0200
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART;VALUE=DATE-TIME:19810927T010000Z
-RRULE:FREQ=YEARLY;COUNT=15;BYDAY=-1SU;BYMONTH=9
-TZNAME:CET
-TZOFFSETFROM:+0200
-TZOFFSETTO:+0100
-END:STANDARD
-BEGIN:STANDARD
-DTSTART;VALUE=DATE-TIME:19961027T010000Z
-RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
-TZNAME:CET
-TZOFFSETFROM:+0200
-TZOFFSETTO:+0100
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-SUMMARY:(Valentinstag)
-DTSTART;VALUE=DATE:20100214
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:UYfoE9MVREOzRjoQFnXHSc-DPA-BhfcoC9Vyn82m3hZIf9nsV-Lhn@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=14;BYMONTH=2
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20100404
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Q2V68c3woNyiTJJdgqD5dY-CIA-KWo8DVBsZRitXbfD4Ycxuk-Gb4@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20110424
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:DZNNPrKWumPekczTuKQsN4-CIA-DIg4skLrxzuNDzMxFSBHGE-SKS@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20120408
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HX3awNhibAEYJodVD4eya4-CIA-IY6MX3nTWdRq6nJeonq7xA-HUu@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20130331
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:IjXeIqCePo52D7nXJJTtDO-CIA-IrqHIoxswWaZ5ehU8cKnz9-HnJ@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20140420
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VgZoCTTDSbuuiVXqd3pGVs-CIA-FE47S5KjYemHinMw7UrTL5-JqJ@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20150405
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:IxXExC65L5PrUKiAJzV7Tt-CIA-GSQYtNwVsRoZJ4FgdOMhiB-L7o@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20160327
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TDskhoqM2U4Eq4AwQneDmV-CIA-DfONtuoxM3FKwgFIQCRWgs-55A@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20170416
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:IPi9CSXJFJCDLjKYTzemED-CIA-Gu3V6pFeTRPtHYJmankAhI-G9R@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20180401
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:InjX6FVtMyCZZsnT3YBLBP-CIA-B8asgQVzkSRY2QoJnjqTMT-NmI@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20190421
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:BCCN5b64V6f89UVxi7ykVf-CIA-J7xsQC8ah3LhrXRRkcEnBk-QoA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20200412
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QHxQtcgYtyKtQcApdOosp7-CIA-K5WZZsJcqhGkfZ7kB875Xu-MTK@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20210404
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:uh6ehP6bncjhKeVOEjBmsA-CIA-HgEe3ySLkHdrWgs74W4VQY-C8H@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20220417
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Fg3D9LFiYB6LRO9nqzQjei-CIA-DStSH9drIVITLoLL7qrmUg-MRj@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20230409
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QFOekiEOjUGLtfkj2g4TRJ-CIA-IWigVGrNtXGGWesPqeu93c-P5p@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20240331
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VSuVAMB5tpUbXX5RPpNR79-CIA-IwBF9Ic4xb8KAEDn8UudYT-BSP@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-DTSTART;VALUE=DATE:20250420
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:DItomNBJTu54sboZbcUKD6-CIA-CiuDT2XuKiBFt4tLcXTITj-G8V@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20100523
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:BuFkkHay2rogouKUFyqhku-CLA-B9gtBpiHsWUOKWhjhprkIn-QEr@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20110612
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:UEk69s5e2UxkhXB4UUWcDa-CLA-GUuFchunnJqpM9uwF2Yf2H-JBB@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20120527
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NLXyWu6tmDVaHiBVeKuLe5-CLA-JVqTOmkVNYoVAqCaVW7Ej8-Oo9@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20130519
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MNgzOw4MoEwKhPSVVgUESJ-CLA-BN5aIFdXjGuiudw4PwZdhb-Poe@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20140608
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:L48Z6BZIaoiLf3c2XKH8qp-CLA-E9tETu5fpjp3qHVZWYxVya-Ghc@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20150524
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:D5qrKdPV7DEKuuXQyYstbn-CLA-BBKdzqdXQIsrxfLUdHqZbP-Rys@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20160515
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OtKbGUJhVJOqAtfGJHdVR2-CLA-GbEDoKxaWffGhO3ohhuNUX-QEJ@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20170604
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FAt3wKqxCfkxJEFwzKJgyg-CLA-FQVHUbBUzi9hR2uy8mIOX6-Gxx@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20180520
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Us36pItqRgOjDpzuEEhOUI-CLA-KLtWA2QSDVHqF2kwg7dhtB-PFK@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20190609
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:DwTpVqEPrjF8B66B95B2BB-CLA-Jb2Om4ahgSIdBbop8mFINU-RBK@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20200531
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GpZasQ6xaPyfu465bQWL38-CLA-BhjXR6V5ooiANkVK3aBsLL-CdO@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20210523
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:CX7efxwNmpssUmSp9pTDUZ-CLA-HTNXsa6oIG6uEbbQ5ApGtk-EIc@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20220605
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:JDdJcEqCPUIJVOJDfAywyP-CLA-pnJGWbtxjzPtxI24EbuOcA-EOH@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20230528
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:ICQhMXPwWJzwtndqpjhW4h-CLA-CNT6mrhbiFKkrsiLdKMpNh-NL3@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20240519
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:UpPUdYV8VUxq43M9nYQJEJ-CLA-CQ9yeqmOVmD9SCs4WfpJGf-BrD@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-DTSTART;VALUE=DATE:20250608
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MUZR5jr9pkiDsp82ZIHPC4-CLA-DhVgoZOreM2IMRyxqFcuBR-M9U@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Muttertag)
-DTSTART;VALUE=DATE:20100509
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:PQOaSGAy3Rs6zDQ2jEtb4Q-DJA-F8YjfMIF8wTCbIjJj85C82-JP2@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYDAY=+2SU;BYMONTH=5
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20101128
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:M6SyL5NuWC99K5XJVrnBVZ-CTA-E2e2T82hzrY6HuDqjwf8HP-I2c@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20111127
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:2amh7yprhiYNDKQYwgnshA-CTA-Ej928NnaQxA4OCmjcWpk56-C6n@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20121202
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:LsKu2AYWhwqfzBuBAwA7ZR-CTA-7JNMdzK5heC7PXgSJQutwA-RoO@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20131201
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MCmNycWnVSmWF3U9GZDqVR-CTA-D9C598j8icELERhkg4y9wI-IbH@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20141130
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:O9IhrNgV8rxViywNGWCAkq-CTA-HL8wVDRAtztttSZIf4F9tC-Sgp@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20151129
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HDsm8ASAVgVGG6wHywQdIB-CTA-G54s9D8FADYxFZjP5kQZJ4-pVA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20161127
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:G9oyVSW76TF7SEAK7S47GA-CTA-IO3Ik4FbEDSGXzqbIzOokf-JyA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20171203
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GtZ6r8QWjXeCGA6LT5hfcC-CTA-Kf8HFQ3Bj8ouG634HGBIjo-Msx@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20181202
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GCc7fX3WWmIKOzc8H3t4K5-CTA-GwpT3M6DWWGQ6YNcNnB2Na-xcA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20191201
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MeooTCFKuAs3KG27wsFZRa-CTA-K7ThVhF9HrRueAhVntAhoW-M9c@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20201129
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TzTNQEk7LKDihkN7ASpBnh-CTA-HEgrfeBe3XfIZNYxPXm6td-Lsw@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20211128
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TiNZnQMbV9WEkFbOVukM2g-CTA-HTaW36BcyMIywVwsWAreBU-LO5@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20221127
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:G7J4DrFajzPcepjxMOBVEQ-CTA-GsIn6TGjBwadbAfcDwcrto-G6A@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20231203
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HanZJ7kXnITA5r9O4233GO-CTA-EEksjauEx8Y8VcGfcY9gxz-R4t@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20241201
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:N6IVru2L5uVJ6kDkz7YJN9-CTA-HBEtY2Gra2zzaVKJuVdgnJ-CcB@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-DTSTART;VALUE=DATE:20251130
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:SPxj5Of4Q83J2Iq7ixndPT-CTA-I8hIYk5VPkP3ARVqBHjnNE-ICR@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20101205
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NwF2ufd9QsBYSHfVE8y9ru-CTA-HtqqMOZsP6pUFHhP3mEf6y-DxX@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20111204
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:S969CAnGKr7eOBrkgSVwtd-CTA-EZIeVgSET4VcjHZxLqzyxo-B5z@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20121209
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Uk53QV2t4BHRBDbmDaKzmY-CTA-FEnyShY4TMTVWXViJecMIo-RqN@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20131208
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:PDzbm5wwj2z9qR8ai6ayGV-CTA-FKPJjmiS3H93HSBV9pDhXm-BFa@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20141207
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MWeBEV3VqsAi39VZtxk8Jg-CTA-BwFMCb3jcOFWLuWAcAYfk3-LZS@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20151206
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:U7btNKVdmzdOA4kI6gRRbe-CTA-DVccfiHQSHmkbSQFTBeyjC-GIS@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20161204
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:PSPmIHTehtOEKc7Os6cqdJ-CTA-CbsRqGqfV3VSgxj6VPWmzB-MtU@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20171210
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VOSsLe6Vgy3WVNujIDNRi9-CTA-Inh9Btxp6JpU2utJHjNFhm-KFP@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20181209
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Pu4BN2D57LAy3CWOJjJ9Xu-CTA-EkgZtyKFQqh3wUXMVwjiop-PoD@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20191208
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OGtyQ8fyH44fYkMwR7yVn8-CTA-GoyFPJJHDzMDHWT5JiwTfJ-OIh@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20201206
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:2hiIQSVL2DEPgYVx5kHLBA-CTA-IqxO64FL4zWXnudVq9NQ9P-JnL@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20211205
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Q6VW85RMVeXyKGYNIwVUGZ-CTA-CAIba4R7w7MGP8yeOBVSpJ-E7t@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20221204
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:RDSmUoBPLhVSoWjc9NIX5C-CTA-Fr36CDGdxKoO5KCiMoDLbH-OND@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20231210
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HD8EsUq9ZDgikF5utoCUWe-CTA-FzINmuUAQ5x4W3RXarhKRI-ExL@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20241208
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:JsSkqhKPOBAjexGHwxHofN-CTA-J7Lytn43HbyV3sgcsUEYyk-NO2@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-DTSTART;VALUE=DATE:20251207
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QsE8GyFBHrCkT7SRJSfgZ8-CTA-Fb3QVyMVKRwpOJYCZjodNB-BR8@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20101212
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:PzxRR6j6D96DogJC9BsYBY-CUA-HDeNY728pGtHpxQ6OjbXyU-C3f@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20111211
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:SdxzmQzVKmeZr5yxazK4sY-CUA-K22d6ueLfsZLKO2rmdx6RM-EWW@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20121216
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:EefXON2AkkDMdTAZBHCSY7-CUA-EfEksCJsh6JDpAYXjM3mXo-MMn@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20131215
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GNSPRKSjVGjFqnVL2AZFi3-CUA-JFntXjDVa3sRGxINVNIdGy-rmA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20141214
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GqwN7W9OUG4qcdoosqHc5e-CUA-BesFjFMrnG9ejGhbN3aVmI-DnD@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20151213
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MsP2qAwY7auwBMkFK8hBdf-CUA-EJrEV7ZC5o5DVSBB4UMyZt-Oxz@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20161211
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:IUsPMmaP2tp6fuG3Uysg7Z-CUA-B44dX6pDsX9F9QYbW7UUxT-DXn@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20171217
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:CfJbNOWH5ZyHM22UooAVGi-CUA-FNXOeR5LNVb6PgFeWoakpG-KVR@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20181216
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HGPmOPECIcZG8AFrHn7b3U-CUA-FVoLh5ww9VeH2PXWbM6htj-LOK@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20191215
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:LMxKggzLqPswHhVmti5OBg-CUA-KeobhS4SHn8e9ZOM4Gtn9i-OWf@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20201213
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QKnKdHEM6CGEqFVOSLX5EM-CUA-HsEwYNBIihD9YVsMHk2Ftk-KMM@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20211212
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:BJ6wrhTdOVQfUjFwhEhCT8-CUA-JoSDSFoKOd9iGXEAonyPwV-RRr@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20221211
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FIKITtzOVkDkIWOwYeVuZI-CUA-QKQNIxseHZ9ZoXqI2S7A9A-BHL@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20231217
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MIIf54EGtjETcQYtkjU2HS-CUA-FFmHmncdnwfoTni4LGCDuz-LBV@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20241215
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:C4ILYPOXGnObR3TCjSE5u5-CUA-IBciL7puGdE63KN4VUpRwA-CBq@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-DTSTART;VALUE=DATE:20251214
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:SQRkEVZ2YBa45Bx3T6Tpsp-CUA-CzDcfiOeLNB62aVJLJXMwA-D87@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20101219
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QImQdR3ZedcwYyXncbHZQV-CUA-GFcJzsHAI3jjx7cFOhFCzR-Lwm@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20111218
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:ILQziRqjo7nA8q6muuhgIO-CUA-GtEwnTECcdWItLIymubb5D-GpX@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20121223
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:UAbLnQsxPuR4dYVwhROzQQ-CUA-FheeC4NFm4oEyo57t6ItNf-Ckk@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20131222
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:EEf3HtwTHsqzm4H7VIiOV7-CUA-Ff6DV4kLgxMNMsCYL9dmNo-BDr@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20141221
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NDsHrC3SadZgYuhXLnKjBc-CUA-JwuCdmiGKUqGoN9xJgIUX6-Nrr@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20151220
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NNRj47gKp4O9HgXfhGsVAK-CUA-KwPPN3J2ho6jL54JQPIiHw-DsU@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20161218
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:ETEuiHwEOsAkjNi93fTpTV-CUA-GzT2YNMpiyxM5COMZUCJcA-Mf4@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20171224
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:PRrb2Hg65QT7phNsUWjq98-CUA-EwsT7kzYtTSbnVcpQpuKBD-Kf9@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20181223
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:UVFNZE7Is3esAwjz2hHtSh-CUA-CfbiAeFJkk78CCmZVzpNS3-Sc9@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20191222
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:EGY7sq3ifQcAq4memgJcRY-CUA-E6VhBBsfdtxdQ99AVZ6qcP-PXB@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20201220
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:BjKuzFVZG9XBguTG5EEcRO-CUA-JFg4pAJeSaXNi65VqBU7Cg-NVt@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20211219
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:EaZVUfNU3n3sWZ6RVbBaIV-CUA-HDkKXpniDgrOVubne4k6tE-PFZ@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20221218
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:JwkhSoJnfyY46VshB6EVSZ-CUA-HQzOKjYsfYP9bhVDjWSE6U-Os8@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20231224
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:JoVCJYV8jVwHKYnfFhIuAg-CUA-F9N6jBWTHaKRtyKEtDEUQC-G7K@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20241222
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:E8FEBakJGuIjB2ZuyViVzA-CUA-IwPrIVxGq5N2WkaXFAPF7P-QW2@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-DTSTART;VALUE=DATE:20251221
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:UseZXNzchqeCgrGyeuBoUK-CUA-E9bprmZuLKANHk73VycMS4-OYN@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Nikolaustag)
-DTSTART;VALUE=DATE:20101206
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FDggIfbpXwt5x5DYeRX6tZ-DOA-EADLAMHuH4LUNVBh2NkVkk-BB8@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=6;BYMONTH=12
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hl. Abend
-DTSTART;VALUE=DATE:20101224
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GoTo3S9PjJ7cFI7a3saWTk-DFA-Bc9G5Y4nDo56WSoFPAHiWR-HfX@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=24;BYMONTH=12
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sylvester
-DTSTART;VALUE=DATE:20101231
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TgJnNeudMWs2PnVAkJILAu-DFA-KAYZ2wMdIjcM3hZ7aUn2ey-JSZ@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=31;BYMONTH=12
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20100217
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:KnhpM7aDH5LK2khOn6CbN9-CXA-HmEjmH9xJPAkbktOkRHWKV-Lmg@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20110309
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Tz2uhSDcTUKmSDfkehptOO-CXA-JKqiuUVxcqmdHiV3OqpbnM-CQZ@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20120222
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:DxkqOVkJPsmVCSRh5br6C7-CXA-GjsIaEOOLOdwPxXU7rPoaV-JKy@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20130213
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TnZbQGfDSB3Z7B2aOUt9Y9-CXA-IzSfnLIpWnpp8RPOWqBZHB-LFG@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20140305
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VRHYOHGVWR2UO4Rto7pAW7-CXA-CErxzrOHtVCUSVI5UIAenN-Hun@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20150218
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MxBdtSx9jjVcboaRTUxuai-CXA-CSLVwVakwWpSj69yZBtt2t-QoX@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20160210
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HuVOgBHWBwRKRiOeubiHhu-CXA-EnVfOZGrrPmU8U7ZbuGqsu-Ezd@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20170301
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:F6xWsTX5eS7dRpKiDjWAEZ-CXA-GiJgxsPAbmpFpePtWjTuYY-GLX@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20180214
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VYzdkFGTLVPs4poH2tsRtN-CXA-IMKrWOGzTZVqEiJyGtSCaV-Q67@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20190306
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QpQtzTAAWfHFXaG7tLdMRN-CXA-EpTNfKogt5T8zjsbDTepVM-F69@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20200226
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:KYpmgbQRDsxZI75YVN4wDj-CXA-BaHSpLthz8ErxUrDVd7ojn-DMW@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20210217
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:RkyXBoM3LJO6sfACOrfDnF-CXA-FhjVo94fUjVwPEkdTa7KBX-Oos@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20220302
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:SCUVeOsXWEVgIdbSOhXWYD-CXA-JAHTsT4ANDK83p8CmYGCfD-EDN@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20230222
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GeMeAWdQY72qjWByi478Ag-CXA-BoC8F8iHM554oUXNFVue2V-BiV@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20240214
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HQqgVztVBViQGa2eNXYDja-CXA-JCJwm9PAcejyDVOFAMfVp5-IRR@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-DTSTART;VALUE=DATE:20250305
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Miy7AANQLLC8FxxyhJBuNL-CXA-B88UKY2jncqoWsjTZbLzMQ-FuS@rb.dd
-END:VEVENT
BEGIN:VEVENT
+UID:enrico-aut-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
SUMMARY:Neujahr
-DTSTART;VALUE=DATE:20100101
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OSRXW9SVNKj2pDswRdgbmP-DBA-BTAJGrLadZHZKMsFHagEWu-FRx@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=1;BYMONTH=1
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Hl. Drei Könige
-DTSTART;VALUE=DATE:20100106
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:LKorPA2Ka5fFoC9I6ZW5aW-DKA-EbzpQ5TMUMScJMPtVQNcN7-BLg@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=6;BYMONTH=1
+UID:enrico-aut-20180106@kayaposoft.com
+DTSTART;VALUE=DATE:20180106
+DTEND;VALUE=DATE:20180107
+SUMMARY:Heilige Drei Könige
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Staatsfeiertag
-DTSTART;VALUE=DATE:20100501
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:CYcMobU7GGxVjkzirh7uWQ-DIA-HSBQQSLDUosjSIZZRz6Uuw-IB2@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=1;BYMONTH=5
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20100402
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GbxTpLwMNMS5LFo4iyWNaV-EtA-BV4ZOgIiQbIKLgbAy7GE8s-PhM@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20110422
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OJ6QoG2K4Sdq4ikMMbKDc4-EtA-InjP4WMtHUruqdwOoRjiht-PVO@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20120406
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:cFm3SiSai5RnJJVoO3GKPA-EtA-Fx2FTgDREgRZC4HNiRie8V-BLz@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20130329
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:KWNxIPA33rwZzWxJp9VdP2-EtA-HYSB7BOatH5Uo9J5DHGfrz-GTI@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20140418
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:N6B6EhExBBIpfsaVqMDaNc-EtA-CoY9mYnGwQDFaawrTJRe5A-ETj@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20150403
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:F5XcxSyZYNaWt3mEEyAiIr-EtA-KhYEHgiBnWjFbVjCDJoRYy-NrV@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20160325
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OVLr36HoqWieBCMDttRw3P-EtA-GMT6sVNVMPZt4VjwLMO3PO-L3d@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20170414
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:K9gng8amEqpQFqHnJV8qX4-EtA-IkrVdgUeQ8GE9V4brFUctU-FXT@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20180330
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TQ8hKWFFFyyb4MWubbwLuC-EtA-ofHpnqPdBdHxGQg8txc5MA-LYr@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20190419
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FH42nTHAhN4ZG5K2GF2MaC-EtA-FOTtxda9gkRHX8XSqwzgWQ-Hig@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20200410
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NqYMztNK4dBAJX98Akp7H9-EtA-DR98PLqLoxGEUxIyFreLJ7-KSC@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20210402
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:K4bhtKdkDDTQKdnFLfQQgh-EtA-JzrcSx5VYnf6AInnpjTRWW-JOV@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20220415
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TwWdBxEemV6t5Po46uxde7-EtA-ICZJTpbYtYqSXnyWZ7ExZA-HHD@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20230407
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:BoKPorqpBEjL2AFqdVV6PA-EtA-JV5pToVYbnCiMgK46FDQoK-JeA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20240329
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:KR8fYbSVdLhcZDmCMoKVdG-EtA-CpbFmaR7yIDSWmztkPzyAc-OTn@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-DTSTART;VALUE=DATE:20250418
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:DGh4zpZpr9Krae9L2PNnOU-EtA-Ez87XAzOZdgFKNFupbf9US-B5W@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20100405
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NbStSYNZPXaVQCZWsb3Bi3-CNA-CrMTkpMIDPVhTjrfXVHiDD-QWM@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20110425
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:PYuHrwz8M9RIJOeQaLJVDL-CNA-BM8WIyjbwHRg3bPi2La4Sz-J7m@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20120409
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OGzwXEnRXy2CS8YU3tVfxq-CNA-HsNHw9t3IQoe27QXEL7RmA-RpJ@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20130401
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:P69FeaVZiPCQZUEZh3g4bE-CNA-Faa2TTP4ukMmFk3CSCmjci-PZA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20140421
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Gh8WHmW75ZBguyDJzdPPRd-CNA-GCVB6uSw5Zr4ae7BLfJTHb-Iff@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20150406
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:BBiOn3Rp2dM35XXNBEOz94-CNA-KzoJa6VgLUPxbrSfrSmcjT-SwU@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20160328
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:V63TxCOYgkEse5Tar6JqkI-CNA-E9LrrtAdtranixoHjgjxsT-jmA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20170417
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MBknpbVKwp9ethKAWjKHzf-CNA-BBwVoFAd4H8tOwLdZQIzeC-Rqt@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
+UID:enrico-aut-20180402@kayaposoft.com
DTSTART;VALUE=DATE:20180402
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GRsLk9rVtPdi9OiUsXxoV7-CNA-e92SCLx7IdbFp7iGhCH3VA-Jei@rb.dd
-END:VEVENT
-BEGIN:VEVENT
+DTEND;VALUE=DATE:20180403
SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20190422
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:InA92rhq2JKWHF3ksmLM4p-CNA-CTeLnkQtD7ak5bryPuumog-LnW@rb.dd
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20200413
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OKRJwiUADpmPjfuJfHUzdQ-CNA-rXmRrXF4EbCdiXXTHHVQsA-SHp@rb.dd
+UID:enrico-aut-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Staatsfeiertag
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20210405
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Uc7idNyBCqPfyLOXcjcUVz-CNA-HyReZaqcJuYft3u75b92Lk-Etx@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20220418
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TDWJhBFnrSGSXsO8XXe8je-CNA-3K6faaz6KFMeWPUEwS8EeA-QZD@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20230410
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:JY2ANrCjYsUHoIaUPDsNpH-CNA-FQwIbaHXwgbo77BJqjCTeg-Pqd@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20240401
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:LrRaE852mIIZXiQjCMZ8E2-CNA-C933fDqrqk5HOrJhVqxVf2-M3N@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-DTSTART;VALUE=DATE:20250421
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:RbwA3LkV6VssTq8m3wj9oT-CNA-BcwfbyxnCoWPu48ZgAku5h-2FA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20100513
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Uhw9Aug2ZoafPJ36EhHQfM-CVA-HwmiykchZVtksL5NmdHRXy-LMs@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20110602
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:GNKJmKxSUHpnVsVQkWwgxB-CVA-Bs7J4DpjaENaKd53RFNdPF-B9k@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20120517
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NVNPC5LaIo8tYkfKLEDeOw-CVA-DDWpDJpwJcQaH6VjHrVr88-Fj3@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20130509
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:ICHKIhVDSpTBUqq5GhDawS-CVA-Cdo3TfMDSUzFWtsfYQpVaj-C2J@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20140529
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:G9bezqDpAh9hRXQqcPDwaG-CVA-sY9O8UmpXazx6bfsaIcbFA-PYA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20150514
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:CAjrH7mwbm6DrHJJTVjSEA-CVA-GTOBoHCPOY87nTHBUyNpDM-LFX@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20160505
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:G3Mr3ocPDSOaVIL6mgKQ7W-CVA-DQ6DUgggZZ4i2gMNCh3Jmj-DaO@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20170525
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FMPjG7MAuCXPfS8Rcctn6x-CVA-Hf2mOQ44f6VVrEe8T4wkX6-QUf@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
+UID:enrico-aut-20180510@kayaposoft.com
DTSTART;VALUE=DATE:20180510
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FRA5PKifPt2qm5YsjaAYDG-CVA-3dCaVpYfmRVWBTBXHDwWZA-FRy@rb.dd
-END:VEVENT
-BEGIN:VEVENT
+DTEND;VALUE=DATE:20180511
SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20190530
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:CP38hQTVsjIDOtboBdJQSa-CVA-JSQkJpbwOZf58U7KD9xoKo-IKd@rb.dd
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20200521
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:LYATiipd3fo4LCMomP6kWD-CVA-JzjW4NG7WTH48ZyIbj2Vcq-OKA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20210513
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:NoWP3m86HMuycMpEcxgHeh-CVA-CctxstonyzDuaUwEdNpibs-Q7S@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20220526
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:JPmAMoz6EcuGPeExo9fAPG-CVA-GpAdqPqtHntHFaf8hLqMmc-I9Z@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20230518
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:pkTNu2FAig4wDQSWQow4LA-CVA-E25HtghJpefCwP8zP8aG5Q-SC3@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20240509
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:G8WmKzW4ArDGkoGxJ3NG2w-CVA-FSewqLOs6y7QG6Ie2u2nRO-L88@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Christi Himmelfahrt
-DTSTART;VALUE=DATE:20250529
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:UawajMXcpEfsbPkmh2G9Re-CVA-FymW5cjkTTDt5F9XNyQ2mq-NxC@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20100524
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:G7kXn2qSTSr6UpZ7o2WKib-CPA-FPAaG3DEMzRE6X8jNTe2Fg-EQg@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20110613
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:K6JAmLuFf5VH8m6xQspJII-CPA-Jg9pZ7gVdYI4U9jzRI4pYG-PpO@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20120528
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HbXCVNiWtTakMoEHjRBPIE-CPA-WQwrZqYzamx7w2wnkc5uuA-MS4@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20130520
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:SVMV6YRy3RIeXeLRJ6nUmF-CPA-D4JdwJhASAw6rzNJpKLApN-KSf@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20140609
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HAWFTikQYECi2LmkiUZHs7-CPA-JY9u6ewrWmiVgTdgm7ZjDN-M6c@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20150525
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QRUbEe2fXyhHKpRWYno5Bh-CPA-IbR2FxHLMURJcc8PjqPPRS-J77@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20160516
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:K3UMHIRemCQhLeURRJhUoz-CPA-HTZMYBszu7698TkFRkPiq8-DQq@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20170605
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:LNybWPZixYngFe6woc5DeO-CPA-FQp5VKh3J7mQVYIqc8dk9d-QH8@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
+UID:enrico-aut-20180521@kayaposoft.com
DTSTART;VALUE=DATE:20180521
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TZywC3cHoxBC43RxeDI6Sb-CPA-GKNheedIBPVIIYSffi8UGr-NwU@rb.dd
-END:VEVENT
-BEGIN:VEVENT
+DTEND;VALUE=DATE:20180522
SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20190610
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MNWPHs8Vmwpg6ALzrV6E2h-CPA-EPdVKrCUPCwOngNUWs7PDk-Lbh@rb.dd
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20200601
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:IERg8kjU5XTnVzHOqHXAzm-CPA-FcBDx4rb5R6y7zO5jVwwnK-M36@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20210524
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MqViMzjsc4s9EwbpLYcGd6-CPA-EyfNqaEMeWKpYiURXToNw5-CxT@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20220606
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FxFhtYSgIyE2bS2moyabUj-CPA-BD97JqaVDjsKgcpiuYrCeJ-LIU@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20230529
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HFfbZdUfMotebyhSo77E82-CPA-IkzXd3SS8NmhwBjZVaQ3nh-H5A@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20240520
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:FQx5IOnAxuqVrwLDM7refO-CPA-Gcrdon94ushFr3TmhMc8WH-Qrd@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-DTSTART;VALUE=DATE:20250609
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:T2mDVDBhR2EMjjcbbOWxV5-CPA-F32nGRTRmkB2VARCDCMOAD-GFf@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20100603
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:BEapfO68qTd8tRG4tc98rJ-COA-GAd3u3Cco5mK3wX7QeCIDO-OIA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20110623
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:IudIhG6hfsRgN9fMW4qjsV-COA-FBhCZHXb4xTTNGCeR9GZRU-Ew3@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20120607
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:CKVjPHcC8AztyJptQo9SF9-COA-GXVdnF5q8UnL2ZcOKYwr4R-Cwd@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20130530
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Qyjnk6TibJbIAIgLaFUwCx-COA-Is6JRhofDZOBkf48DLVPVp-HnD@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20140619
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:OV7UVGQ6MewMKrsVVxZXAe-COA-KFFbNYOIzqncm8w8RQeE3R-KHP@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20150604
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VJ3VcukP6bXyHT3yPyV2Mr-COA-C8qXRoPVsc92CYyhgcXUs6-dLA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20160526
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Ej4VtcL4tiNeaLSQnwzyJh-COA-DSyZhUEy85sGBFmK9IJn9F-DIV@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20170615
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:Nz2WQqAqceiRd3sCykYIAf-COA-Ii5bpLZySDrVgcRSF9s2Lj-Kgn@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
+UID:enrico-aut-20180531@kayaposoft.com
DTSTART;VALUE=DATE:20180531
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VRRISZiEUdIzcH4ggcVHSd-COA-GaywBieYspGc9Ui59oYSFd-MgG@rb.dd
-END:VEVENT
-BEGIN:VEVENT
+DTEND;VALUE=DATE:20180601
SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20190620
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:EP4MJcVnAVfW4dHV3GgZVh-COA-HALD5umVoxYT5I6dmGayrT-LUM@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20200611
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:VDOZJccpQmaTZo3y2HnEj2-COA-GFNAP5KaWOAZ2LHBybPHJn-OYA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20210603
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:B7ZetFdFybtreXnc69ZzZZ-COA-EfpQ5V3X6ceYt5I6yMSWQ7-aqA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20220616
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:TYmybgWpbCjgCWNenMkgOL-COA-IPUBbaAJO7icxWpIBZaFKd-ncA@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20230608
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:QL2AejgzuUxspUtJDIRK2Z-COA-CEaTAC9QcWNtnYN7rnVC7d-RMn@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20240530
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:RNprGIG8ZzhhOfzi9VhjIT-COA-KUzSccNVf2cVLNSx9O8dVq-H9V@rb.dd
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-DTSTART;VALUE=DATE:20250619
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MVRVwAkbq7YC9tVBxiEnRj-COA-wqTsUxTGD7fpVynqPcmXiA-KTE@rb.dd
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
+UID:enrico-aut-20180815@kayaposoft.com
+DTSTART;VALUE=DATE:20180815
+DTEND;VALUE=DATE:20180816
SUMMARY:Mariä Himmelfahrt
-DTSTART;VALUE=DATE:20100815
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:MbQLH8OkkfgjXOBJeVUd7B-DtA-GGBwJkHsKDy5ToiXAshDay-KXV@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=15;BYMONTH=8
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
+UID:enrico-aut-20181026@kayaposoft.com
+DTSTART;VALUE=DATE:20181026
+DTEND;VALUE=DATE:20181027
SUMMARY:Nationalfeiertag
-DTSTART;VALUE=DATE:20101026
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:HURkGE2RKAUuD6atwnqad2-DMA-JICpFIapSYGtVrh4CYf6Ic-BWs@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=26;BYMONTH=10
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
+UID:enrico-aut-20181101@kayaposoft.com
+DTSTART;VALUE=DATE:20181101
+DTEND;VALUE=DATE:20181102
SUMMARY:Allerheiligen
-DTSTART;VALUE=DATE:20101101
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:KKRXXqrBZL5kRrGDjgKy6z-DIA-F4TG6nJGRRPbanOrRF2rfn-EDt@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=1;BYMONTH=11
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
+UID:enrico-aut-20181208@kayaposoft.com
+DTSTART;VALUE=DATE:20181208
+DTEND;VALUE=DATE:20181209
SUMMARY:Mariä Empfängnis
-DTSTART;VALUE=DATE:20101208
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:B92t4hRJuYH8RuNT3oCYxK-DNA-CF5VeYWE3Dw3CLDQBVJGGc-Ju2@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=8;BYMONTH=12
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Christtag
-DTSTART;VALUE=DATE:20101225
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:CKCIWWNFYJEBR5hpszYZ9H-DFA-Ic3aEMVWJyV87VdbpEHgmS-JGU@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=25;BYMONTH=12
+UID:enrico-aut-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Weihnachten
+STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Stefanitag
-DTSTART;VALUE=DATE:20101226
-DURATION:P1D
-DTSTAMP:20151111T111100Z
-UID:H4E5xq88HuLp4tnHGqAw5y-DGA-EkNYwVGmVeJ4aRZKhM9eh2-PK6@rb.dd
-RRULE:FREQ=YEARLY;COUNT=16;BYMONTHDAY=26;BYMONTH=12
+UID:enrico-aut-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Stephanstag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Neujahr
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190106@kayaposoft.com
+DTSTART;VALUE=DATE:20190106
+DTEND;VALUE=DATE:20190107
+SUMMARY:Heilige Drei Könige
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:Ostermontag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Staatsfeiertag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190530@kayaposoft.com
+DTSTART;VALUE=DATE:20190530
+DTEND;VALUE=DATE:20190531
+SUMMARY:Christi Himmelfahrt
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190610@kayaposoft.com
+DTSTART;VALUE=DATE:20190610
+DTEND;VALUE=DATE:20190611
+SUMMARY:Pfingstmontag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190620@kayaposoft.com
+DTSTART;VALUE=DATE:20190620
+DTEND;VALUE=DATE:20190621
+SUMMARY:Fronleichnam
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20190815@kayaposoft.com
+DTSTART;VALUE=DATE:20190815
+DTEND;VALUE=DATE:20190816
+SUMMARY:Mariä Himmelfahrt
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20191026@kayaposoft.com
+DTSTART;VALUE=DATE:20191026
+DTEND;VALUE=DATE:20191027
+SUMMARY:Nationalfeiertag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20191101@kayaposoft.com
+DTSTART;VALUE=DATE:20191101
+DTEND;VALUE=DATE:20191102
+SUMMARY:Allerheiligen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20191208@kayaposoft.com
+DTSTART;VALUE=DATE:20191208
+DTEND;VALUE=DATE:20191209
+SUMMARY:Mariä Empfängnis
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Weihnachten
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Stephanstag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Neujahr
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200106@kayaposoft.com
+DTSTART;VALUE=DATE:20200106
+DTEND;VALUE=DATE:20200107
+SUMMARY:Heilige Drei Könige
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Ostermontag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Staatsfeiertag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200521@kayaposoft.com
+DTSTART;VALUE=DATE:20200521
+DTEND;VALUE=DATE:20200522
+SUMMARY:Christi Himmelfahrt
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200601@kayaposoft.com
+DTSTART;VALUE=DATE:20200601
+DTEND;VALUE=DATE:20200602
+SUMMARY:Pfingstmontag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200611@kayaposoft.com
+DTSTART;VALUE=DATE:20200611
+DTEND;VALUE=DATE:20200612
+SUMMARY:Fronleichnam
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20200815@kayaposoft.com
+DTSTART;VALUE=DATE:20200815
+DTEND;VALUE=DATE:20200816
+SUMMARY:Mariä Himmelfahrt
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20201026@kayaposoft.com
+DTSTART;VALUE=DATE:20201026
+DTEND;VALUE=DATE:20201027
+SUMMARY:Nationalfeiertag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20201101@kayaposoft.com
+DTSTART;VALUE=DATE:20201101
+DTEND;VALUE=DATE:20201102
+SUMMARY:Allerheiligen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20201208@kayaposoft.com
+DTSTART;VALUE=DATE:20201208
+DTEND;VALUE=DATE:20201209
+SUMMARY:Mariä Empfängnis
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Weihnachten
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-aut-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:Stephanstag
+STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/assets/china.ics b/app/src/main/assets/china.ics
new file mode 100644
index 000000000..b726e5021
--- /dev/null
+++ b/app/src/main/assets/china.ics
@@ -0,0 +1,149 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-chn-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:元旦
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20180215@kayaposoft.com
+DTSTART;VALUE=DATE:20180215
+DTEND;VALUE=DATE:20180222
+SUMMARY:春节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20180405@kayaposoft.com
+DTSTART;VALUE=DATE:20180405
+DTEND;VALUE=DATE:20180408
+SUMMARY:清明节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20180429@kayaposoft.com
+DTSTART;VALUE=DATE:20180429
+DTEND;VALUE=DATE:20180502
+SUMMARY:劳动节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20180616@kayaposoft.com
+DTSTART;VALUE=DATE:20180616
+DTEND;VALUE=DATE:20180619
+SUMMARY:端午节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20180922@kayaposoft.com
+DTSTART;VALUE=DATE:20180922
+DTEND;VALUE=DATE:20180925
+SUMMARY:中秋节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20181001@kayaposoft.com
+DTSTART;VALUE=DATE:20181001
+DTEND;VALUE=DATE:20181008
+SUMMARY:国庆节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:元旦
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20190205@kayaposoft.com
+DTSTART;VALUE=DATE:20190205
+DTEND;VALUE=DATE:20190206
+SUMMARY:春节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20190405@kayaposoft.com
+DTSTART;VALUE=DATE:20190405
+DTEND;VALUE=DATE:20190406
+SUMMARY:清明节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:劳动节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20190607@kayaposoft.com
+DTSTART;VALUE=DATE:20190607
+DTEND;VALUE=DATE:20190608
+SUMMARY:端午节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20190913@kayaposoft.com
+DTSTART;VALUE=DATE:20190913
+DTEND;VALUE=DATE:20190914
+SUMMARY:中秋节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20191001@kayaposoft.com
+DTSTART;VALUE=DATE:20191001
+DTEND;VALUE=DATE:20191002
+SUMMARY:国庆节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:元旦
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20200125@kayaposoft.com
+DTSTART;VALUE=DATE:20200125
+DTEND;VALUE=DATE:20200126
+SUMMARY:春节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20200404@kayaposoft.com
+DTSTART;VALUE=DATE:20200404
+DTEND;VALUE=DATE:20200405
+SUMMARY:清明节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:劳动节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20200625@kayaposoft.com
+DTSTART;VALUE=DATE:20200625
+DTEND;VALUE=DATE:20200626
+SUMMARY:端午节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20201001@kayaposoft.com
+DTSTART;VALUE=DATE:20201001
+DTEND;VALUE=DATE:20201002
+SUMMARY:国庆节
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-chn-20201001-529562@kayaposoft.com
+DTSTART;VALUE=DATE:20201001
+DTEND;VALUE=DATE:20201002
+SUMMARY:中秋节
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/colombia.ics b/app/src/main/assets/colombia.ics
new file mode 100644
index 000000000..81b232bd0
--- /dev/null
+++ b/app/src/main/assets/colombia.ics
@@ -0,0 +1,422 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-col-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Año Nuevo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180108@kayaposoft.com
+DTSTART;VALUE=DATE:20180108
+DTEND;VALUE=DATE:20180109
+SUMMARY:Día de los Reyes Magos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180319@kayaposoft.com
+DTSTART;VALUE=DATE:20180319
+DTEND;VALUE=DATE:20180320
+SUMMARY:Día de San José
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180325@kayaposoft.com
+DTSTART;VALUE=DATE:20180325
+DTEND;VALUE=DATE:20180326
+SUMMARY:Domingo de Ramos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180329@kayaposoft.com
+DTSTART;VALUE=DATE:20180329
+DTEND;VALUE=DATE:20180330
+SUMMARY:Jueves Santo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180330@kayaposoft.com
+DTSTART;VALUE=DATE:20180330
+DTEND;VALUE=DATE:20180331
+SUMMARY:Viernes Santo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180401@kayaposoft.com
+DTSTART;VALUE=DATE:20180401
+DTEND;VALUE=DATE:20180402
+SUMMARY:Domingo de Pascuas o Resurrección
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Día del Trabajo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180514@kayaposoft.com
+DTSTART;VALUE=DATE:20180514
+DTEND;VALUE=DATE:20180515
+SUMMARY:Día de la Ascensión
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180604@kayaposoft.com
+DTSTART;VALUE=DATE:20180604
+DTEND;VALUE=DATE:20180605
+SUMMARY:Corpus Christi
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180611@kayaposoft.com
+DTSTART;VALUE=DATE:20180611
+DTEND;VALUE=DATE:20180612
+SUMMARY:Sagrado Corazón de Jesús
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180702@kayaposoft.com
+DTSTART;VALUE=DATE:20180702
+DTEND;VALUE=DATE:20180703
+SUMMARY:San Pedro y San Pablo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180720@kayaposoft.com
+DTSTART;VALUE=DATE:20180720
+DTEND;VALUE=DATE:20180721
+SUMMARY:Declaracion de la Independencia de Colombia
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180807@kayaposoft.com
+DTSTART;VALUE=DATE:20180807
+DTEND;VALUE=DATE:20180808
+SUMMARY:Batalla de Boyacá
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20180820@kayaposoft.com
+DTSTART;VALUE=DATE:20180820
+DTEND;VALUE=DATE:20180821
+SUMMARY:Asunción de la Virgen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20181015@kayaposoft.com
+DTSTART;VALUE=DATE:20181015
+DTEND;VALUE=DATE:20181016
+SUMMARY:Día de la Raza
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20181105@kayaposoft.com
+DTSTART;VALUE=DATE:20181105
+DTEND;VALUE=DATE:20181106
+SUMMARY:Día de todos los Santos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20181112@kayaposoft.com
+DTSTART;VALUE=DATE:20181112
+DTEND;VALUE=DATE:20181113
+SUMMARY:Independencia de Cartagena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20181208@kayaposoft.com
+DTSTART;VALUE=DATE:20181208
+DTEND;VALUE=DATE:20181209
+SUMMARY:Día de la Inmaculada Concepción
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Navidad
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Año Nuevo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190107@kayaposoft.com
+DTSTART;VALUE=DATE:20190107
+DTEND;VALUE=DATE:20190108
+SUMMARY:Día de los Reyes Magos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190325@kayaposoft.com
+DTSTART;VALUE=DATE:20190325
+DTEND;VALUE=DATE:20190326
+SUMMARY:Día de San José
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190414@kayaposoft.com
+DTSTART;VALUE=DATE:20190414
+DTEND;VALUE=DATE:20190415
+SUMMARY:Domingo de Ramos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190418@kayaposoft.com
+DTSTART;VALUE=DATE:20190418
+DTEND;VALUE=DATE:20190419
+SUMMARY:Jueves Santo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190419@kayaposoft.com
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+SUMMARY:Viernes Santo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190421@kayaposoft.com
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+SUMMARY:Domingo de Pascuas o Resurrección
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Día del Trabajo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190603@kayaposoft.com
+DTSTART;VALUE=DATE:20190603
+DTEND;VALUE=DATE:20190604
+SUMMARY:Día de la Ascensión
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190624@kayaposoft.com
+DTSTART;VALUE=DATE:20190624
+DTEND;VALUE=DATE:20190625
+SUMMARY:Corpus Christi
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190701@kayaposoft.com
+DTSTART;VALUE=DATE:20190701
+DTEND;VALUE=DATE:20190702
+SUMMARY:Sagrado Corazón de Jesús
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190701-289927@kayaposoft.com
+DTSTART;VALUE=DATE:20190701
+DTEND;VALUE=DATE:20190702
+SUMMARY:San Pedro y San Pablo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190720@kayaposoft.com
+DTSTART;VALUE=DATE:20190720
+DTEND;VALUE=DATE:20190721
+SUMMARY:Declaracion de la Independencia de Colombia
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190807@kayaposoft.com
+DTSTART;VALUE=DATE:20190807
+DTEND;VALUE=DATE:20190808
+SUMMARY:Batalla de Boyacá
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20190819@kayaposoft.com
+DTSTART;VALUE=DATE:20190819
+DTEND;VALUE=DATE:20190820
+SUMMARY:Asunción de la Virgen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20191014@kayaposoft.com
+DTSTART;VALUE=DATE:20191014
+DTEND;VALUE=DATE:20191015
+SUMMARY:Día de la Raza
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20191104@kayaposoft.com
+DTSTART;VALUE=DATE:20191104
+DTEND;VALUE=DATE:20191105
+SUMMARY:Día de todos los Santos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20191111@kayaposoft.com
+DTSTART;VALUE=DATE:20191111
+DTEND;VALUE=DATE:20191112
+SUMMARY:Independencia de Cartagena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20191208@kayaposoft.com
+DTSTART;VALUE=DATE:20191208
+DTEND;VALUE=DATE:20191209
+SUMMARY:Día de la Inmaculada Concepción
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Navidad
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Año Nuevo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200106@kayaposoft.com
+DTSTART;VALUE=DATE:20200106
+DTEND;VALUE=DATE:20200107
+SUMMARY:Día de los Reyes Magos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200323@kayaposoft.com
+DTSTART;VALUE=DATE:20200323
+DTEND;VALUE=DATE:20200324
+SUMMARY:Día de San José
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200405@kayaposoft.com
+DTSTART;VALUE=DATE:20200405
+DTEND;VALUE=DATE:20200406
+SUMMARY:Domingo de Ramos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200409@kayaposoft.com
+DTSTART;VALUE=DATE:20200409
+DTEND;VALUE=DATE:20200410
+SUMMARY:Jueves Santo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200410@kayaposoft.com
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+SUMMARY:Viernes Santo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200412@kayaposoft.com
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+SUMMARY:Domingo de Pascuas o Resurrección
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Día del Trabajo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200525@kayaposoft.com
+DTSTART;VALUE=DATE:20200525
+DTEND;VALUE=DATE:20200526
+SUMMARY:Día de la Ascensión
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200615@kayaposoft.com
+DTSTART;VALUE=DATE:20200615
+DTEND;VALUE=DATE:20200616
+SUMMARY:Corpus Christi
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200622@kayaposoft.com
+DTSTART;VALUE=DATE:20200622
+DTEND;VALUE=DATE:20200623
+SUMMARY:Sagrado Corazón de Jesús
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200629@kayaposoft.com
+DTSTART;VALUE=DATE:20200629
+DTEND;VALUE=DATE:20200630
+SUMMARY:San Pedro y San Pablo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200720@kayaposoft.com
+DTSTART;VALUE=DATE:20200720
+DTEND;VALUE=DATE:20200721
+SUMMARY:Declaracion de la Independencia de Colombia
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200807@kayaposoft.com
+DTSTART;VALUE=DATE:20200807
+DTEND;VALUE=DATE:20200808
+SUMMARY:Batalla de Boyacá
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20200817@kayaposoft.com
+DTSTART;VALUE=DATE:20200817
+DTEND;VALUE=DATE:20200818
+SUMMARY:Asunción de la Virgen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20201012@kayaposoft.com
+DTSTART;VALUE=DATE:20201012
+DTEND;VALUE=DATE:20201013
+SUMMARY:Día de la Raza
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20201102@kayaposoft.com
+DTSTART;VALUE=DATE:20201102
+DTEND;VALUE=DATE:20201103
+SUMMARY:Día de todos los Santos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20201116@kayaposoft.com
+DTSTART;VALUE=DATE:20201116
+DTEND;VALUE=DATE:20201117
+SUMMARY:Independencia de Cartagena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20201208@kayaposoft.com
+DTSTART;VALUE=DATE:20201208
+DTEND;VALUE=DATE:20201209
+SUMMARY:Día de la Inmaculada Concepción
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-col-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Navidad
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/croatia.ics b/app/src/main/assets/croatia.ics
new file mode 100644
index 000000000..9fd155e13
--- /dev/null
+++ b/app/src/main/assets/croatia.ics
@@ -0,0 +1,296 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-hrv-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180106@kayaposoft.com
+DTSTART;VALUE=DATE:20180106
+DTEND;VALUE=DATE:20180107
+SUMMARY:Bogojavljanje ili Sveta tri kralja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180401@kayaposoft.com
+DTSTART;VALUE=DATE:20180401
+DTEND;VALUE=DATE:20180402
+SUMMARY:Uskrs
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180402@kayaposoft.com
+DTSTART;VALUE=DATE:20180402
+DTEND;VALUE=DATE:20180403
+SUMMARY:Uskrsni ponedjeljak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180531@kayaposoft.com
+DTSTART;VALUE=DATE:20180531
+DTEND;VALUE=DATE:20180601
+SUMMARY:Tijelovo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180622@kayaposoft.com
+DTSTART;VALUE=DATE:20180622
+DTEND;VALUE=DATE:20180623
+SUMMARY:Dan antifašističke borbe
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180625@kayaposoft.com
+DTSTART;VALUE=DATE:20180625
+DTEND;VALUE=DATE:20180626
+SUMMARY:Dan državnosti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180805@kayaposoft.com
+DTSTART;VALUE=DATE:20180805
+DTEND;VALUE=DATE:20180806
+SUMMARY:Dan pobjede i domovinske zahvalnosti i Dan hrvatskih branitelja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20180815@kayaposoft.com
+DTSTART;VALUE=DATE:20180815
+DTEND;VALUE=DATE:20180816
+SUMMARY:Velika Gospa
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20181008@kayaposoft.com
+DTSTART;VALUE=DATE:20181008
+DTEND;VALUE=DATE:20181009
+SUMMARY:Dan neovisnosti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20181101@kayaposoft.com
+DTSTART;VALUE=DATE:20181101
+DTEND;VALUE=DATE:20181102
+SUMMARY:Svi sveti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Božić
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Prvi dan po Božiću\, Sveti Stjepan
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190106@kayaposoft.com
+DTSTART;VALUE=DATE:20190106
+DTEND;VALUE=DATE:20190107
+SUMMARY:Bogojavljanje ili Sveta tri kralja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190421@kayaposoft.com
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+SUMMARY:Uskrs
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:Uskrsni ponedjeljak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190620@kayaposoft.com
+DTSTART;VALUE=DATE:20190620
+DTEND;VALUE=DATE:20190621
+SUMMARY:Tijelovo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190622@kayaposoft.com
+DTSTART;VALUE=DATE:20190622
+DTEND;VALUE=DATE:20190623
+SUMMARY:Dan antifašističke borbe
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190625@kayaposoft.com
+DTSTART;VALUE=DATE:20190625
+DTEND;VALUE=DATE:20190626
+SUMMARY:Dan državnosti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190805@kayaposoft.com
+DTSTART;VALUE=DATE:20190805
+DTEND;VALUE=DATE:20190806
+SUMMARY:Dan pobjede i domovinske zahvalnosti i Dan hrvatskih branitelja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20190815@kayaposoft.com
+DTSTART;VALUE=DATE:20190815
+DTEND;VALUE=DATE:20190816
+SUMMARY:Velika Gospa
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20191008@kayaposoft.com
+DTSTART;VALUE=DATE:20191008
+DTEND;VALUE=DATE:20191009
+SUMMARY:Dan neovisnosti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20191101@kayaposoft.com
+DTSTART;VALUE=DATE:20191101
+DTEND;VALUE=DATE:20191102
+SUMMARY:Svi sveti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Božić
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Prvi dan po Božiću\, Sveti Stjepan
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200106@kayaposoft.com
+DTSTART;VALUE=DATE:20200106
+DTEND;VALUE=DATE:20200107
+SUMMARY:Bogojavljanje ili Sveta tri kralja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200412@kayaposoft.com
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+SUMMARY:Uskrs
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Uskrsni ponedjeljak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200611@kayaposoft.com
+DTSTART;VALUE=DATE:20200611
+DTEND;VALUE=DATE:20200612
+SUMMARY:Tijelovo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200622@kayaposoft.com
+DTSTART;VALUE=DATE:20200622
+DTEND;VALUE=DATE:20200623
+SUMMARY:Dan antifašističke borbe
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200625@kayaposoft.com
+DTSTART;VALUE=DATE:20200625
+DTEND;VALUE=DATE:20200626
+SUMMARY:Dan državnosti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200805@kayaposoft.com
+DTSTART;VALUE=DATE:20200805
+DTEND;VALUE=DATE:20200806
+SUMMARY:Dan pobjede i domovinske zahvalnosti i Dan hrvatskih branitelja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20200815@kayaposoft.com
+DTSTART;VALUE=DATE:20200815
+DTEND;VALUE=DATE:20200816
+SUMMARY:Velika Gospa
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20201008@kayaposoft.com
+DTSTART;VALUE=DATE:20201008
+DTEND;VALUE=DATE:20201009
+SUMMARY:Dan neovisnosti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20201101@kayaposoft.com
+DTSTART;VALUE=DATE:20201101
+DTEND;VALUE=DATE:20201102
+SUMMARY:Svi sveti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Božić
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-hrv-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:Prvi dan po Božiću\, Sveti Stjepan
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/denmark.ics b/app/src/main/assets/denmark.ics
new file mode 100644
index 000000000..1b7ccc624
--- /dev/null
+++ b/app/src/main/assets/denmark.ics
@@ -0,0 +1,254 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-dnk-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Nytårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180325@kayaposoft.com
+DTSTART;VALUE=DATE:20180325
+DTEND;VALUE=DATE:20180326
+SUMMARY:Palmesøndag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180329@kayaposoft.com
+DTSTART;VALUE=DATE:20180329
+DTEND;VALUE=DATE:20180330
+SUMMARY:Skærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180330@kayaposoft.com
+DTSTART;VALUE=DATE:20180330
+DTEND;VALUE=DATE:20180331
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180401@kayaposoft.com
+DTSTART;VALUE=DATE:20180401
+DTEND;VALUE=DATE:20180402
+SUMMARY:1. Påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180402@kayaposoft.com
+DTSTART;VALUE=DATE:20180402
+DTEND;VALUE=DATE:20180403
+SUMMARY:2. Påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180427@kayaposoft.com
+DTSTART;VALUE=DATE:20180427
+DTEND;VALUE=DATE:20180428
+SUMMARY:Store Bededag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180510@kayaposoft.com
+DTSTART;VALUE=DATE:20180510
+DTEND;VALUE=DATE:20180511
+SUMMARY:Kristi Himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180520@kayaposoft.com
+DTSTART;VALUE=DATE:20180520
+DTEND;VALUE=DATE:20180521
+SUMMARY:1. Pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20180521@kayaposoft.com
+DTSTART;VALUE=DATE:20180521
+DTEND;VALUE=DATE:20180522
+SUMMARY:2. Pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:1. Juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:2. Juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Nytårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190414@kayaposoft.com
+DTSTART;VALUE=DATE:20190414
+DTEND;VALUE=DATE:20190415
+SUMMARY:Palmesøndag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190418@kayaposoft.com
+DTSTART;VALUE=DATE:20190418
+DTEND;VALUE=DATE:20190419
+SUMMARY:Skærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190419@kayaposoft.com
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190421@kayaposoft.com
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+SUMMARY:1. Påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:2. Påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190517@kayaposoft.com
+DTSTART;VALUE=DATE:20190517
+DTEND;VALUE=DATE:20190518
+SUMMARY:Store Bededag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190530@kayaposoft.com
+DTSTART;VALUE=DATE:20190530
+DTEND;VALUE=DATE:20190531
+SUMMARY:Kristi Himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190609@kayaposoft.com
+DTSTART;VALUE=DATE:20190609
+DTEND;VALUE=DATE:20190610
+SUMMARY:1. Pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20190610@kayaposoft.com
+DTSTART;VALUE=DATE:20190610
+DTEND;VALUE=DATE:20190611
+SUMMARY:2. Pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:1. Juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:2. Juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Nytårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200405@kayaposoft.com
+DTSTART;VALUE=DATE:20200405
+DTEND;VALUE=DATE:20200406
+SUMMARY:Palmesøndag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200409@kayaposoft.com
+DTSTART;VALUE=DATE:20200409
+DTEND;VALUE=DATE:20200410
+SUMMARY:Skærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200410@kayaposoft.com
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200412@kayaposoft.com
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+SUMMARY:1. Påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:2. Påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200508@kayaposoft.com
+DTSTART;VALUE=DATE:20200508
+DTEND;VALUE=DATE:20200509
+SUMMARY:Store Bededag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200521@kayaposoft.com
+DTSTART;VALUE=DATE:20200521
+DTEND;VALUE=DATE:20200522
+SUMMARY:Kristi Himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200531@kayaposoft.com
+DTSTART;VALUE=DATE:20200531
+DTEND;VALUE=DATE:20200601
+SUMMARY:1. Pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20200601@kayaposoft.com
+DTSTART;VALUE=DATE:20200601
+DTEND;VALUE=DATE:20200602
+SUMMARY:2. Pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:1. Juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-dnk-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:2. Juledag
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/estonia.ics b/app/src/main/assets/estonia.ics
index 211dbc295..ffe6ebb2e 100755
--- a/app/src/main/assets/estonia.ics
+++ b/app/src/main/assets/estonia.ics
@@ -1,219 +1,254 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
-SUMMARY:Leheristipäev
-UID:87aea25e-f592-47eb-b603-81640c074097
-DTSTART;VALUE=DATE:20170518
-DTEND;VALUE=DATE:20170519
+UID:enrico-est-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:uusaasta
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Taevaminemispüha
-UID:6e5e0af9-0230-427b-97f3-e799ddad9294
-DTSTART;VALUE=DATE:20170525
-DTEND;VALUE=DATE:20170526
+UID:enrico-est-20180224@kayaposoft.com
+DTSTART;VALUE=DATE:20180224
+DTEND;VALUE=DATE:20180225
+SUMMARY:iseseisvuspäev
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:1. nelipüha
-UID:496869e4-af27-425b-ac0d-67c6dcb01adc
-DTSTART;VALUE=DATE:20170604
-DTEND;VALUE=DATE:20170605
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:2. nelipüha
-UID:b70ead8d-fad3-484a-bf67-5a52632c579d
-DTSTART;VALUE=DATE:20170605
-DTEND;VALUE=DATE:20170606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Suve algus kell 07:24
-UID:328ecee3-576a-44e8-9c1e-2f3a5e357f83
-DTSTART;VALUE=DATE:20170621
-DTEND;VALUE=DATE:20170622
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sügise algus kell 23:02
-UID:afc4d254-2139-497a-a8d8-38df0bbaea0d
-DTSTART;VALUE=DATE:20170922
-DTEND;VALUE=DATE:20170923
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Kohaliku omavalitsuse volikogu valimised
-UID:8da59f54-176f-446e-a270-c111ea3e18dd
-DTSTART;VALUE=DATE:20171015
-DTEND;VALUE=DATE:20171016
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:1. advent
-UID:47dc3b6a-d95b-44b5-bfc7-1b3a35f7205d
-DTSTART;VALUE=DATE:20171203
-DTEND;VALUE=DATE:20171204
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:2. advent
-UID:4864b353-09b9-4efa-83d0-a972392b84bb
-DTSTART;VALUE=DATE:20171210
-DTEND;VALUE=DATE:20171211
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:3. advent
-UID:329a9209-d449-4bbf-a34c-ad1d5a5c614b
-DTSTART;VALUE=DATE:20171217
-DTEND;VALUE=DATE:20171218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Talve algus kell 18:28
-UID:8f789507-7ebb-43a0-98c3-7d43e02383ab
-DTSTART;VALUE=DATE:20171221
-DTEND;VALUE=DATE:20171222
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:4. advent
-UID:b2be7f14-42d6-4315-ae41-a8e70cc0389d
-DTSTART;VALUE=DATE:20171224
-DTEND;VALUE=DATE:20171225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vastlapäev
-UID:a95dc427-9bc3-42c2-bf73-e28475488119
-DTSTART;VALUE=DATE:20180213
-DTEND;VALUE=DATE:20180214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tuhkapäev
-UID:f154d202-d199-47d7-8c9b-2f7e9b6ee566
-DTSTART;VALUE=DATE:20180214
-DTEND;VALUE=DATE:20180215
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Kevade algus kell 18:15
-UID:bf16eef0-f725-4bad-8f08-782b799d3c23
-DTSTART;VALUE=DATE:20180320
-DTEND;VALUE=DATE:20180321
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Suur reede
-UID:c6622440-ffcb-4ac1-b74e-9585bbd4201f
+UID:enrico-est-20180330@kayaposoft.com
DTSTART;VALUE=DATE:20180330
DTEND;VALUE=DATE:20180331
+SUMMARY:suur reede
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:1. ülestõusmispüha
-UID:e2b4326b-a759-4139-983c-010e11c00ed6
+UID:enrico-est-20180401@kayaposoft.com
DTSTART;VALUE=DATE:20180401
DTEND;VALUE=DATE:20180402
+SUMMARY:ülestõusmispühade 1. püha
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. ülestõusmispüha
-UID:7953c329-b375-453d-aa22-3178ab4c8635
-DTSTART;VALUE=DATE:20180402
-DTEND;VALUE=DATE:20180403
+UID:enrico-est-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:kevadpüha
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Tuuleristipäev
-UID:3bcd442c-cfb7-4ce2-b094-5c8faa2eb6e3
-DTSTART;VALUE=DATE:20180419
-DTEND;VALUE=DATE:20180420
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Linnuristipäev
-UID:ed690184-a077-404a-9898-526fc30c0edd
-DTSTART;VALUE=DATE:20180426
-DTEND;VALUE=DATE:20180427
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Leheristipäev
-UID:3ee1e9ca-4a0f-4b50-95e2-9d6f0df602b4
-DTSTART;VALUE=DATE:20180503
-DTEND;VALUE=DATE:20180504
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Taevaminemispüha
-UID:77e33601-6e4a-451d-8f05-f0e32aae10e8
-DTSTART;VALUE=DATE:20180510
-DTEND;VALUE=DATE:20180511
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:1. nelipüha
-UID:b1ec19e5-7aad-4c68-9b57-afb2fdc443d7
+UID:enrico-est-20180520@kayaposoft.com
DTSTART;VALUE=DATE:20180520
DTEND;VALUE=DATE:20180521
+SUMMARY:nelipühade 1. püha
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. nelipüha
-UID:fbd7814f-0b3b-4916-94de-3c20d7a32fae
-DTSTART;VALUE=DATE:20180521
-DTEND;VALUE=DATE:20180522
+UID:enrico-est-20180623@kayaposoft.com
+DTSTART;VALUE=DATE:20180623
+DTEND;VALUE=DATE:20180624
+SUMMARY:võidupüha
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Suve algus kell 13:07
-UID:2c96272e-c227-46ba-8219-54e5ae360ffd
-DTSTART;VALUE=DATE:20180621
-DTEND;VALUE=DATE:20180622
+UID:enrico-est-20180624@kayaposoft.com
+DTSTART;VALUE=DATE:20180624
+DTEND;VALUE=DATE:20180625
+SUMMARY:jaanipäev
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Sügise algus kell 04:54
-UID:5f22d789-9b4b-4aeb-9691-f813e2a01aa8
-DTSTART;VALUE=DATE:20180923
-DTEND;VALUE=DATE:20180924
+UID:enrico-est-20180820@kayaposoft.com
+DTSTART;VALUE=DATE:20180820
+DTEND;VALUE=DATE:20180821
+SUMMARY:taasiseseisvumispäev
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:1. advent
-UID:ec10f2a1-aa5f-44d3-afe1-a6d1cf3e5a9e
-DTSTART;VALUE=DATE:20181202
-DTEND;VALUE=DATE:20181203
+UID:enrico-est-20181224@kayaposoft.com
+DTSTART;VALUE=DATE:20181224
+DTEND;VALUE=DATE:20181225
+SUMMARY:jõululaupäev
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. advent
-UID:c29c4c4a-6905-4864-9bb9-ebf6c1ed394e
-DTSTART;VALUE=DATE:20181209
-DTEND;VALUE=DATE:20181210
+UID:enrico-est-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:esimene jõulupüha
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:3. advent
-UID:fc82eb9d-15b2-4182-9e7c-30096371decc
-DTSTART;VALUE=DATE:20181216
-DTEND;VALUE=DATE:20181217
+UID:enrico-est-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:teine jõulupüha
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Talve algus kell 00:23
-UID:89ae6fc6-699f-4189-80db-c118f2aca9c4
-DTSTART;VALUE=DATE:20181222
-DTEND;VALUE=DATE:20181223
+UID:enrico-est-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:uusaasta
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:4. advent
-UID:6b3cd6d7-77ab-4d12-97c8-92fbe78264a8
-DTSTART;VALUE=DATE:20181223
-DTEND;VALUE=DATE:20181224
+UID:enrico-est-20190224@kayaposoft.com
+DTSTART;VALUE=DATE:20190224
+DTEND;VALUE=DATE:20190225
+SUMMARY:iseseisvuspäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20190419@kayaposoft.com
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+SUMMARY:suur reede
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20190421@kayaposoft.com
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+SUMMARY:ülestõusmispühade 1. püha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:kevadpüha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20190609@kayaposoft.com
+DTSTART;VALUE=DATE:20190609
+DTEND;VALUE=DATE:20190610
+SUMMARY:nelipühade 1. püha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20190623@kayaposoft.com
+DTSTART;VALUE=DATE:20190623
+DTEND;VALUE=DATE:20190624
+SUMMARY:võidupüha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20190624@kayaposoft.com
+DTSTART;VALUE=DATE:20190624
+DTEND;VALUE=DATE:20190625
+SUMMARY:jaanipäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20190820@kayaposoft.com
+DTSTART;VALUE=DATE:20190820
+DTEND;VALUE=DATE:20190821
+SUMMARY:taasiseseisvumispäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20191224@kayaposoft.com
+DTSTART;VALUE=DATE:20191224
+DTEND;VALUE=DATE:20191225
+SUMMARY:jõululaupäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:esimene jõulupüha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:teine jõulupüha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:uusaasta
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200224@kayaposoft.com
+DTSTART;VALUE=DATE:20200224
+DTEND;VALUE=DATE:20200225
+SUMMARY:iseseisvuspäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200410@kayaposoft.com
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+SUMMARY:suur reede
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200412@kayaposoft.com
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+SUMMARY:ülestõusmispühade 1. püha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:kevadpüha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200531@kayaposoft.com
+DTSTART;VALUE=DATE:20200531
+DTEND;VALUE=DATE:20200601
+SUMMARY:nelipühade 1. püha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200623@kayaposoft.com
+DTSTART;VALUE=DATE:20200623
+DTEND;VALUE=DATE:20200624
+SUMMARY:võidupüha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200624@kayaposoft.com
+DTSTART;VALUE=DATE:20200624
+DTEND;VALUE=DATE:20200625
+SUMMARY:jaanipäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20200820@kayaposoft.com
+DTSTART;VALUE=DATE:20200820
+DTEND;VALUE=DATE:20200821
+SUMMARY:taasiseseisvumispäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20201224@kayaposoft.com
+DTSTART;VALUE=DATE:20201224
+DTEND;VALUE=DATE:20201225
+SUMMARY:jõululaupäev
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:esimene jõulupüha
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-est-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:teine jõulupüha
STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/assets/finland.ics b/app/src/main/assets/finland.ics
index 637121ade..da9373306 100755
--- a/app/src/main/assets/finland.ics
+++ b/app/src/main/assets/finland.ics
@@ -65,16 +65,16 @@ END:VEVENT
BEGIN:VEVENT
SUMMARY:Naistenpäivä
UID:55954496-9c99-4eeb-871e-b015d0f8ce40
-DTSTART;VALUE=DATE:19750308
-DTEND;VALUE=DATE:19750309
+DTSTART;VALUE=DATE:20170308
+DTEND;VALUE=DATE:20170309
STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
SUMMARY:Ruotsalaisuuden päivä
UID:ac081b74-6737-49a2-aeaf-4993eec75d0e
-DTSTART;VALUE=DATE:19801106
-DTEND;VALUE=DATE:19801107
+DTSTART;VALUE=DATE:20171106
+DTEND;VALUE=DATE:20171107
STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
diff --git a/app/src/main/assets/germany.ics b/app/src/main/assets/germany.ics
index 408d9f529..ba35fdaae 100755
--- a/app/src/main/assets/germany.ics
+++ b/app/src/main/assets/germany.ics
@@ -7,13 +7,6 @@ DTEND;VALUE=DATE:20170526
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:55d7b48b-f4ac-4d4f-9f7a-c750cd836fb9
-DTSTART;VALUE=DATE:20170604
-DTEND;VALUE=DATE:20170605
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:0763c01f-245e-4ebf-acff-45597e999a01
DTSTART;VALUE=DATE:20170605
@@ -35,13 +28,6 @@ DTEND;VALUE=DATE:20171120
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:e2e10823-b9cd-4c10-931c-38f2b0853844
-DTSTART;VALUE=DATE:20171122
-DTEND;VALUE=DATE:20171123
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:3d292c6c-c482-4a6a-8ec3-18dae2c57aa0
DTSTART;VALUE=DATE:20171126
@@ -49,48 +35,6 @@ DTEND;VALUE=DATE:20171127
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:f31f4c45-3d27-4992-88b7-8ed6e269069a
-DTSTART;VALUE=DATE:20171203
-DTEND;VALUE=DATE:20171204
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:4f5ceb8a-4fdf-471b-873d-7b6d74ce1527
-DTSTART;VALUE=DATE:20171210
-DTEND;VALUE=DATE:20171211
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:13fc6d4f-c378-478e-b4d1-98578060b359
-DTSTART;VALUE=DATE:20171217
-DTEND;VALUE=DATE:20171218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:71807f5d-ea0a-4c85-954d-301490a4db78
-DTSTART;VALUE=DATE:20171224
-DTEND;VALUE=DATE:20171225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:16a1f889-913e-4d6c-ae41-63d05096af02
-DTSTART;VALUE=DATE:20180212
-DTEND;VALUE=DATE:20180213
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:553ebbee-4e98-4979-9fb4-f16de19b78c9
-DTSTART;VALUE=DATE:20180214
-DTEND;VALUE=DATE:20180215
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:00fa1ea2-c91d-4d5b-89c5-1102259e1d24
DTSTART;VALUE=DATE:20180330
@@ -98,13 +42,6 @@ DTEND;VALUE=DATE:20180331
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostern
-UID:3a6ff1ad-1215-462e-bc6e-31ff03cab1fe
-DTSTART;VALUE=DATE:20180401
-DTEND;VALUE=DATE:20180402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Ostermontag
UID:0ab12047-4716-4307-9d41-025db2534867
DTSTART;VALUE=DATE:20180402
@@ -119,20 +56,6 @@ DTEND;VALUE=DATE:20180511
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:01e33d81-7688-4b5f-a448-8f30936d53c5
-DTSTART;VALUE=DATE:20180513
-DTEND;VALUE=DATE:20180514
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:e6891ff2-4e97-4163-8b68-2711a9395a2b
-DTSTART;VALUE=DATE:20180520
-DTEND;VALUE=DATE:20180521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:01da1f59-2a95-4528-84b7-45c420d73130
DTSTART;VALUE=DATE:20180521
@@ -154,13 +77,6 @@ DTEND;VALUE=DATE:20181119
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:8f011c35-0bc0-4ce7-983c-3f5107105089
-DTSTART;VALUE=DATE:20181121
-DTEND;VALUE=DATE:20181122
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:5ce3351e-8a46-4a6a-86c1-9d6a4691d69c
DTSTART;VALUE=DATE:20181125
@@ -168,55 +84,6 @@ DTEND;VALUE=DATE:20181126
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:54af4894-22f2-4d6f-afec-b41a4773a7c1
-DTSTART;VALUE=DATE:20181202
-DTEND;VALUE=DATE:20181203
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:075866e7-e5a4-4e28-9e7b-da1668457f12
-DTSTART;VALUE=DATE:20181209
-DTEND;VALUE=DATE:20181210
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:859543e9-8310-4c39-a953-55d01afb95d5
-DTSTART;VALUE=DATE:20181216
-DTEND;VALUE=DATE:20181217
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:86472732-c54c-454c-8c46-60dc8d872f22
-DTSTART;VALUE=DATE:20181223
-DTEND;VALUE=DATE:20181224
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:d37799d7-4c4e-49a1-8132-3b41ffdc650e
-DTSTART;VALUE=DATE:20190304
-DTEND;VALUE=DATE:20190305
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:59874060-e592-493d-beb3-7710a4c8a4c4
-DTSTART;VALUE=DATE:20190306
-DTEND;VALUE=DATE:20190307
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palmsonntag
-UID:f69b0d53-6039-4f44-993e-dfed2988dbbb
-DTSTART;VALUE=DATE:20190414
-DTEND;VALUE=DATE:20190415
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:0aaa7d21-c168-4dbb-a9ca-311c83c0840d
DTSTART;VALUE=DATE:20190419
@@ -224,13 +91,6 @@ DTEND;VALUE=DATE:20190420
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostern
-UID:c6ae2cd0-f9dc-4936-ac69-7f43a8d5c454
-DTSTART;VALUE=DATE:20190421
-DTEND;VALUE=DATE:20190422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Ostermontag
UID:b0300aa9-c14b-482c-b9d9-be81d9475123
DTSTART;VALUE=DATE:20190422
@@ -238,13 +98,6 @@ DTEND;VALUE=DATE:20190423
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:1cbb64eb-c8c1-4f49-bc4e-e11190c37f42
-DTSTART;VALUE=DATE:20190512
-DTEND;VALUE=DATE:20190513
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Christi Himmelfahrt
UID:54b3d09c-99f7-4f27-9253-d1fe8e358c50
DTSTART;VALUE=DATE:20190530
@@ -252,13 +105,6 @@ DTEND;VALUE=DATE:20190531
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:4154d812-6a09-4bb1-9b39-9654af771086
-DTSTART;VALUE=DATE:20190609
-DTEND;VALUE=DATE:20190610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:68095b69-e62a-4a0a-be4a-c9a68844a341
DTSTART;VALUE=DATE:20190610
@@ -280,13 +126,6 @@ DTEND;VALUE=DATE:20191118
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:29bcda47-bb53-40ef-8d23-5f96e87d7b11
-DTSTART;VALUE=DATE:20191120
-DTEND;VALUE=DATE:20191121
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:55f73cbf-979c-4947-9245-1f39720f017a
DTSTART;VALUE=DATE:20191124
@@ -294,55 +133,6 @@ DTEND;VALUE=DATE:20191125
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:f88e5652-cc2c-4e29-8d7a-aaf2b839ca87
-DTSTART;VALUE=DATE:20191201
-DTEND;VALUE=DATE:20191202
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:6a1e6f6b-4a86-45cf-ae59-e73968a4f094
-DTSTART;VALUE=DATE:20191208
-DTEND;VALUE=DATE:20191209
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:59ab9870-918f-49ef-a3c5-ac1b79c12e43
-DTSTART;VALUE=DATE:20191215
-DTEND;VALUE=DATE:20191216
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:cc5ff7ef-7d49-49ab-ab20-b2fcfb9d5c8a
-DTSTART;VALUE=DATE:20191222
-DTEND;VALUE=DATE:20191223
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:f10cb9c7-e9c6-40da-9799-e9f7defc8706
-DTSTART;VALUE=DATE:20200224
-DTEND;VALUE=DATE:20200225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:d263556d-11f7-41dd-9b87-e7e33b5e0cd7
-DTSTART;VALUE=DATE:20200226
-DTEND;VALUE=DATE:20200227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palmsonntag
-UID:e7d82cc8-29d3-4b46-8c64-6d0514ff01a3
-DTSTART;VALUE=DATE:20200405
-DTEND;VALUE=DATE:20200406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:84fb596d-f62c-4f0c-be11-7d96e7883e97
DTSTART;VALUE=DATE:20200410
@@ -350,8 +140,8 @@ DTEND;VALUE=DATE:20200411
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostern
-UID:b65c108f-5eea-4065-a4cd-8f5cc869666b
+SUMMARY:Ostersonntag
+UID:84fb596d-f62c-4f0c-be11-7d96e7884567
DTSTART;VALUE=DATE:20200412
DTEND;VALUE=DATE:20200413
STATUS:CONFIRMED
@@ -364,13 +154,6 @@ DTEND;VALUE=DATE:20200414
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:b3a25d49-3ae0-4941-a0a3-605a0aa013a3
-DTSTART;VALUE=DATE:20200510
-DTEND;VALUE=DATE:20200511
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Christi Himmelfahrt
UID:20b3805d-d325-478b-bcee-c8f1f87f7e2d
DTSTART;VALUE=DATE:20200521
@@ -378,8 +161,8 @@ DTEND;VALUE=DATE:20200522
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:b394de73-9c84-425c-8f43-acb3f08988fb
+SUMMARY:Pfingstsonntag
+UID:12013b62-6924-4457-89ff-3a8ba947778
DTSTART;VALUE=DATE:20200531
DTEND;VALUE=DATE:20200601
STATUS:CONFIRMED
@@ -406,13 +189,6 @@ DTEND;VALUE=DATE:20201116
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:fbda2175-0043-463e-9559-75846f246604
-DTSTART;VALUE=DATE:20201118
-DTEND;VALUE=DATE:20201119
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:0cc4f56c-7eb8-4de7-ae2a-99b293b25f97
DTSTART;VALUE=DATE:20201122
@@ -420,48 +196,6 @@ DTEND;VALUE=DATE:20201123
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:af547b9d-c9fb-4b2c-a9ee-1883a246fb4a
-DTSTART;VALUE=DATE:20201129
-DTEND;VALUE=DATE:20201130
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:9be17f09-8386-4d4a-b611-ea5b8aaca4d1
-DTSTART;VALUE=DATE:20201206
-DTEND;VALUE=DATE:20201207
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:7b494bd1-ed8b-4278-985e-f974481b1574
-DTSTART;VALUE=DATE:20201213
-DTEND;VALUE=DATE:20201214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:1b11acb7-0340-4958-9225-cfc14f4a478f
-DTSTART;VALUE=DATE:20201220
-DTEND;VALUE=DATE:20201221
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:2a024898-6e15-448b-83f5-c994fb1e4aa1
-DTSTART;VALUE=DATE:20210215
-DTEND;VALUE=DATE:20210216
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:82dc191a-e513-407e-99e3-9cb98483715d
-DTSTART;VALUE=DATE:20210217
-DTEND;VALUE=DATE:20210218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:4c8899f1-6694-4804-bdaf-dd13f837c8d5
DTSTART;VALUE=DATE:20210402
@@ -469,13 +203,6 @@ DTEND;VALUE=DATE:20210403
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostern
-UID:7de1ada4-4e88-417e-91a1-86a219184830
-DTSTART;VALUE=DATE:20210404
-DTEND;VALUE=DATE:20210405
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Ostermontag
UID:3ab2214a-92e4-4fca-87b6-77d92c142631
DTSTART;VALUE=DATE:20210405
@@ -483,13 +210,6 @@ DTEND;VALUE=DATE:20210406
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:d6cfc602-b0ec-4cb7-a066-4fed1804dc2e
-DTSTART;VALUE=DATE:20210509
-DTEND;VALUE=DATE:20210510
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Christi Himmelfahrt
UID:65fd8793-c12f-4c51-b392-a40b8b434fee
DTSTART;VALUE=DATE:20210513
@@ -497,13 +217,6 @@ DTEND;VALUE=DATE:20210514
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:6b058d52-a7c5-40e4-9c49-9b2cf6994053
-DTSTART;VALUE=DATE:20210523
-DTEND;VALUE=DATE:20210524
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:8a560aec-1ad3-4116-b0c0-f345345f8112
DTSTART;VALUE=DATE:20210524
@@ -525,13 +238,6 @@ DTEND;VALUE=DATE:20211115
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:b495650e-002d-4c02-b9d3-52c26b204bf0
-DTSTART;VALUE=DATE:20211117
-DTEND;VALUE=DATE:20211118
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:f0a7eadd-30e7-46c1-bce0-37616e68ac13
DTSTART;VALUE=DATE:20211121
@@ -539,55 +245,6 @@ DTEND;VALUE=DATE:20211122
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:03e79870-f2e5-414e-ac71-a41f73acd5c8
-DTSTART;VALUE=DATE:20211128
-DTEND;VALUE=DATE:20211129
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:79ba8671-3780-461a-a884-c23a64cf7930
-DTSTART;VALUE=DATE:20211205
-DTEND;VALUE=DATE:20211206
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:7128bd77-e61e-48ec-baca-adc12c869c87
-DTSTART;VALUE=DATE:20211212
-DTEND;VALUE=DATE:20211213
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:b73f7d0d-0d10-4d42-96d7-d3256415d1b9
-DTSTART;VALUE=DATE:20211219
-DTEND;VALUE=DATE:20211220
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:28273646-1658-430a-836f-90b3079ab56c
-DTSTART;VALUE=DATE:20220228
-DTEND;VALUE=DATE:20220301
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:642134b6-db26-4c84-9055-88f1e350ef4e
-DTSTART;VALUE=DATE:20220302
-DTEND;VALUE=DATE:20220303
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palmsonntag
-UID:70683648-440f-45aa-9460-10d1ffced6e1
-DTSTART;VALUE=DATE:20220410
-DTEND;VALUE=DATE:20220411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:018fdbdd-e2c5-48aa-bf6e-4f802d757d52
DTSTART;VALUE=DATE:20220415
@@ -595,13 +252,6 @@ DTEND;VALUE=DATE:20220416
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostern
-UID:fca22c3c-3e5a-4aec-b1f8-14b146ec71ef
-DTSTART;VALUE=DATE:20220417
-DTEND;VALUE=DATE:20220418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Ostermontag
UID:402857cd-1eb5-48ef-9b8c-fd0eb188dec6
DTSTART;VALUE=DATE:20220418
@@ -609,13 +259,6 @@ DTEND;VALUE=DATE:20220419
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:ff344166-2d62-43c5-9ded-53a300f684b9
-DTSTART;VALUE=DATE:20220508
-DTEND;VALUE=DATE:20220509
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Christi Himmelfahrt
UID:c90fd01f-5521-4973-b0bb-11c3b16dd9ff
DTSTART;VALUE=DATE:20220526
@@ -623,13 +266,6 @@ DTEND;VALUE=DATE:20220527
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:0d0106e9-1335-464e-b463-5cb142db5f1a
-DTSTART;VALUE=DATE:20220605
-DTEND;VALUE=DATE:20220606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:a22fa187-185b-4cf1-93c1-82bdca14fd0e
DTSTART;VALUE=DATE:20220606
@@ -651,13 +287,6 @@ DTEND;VALUE=DATE:20221114
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:590b067f-31f8-4c6f-bafb-accf22a6cf89
-DTSTART;VALUE=DATE:20221116
-DTEND;VALUE=DATE:20221117
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:f3b740a9-5fe6-42f8-8ea1-0a24c3b7947c
DTSTART;VALUE=DATE:20221120
@@ -665,55 +294,6 @@ DTEND;VALUE=DATE:20221121
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:9b5a13c3-6abe-458e-bcbe-f1d6cdcdd02d
-DTSTART;VALUE=DATE:20221127
-DTEND;VALUE=DATE:20221128
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:1a7f91ae-5850-4e72-bdd1-1108c812d6fb
-DTSTART;VALUE=DATE:20221204
-DTEND;VALUE=DATE:20221205
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:a7d53387-8173-4a44-8d67-b64563dff278
-DTSTART;VALUE=DATE:20221211
-DTEND;VALUE=DATE:20221212
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:220094a1-08c4-4a0d-bd30-40d071682395
-DTSTART;VALUE=DATE:20221218
-DTEND;VALUE=DATE:20221219
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:5f7513bd-0978-4ed8-8022-7280645c3186
-DTSTART;VALUE=DATE:20230220
-DTEND;VALUE=DATE:20230221
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:956d4b66-1b2c-418e-ae7d-3cffad30eae7
-DTSTART;VALUE=DATE:20230222
-DTEND;VALUE=DATE:20230223
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palmsonntag
-UID:0b5571a1-a813-4f47-bef7-dc28569de753
-DTSTART;VALUE=DATE:20230402
-DTEND;VALUE=DATE:20230403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:ffaf8199-f853-45c8-b635-5f0e302f13fe
DTSTART;VALUE=DATE:20230407
@@ -721,13 +301,6 @@ DTEND;VALUE=DATE:20230408
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostern
-UID:a379dcf3-85eb-401f-b8cf-7f02da344d11
-DTSTART;VALUE=DATE:20230409
-DTEND;VALUE=DATE:20230410
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Ostermontag
UID:bb40c59f-7f00-44b7-98fc-e5fd5e0c9c27
DTSTART;VALUE=DATE:20230410
@@ -735,13 +308,6 @@ DTEND;VALUE=DATE:20230411
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:8a0d77ba-30f9-4238-83b5-adcab7a3badb
-DTSTART;VALUE=DATE:20230514
-DTEND;VALUE=DATE:20230515
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Christi Himmelfahrt
UID:f91d0e50-3550-46f4-959e-4c0837d122b3
DTSTART;VALUE=DATE:20230518
@@ -749,13 +315,6 @@ DTEND;VALUE=DATE:20230519
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:888f86d6-9809-4b2c-948b-1443edac5e8e
-DTSTART;VALUE=DATE:20230528
-DTEND;VALUE=DATE:20230529
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:4eebf391-9bab-4c47-9828-9f102779600a
DTSTART;VALUE=DATE:20230529
@@ -777,13 +336,6 @@ DTEND;VALUE=DATE:20231120
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:ba67b1dd-5db8-4536-af86-5ac01e6db382
-DTSTART;VALUE=DATE:20231122
-DTEND;VALUE=DATE:20231123
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:0eae032b-ae56-42ba-9d9b-42893d74dfb2
DTSTART;VALUE=DATE:20231126
@@ -791,55 +343,6 @@ DTEND;VALUE=DATE:20231127
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:42a5ae0f-030c-4e61-a16a-168e3644c61a
-DTSTART;VALUE=DATE:20231203
-DTEND;VALUE=DATE:20231204
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:3568cc04-205f-43ce-981f-5f695f0ffb89
-DTSTART;VALUE=DATE:20231210
-DTEND;VALUE=DATE:20231211
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:c706891d-2560-4b11-b165-01b1810f36a3
-DTSTART;VALUE=DATE:20231217
-DTEND;VALUE=DATE:20231218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:966f4e2f-a485-4617-be7b-fc1bfed38d05
-DTSTART;VALUE=DATE:20231224
-DTEND;VALUE=DATE:20231225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:28ff27ae-6c43-4241-8e19-b859d3bacbbd
-DTSTART;VALUE=DATE:20240212
-DTEND;VALUE=DATE:20240213
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:99af362f-b9b1-4452-896d-648906ae4920
-DTSTART;VALUE=DATE:20240214
-DTEND;VALUE=DATE:20240215
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palmsonntag
-UID:aabf87df-8b3b-469d-b731-fd8ee5c851da
-DTSTART;VALUE=DATE:20240324
-DTEND;VALUE=DATE:20240325
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:179a8a7f-6ebc-427f-89c8-dfd87f770bf9
DTSTART;VALUE=DATE:20240329
@@ -861,20 +364,6 @@ DTEND;VALUE=DATE:20240510
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:23fa06d1-ccfe-40bf-81e8-d558ed1bf420
-DTSTART;VALUE=DATE:20240512
-DTEND;VALUE=DATE:20240513
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:7075eb28-7dde-4b96-8aad-76083ac8682b
-DTSTART;VALUE=DATE:20240519
-DTEND;VALUE=DATE:20240520
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:aa76019e-b3b6-4488-8bbf-31dc09fb1764
DTSTART;VALUE=DATE:20240520
@@ -896,13 +385,6 @@ DTEND;VALUE=DATE:20241118
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:dcf3f9a7-086c-4e3e-805f-adaaec6fda2e
-DTSTART;VALUE=DATE:20241120
-DTEND;VALUE=DATE:20241121
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:546e46fb-ec99-4ddd-aa9d-6ec619b96dc0
DTSTART;VALUE=DATE:20241124
@@ -910,55 +392,6 @@ DTEND;VALUE=DATE:20241125
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Erster Advent
-UID:0b6285cb-89f9-4b85-8de0-a42bd18589c9
-DTSTART;VALUE=DATE:20241201
-DTEND;VALUE=DATE:20241202
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zweiter Advent
-UID:4ad38de2-8fce-4344-a208-979f1916b835
-DTSTART;VALUE=DATE:20241208
-DTEND;VALUE=DATE:20241209
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dritter Advent
-UID:b49431ca-7be4-4b79-bbb2-5f6a1248ef93
-DTSTART;VALUE=DATE:20241215
-DTEND;VALUE=DATE:20241216
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vierter Advent
-UID:6b46edc1-b54b-430b-9028-759a11d582c7
-DTSTART;VALUE=DATE:20241222
-DTEND;VALUE=DATE:20241223
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Rosenmontag
-UID:749e7d53-b90e-4e4c-8375-10bc41996985
-DTSTART;VALUE=DATE:20250303
-DTEND;VALUE=DATE:20250304
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Aschermittwoch
-UID:0c4a90fc-4651-4a12-886c-0659eb26d299
-DTSTART;VALUE=DATE:20250305
-DTEND;VALUE=DATE:20250306
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palmsonntag
-UID:1c33e111-bb08-4f3d-992e-4e4712ab7118
-DTSTART;VALUE=DATE:20250413
-DTEND;VALUE=DATE:20250414
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Karfreitag
UID:e9a51e52-0956-4d0f-be18-c5ce39dae024
DTSTART;VALUE=DATE:20250418
@@ -966,13 +399,6 @@ DTEND;VALUE=DATE:20250419
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Ostern
-UID:efacb91a-6c19-4f00-bda9-cd6b8cfc4ce0
-DTSTART;VALUE=DATE:20250420
-DTEND;VALUE=DATE:20250421
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Ostermontag
UID:457982d2-a038-49d2-9b27-6f9708a16206
DTSTART;VALUE=DATE:20250421
@@ -980,13 +406,6 @@ DTEND;VALUE=DATE:20250422
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Muttertag
-UID:1b9ecb65-b7ff-4388-b20c-64a5f90ff83f
-DTSTART;VALUE=DATE:20250511
-DTEND;VALUE=DATE:20250512
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Christi Himmelfahrt
UID:0705f107-eef7-4929-ac93-a9d7a645e7f8
DTSTART;VALUE=DATE:20250529
@@ -994,13 +413,6 @@ DTEND;VALUE=DATE:20250530
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:49fa3924-037e-46aa-985b-29f0f886ef8c
-DTSTART;VALUE=DATE:20250608
-DTEND;VALUE=DATE:20250609
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Pfingstmontag
UID:082092fe-3345-4652-b5f9-d5aeee66328f
DTSTART;VALUE=DATE:20250609
@@ -1022,13 +434,6 @@ DTEND;VALUE=DATE:20251117
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Buß- und Bettag
-UID:b2e0c62b-d371-414a-9f14-bdd0fa5a3eb9
-DTSTART;VALUE=DATE:20251119
-DTEND;VALUE=DATE:20251120
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Totensonntag
UID:54c731a4-2052-44f7-be17-7976de70f144
DTSTART;VALUE=DATE:20251123
@@ -1080,14 +485,6 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Valentinstag
-UID:959125914
-DTSTART;VALUE=DATE:20060214
-DTEND;VALUE=DATE:20060215
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Tag der Arbeit
UID:999463355
DTSTART;VALUE=DATE:20060501
@@ -1104,7 +501,7 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Mariä Himmelfahrt
+SUMMARY:Maria Himmelfahrt
UID:948278340
DTSTART;VALUE=DATE:20060815
DTEND;VALUE=DATE:20060816
@@ -1120,14 +517,6 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Reformationstag
-UID:996398531
-DTSTART;VALUE=DATE:20061031
-DTEND;VALUE=DATE:20061101
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Allerheiligen
UID:970745861
DTSTART;VALUE=DATE:20061101
@@ -1136,22 +525,6 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Nikolaus
-UID:915905148
-DTSTART;VALUE=DATE:20061206
-DTEND;VALUE=DATE:20061207
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Heiliger Abend
-UID:031bd550-c8d9-11da-9d0c-a4805e40b203
-DTSTART;VALUE=DATE:20061224
-DTEND;VALUE=DATE:20061225
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:1. Weihnachtstag
UID:982967580
DTSTART;VALUE=DATE:20061225
@@ -1175,4 +548,62 @@ DTEND;VALUE=DATE:20070101
STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Internationaler Frauentag
+UID:4694f46a-aef0-49a0-a8eb-asdasd
+DTSTART;VALUE=DATE:20170308
+DTEND;VALUE=DATE:20170309
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Reformationstag
+UID:4694f46a-aef0-49a0-a8eb-reform
+DTSTART;VALUE=DATE:20171031
+DTEND;VALUE=DATE:20171101
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Buß- und Bettag
+UID:4694f46a-aef0-49a0-a8eb-b1
+DTSTART;VALUE=DATE:20181121
+DTEND;VALUE=DATE:20181122
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Buß- und Bettag
+UID:4694f46a-aef0-49a0-a8eb-b2
+DTSTART;VALUE=DATE:20191120
+DTEND;VALUE=DATE:20191121
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Buß- und Bettag
+UID:4694f46a-aef0-49a0-a8eb-b3
+DTSTART;VALUE=DATE:20201118
+DTEND;VALUE=DATE:20201119
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Buß- und Bettag
+UID:4694f46a-aef0-49a0-a8eb-b4
+DTSTART;VALUE=DATE:20211117
+DTEND;VALUE=DATE:20211118
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Buß- und Bettag
+UID:4694f46a-aef0-49a0-a8eb-b5
+DTSTART;VALUE=DATE:20221116
+DTEND;VALUE=DATE:20221117
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Buß- und Bettag
+UID:4694f46a-aef0-49a0-a8eb-b6
+DTSTART;VALUE=DATE:20231122
+DTEND;VALUE=DATE:20231123
+STATUS:CONFIRMED
+END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/assets/india.ics b/app/src/main/assets/india.ics
index eda425aa4..806cbe8d0 100755
--- a/app/src/main/assets/india.ics
+++ b/app/src/main/assets/india.ics
@@ -300,8 +300,8 @@ END:VEVENT
BEGIN:VEVENT
SUMMARY:ID-I-Milad-un-Nabi [Barah-Wafat]
UID:2002-05-25-1
-DTSTART;VALUE=DATE:20161213
-DTEND;VALUE=DATE:20161214
+DTSTART;VALUE=DATE:20161110
+DTEND;VALUE=DATE:20161111
STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
diff --git a/app/src/main/assets/indonesia.ics b/app/src/main/assets/indonesia.ics
new file mode 100644
index 000000000..c8f27e723
--- /dev/null
+++ b/app/src/main/assets/indonesia.ics
@@ -0,0 +1,300 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:20171212T045342Z-268948299@marudot.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180101
+SUMMARY:New Year's Day 2018
+DESCRIPTION:Tahun Baru 2018
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-601964501@marudot.com
+DTSTART;VALUE=DATE:20180216
+DTEND;VALUE=DATE:20180216
+SUMMARY:Chinese New Year
+DESCRIPTION:Tahun Baru Imlek 2569
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-417357037@marudot.com
+DTSTART;VALUE=DATE:20180228
+DTEND;VALUE=DATE:20180228
+SUMMARY:Coming to Indonesia-Cross Cultural Training
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-578292563@marudot.com
+DTSTART;VALUE=DATE:20180301
+DTEND;VALUE=DATE:20180301
+SUMMARY:Working in Indonesia-Cross Cultural Workshop for Expats
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1099619811@marudot.com
+DTSTART;VALUE=DATE:20180313
+DTEND;VALUE=DATE:20180313
+SUMMARY:Living in Indonesia-Cross Cultural Workshop for Spouses
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-894503703@marudot.com
+DTSTART;VALUE=DATE:20180314
+DTEND;VALUE=DATE:20180314
+SUMMARY:Management in Indonesia-Cross Cultural Workshop
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-791851244@marudot.com
+DTSTART;VALUE=DATE:20180317
+DTEND;VALUE=DATE:20180317
+SUMMARY:Balinese New Year
+DESCRIPTION:Hari Raya Nyepi 1940
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-329618688@marudot.com
+DTSTART;VALUE=DATE:20180330
+DTEND;VALUE=DATE:20180330
+SUMMARY:Good Friday
+DESCRIPTION:Wafat Isa Al-Masih
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-190146881@marudot.com
+DTSTART;VALUE=DATE:20180414
+DTEND;VALUE=DATE:20180414
+SUMMARY:Ascension of Prophet Muhammad SAW
+DESCRIPTION:Isra’ Mi’raj Nabi Muhammad SAW
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-435386496@marudot.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180501
+SUMMARY:Labor Day
+DESCRIPTION:Hari Buruh Internasional
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-418653428@marudot.com
+DTSTART;VALUE=DATE:20180510
+DTEND;VALUE=DATE:20180510
+SUMMARY:Waisak Day (“Buddhas Birthday” 2562)
+DESCRIPTION:Hari Waisak 2562
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-2110293750@marudot.com
+DTSTART;VALUE=DATE:20180510
+DTEND;VALUE=DATE:20180510
+SUMMARY:Ascension Day of Jesus Christ
+DESCRIPTION:Kenaikan Isa Almasih
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-119842090@marudot.com
+DTSTART;VALUE=DATE:20180601
+DTEND;VALUE=DATE:20180601
+SUMMARY:Birthday of Pancasila
+DESCRIPTION:Hari Lahir Pancasila
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1166612864@marudot.com
+DTSTART;VALUE=DATE:20180615
+DTEND;VALUE=DATE:20180615
+SUMMARY:End of Ramadan – Eid-al-Fitr 1439H (1st of Shawwal)
+DESCRIPTION:Hari Raya Idul Fitri 1439H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-640873938@marudot.com
+DTSTART;VALUE=DATE:20180616
+DTEND;VALUE=DATE:20180616
+SUMMARY:End of Ramadan – Eid-al-Fitr 1439H (2nd of Shawwal)
+DESCRIPTION:Hari Raya Idul Fitri 1439H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1948724575@marudot.com
+DTSTART;VALUE=DATE:20180808
+DTEND;VALUE=DATE:20180808
+SUMMARY:Working in Indonesia-Cross Cultural Workshop for Expats
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1926764224@marudot.com
+DTSTART;VALUE=DATE:20180814
+DTEND;VALUE=DATE:20180814
+SUMMARY:Management in Indonesia-Cross Cultural Workshop
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-828431129@marudot.com
+DTSTART;VALUE=DATE:20180817
+DTEND;VALUE=DATE:20180817
+SUMMARY:Indonesia Independence Day
+DESCRIPTION:Hari Kemerdekaan Republik Indonesia ke-73
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1713949044@marudot.com
+DTSTART;VALUE=DATE:20180822
+DTEND;VALUE=DATE:20180822
+SUMMARY:Islamic feast of Sacrifice (Bakr-Eid) 1439H
+DESCRIPTION:Hari Raya Idul Adha 1439H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1202137034@marudot.com
+DTSTART;VALUE=DATE:20180911
+DTEND;VALUE=DATE:20180911
+SUMMARY:Islamic New Year (1st Muharram 1440H)
+DESCRIPTION:Tahun Baru Islam 1440H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-209129850@marudot.com
+DTSTART;VALUE=DATE:20181120
+DTEND;VALUE=DATE:20181120
+SUMMARY:Birth of the Prophet Muhammad SAW
+DESCRIPTION:Maulid Nabi
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-946856460@marudot.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181225
+SUMMARY:Christmas Day
+DESCRIPTION:Hari Raya Natal
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-2689482992@marudot.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190101
+SUMMARY:New Year's Day 2019
+DESCRIPTION:Tahun Baru 2019
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-6019645012@marudot.com
+DTSTART;VALUE=DATE:20190205
+DTEND;VALUE=DATE:20190205
+SUMMARY:Chinese New Year
+DESCRIPTION:Tahun Baru Imlek 2569
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-7918512442@marudot.com
+DTSTART;VALUE=DATE:20190307
+DTEND;VALUE=DATE:20190307
+SUMMARY:Balinese New Year
+DESCRIPTION:Hari Raya Nyepi 1940
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-3296186882@marudot.com
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190419
+SUMMARY:Good Friday
+DESCRIPTION:Wafat Isa Al-Masih
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1901468812@marudot.com
+DTSTART;VALUE=DATE:20190530
+DTEND;VALUE=DATE:20190530
+SUMMARY:Ascension of Prophet Muhammad SAW
+DESCRIPTION:Isra’ Mi’raj Nabi Muhammad SAW
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-4353864962@marudot.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190501
+SUMMARY:Labor Day
+DESCRIPTION:Hari Buruh Internasional
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-4186534282@marudot.com
+DTSTART;VALUE=DATE:20190519
+DTEND;VALUE=DATE:20190519
+SUMMARY:Waisak Day (“Buddhas Birthday” 2562)
+DESCRIPTION:Hari Waisak 2562
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-21102937502@marudot.com
+DTSTART;VALUE=DATE:20190530
+DTEND;VALUE=DATE:20190530
+SUMMARY:Ascension Day of Jesus Christ
+DESCRIPTION:Kenaikan Isa Almasih
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-1198420902@marudot.com
+DTSTART;VALUE=DATE:20190601
+DTEND;VALUE=DATE:20190601
+SUMMARY:Birthday of Pancasila
+DESCRIPTION:Hari Lahir Pancasila
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-11666128642@marudot.com
+DTSTART;VALUE=DATE:20180605
+DTEND;VALUE=DATE:20180605
+SUMMARY:End of Ramadan – Eid-al-Fitr 1439H (1st of Shawwal)
+DESCRIPTION:Hari Raya Idul Fitri 1439H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-6408739382@marudot.com
+DTSTART;VALUE=DATE:20180606
+DTEND;VALUE=DATE:20180606
+SUMMARY:End of Ramadan – Eid-al-Fitr 1439H (2nd of Shawwal)
+DESCRIPTION:Hari Raya Idul Fitri 1439H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-8284311292@marudot.com
+DTSTART;VALUE=DATE:20190817
+DTEND;VALUE=DATE:20190817
+SUMMARY:Indonesia Independence Day
+DESCRIPTION:Hari Kemerdekaan Republik Indonesia ke-73
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-17139490442@marudot.com
+DTSTART;VALUE=DATE:20180812
+DTEND;VALUE=DATE:20180812
+SUMMARY:Islamic feast of Sacrifice (Bakr-Eid) 1439H
+DESCRIPTION:Hari Raya Idul Adha 1439H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-12021370342@marudot.com
+DTSTART;VALUE=DATE:20180901
+DTEND;VALUE=DATE:20180901
+SUMMARY:Islamic New Year (1st Muharram 1440H)
+DESCRIPTION:Tahun Baru Islam 1440H
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-2091298502@marudot.com
+DTSTART;VALUE=DATE:20181110
+DTEND;VALUE=DATE:20181110
+SUMMARY:Birth of the Prophet Muhammad SAW
+DESCRIPTION:Maulid Nabi
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:20171212T045342Z-9468564602@marudot.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181225
+SUMMARY:Christmas Day
+DESCRIPTION:Hari Raya Natal
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/japan.ics b/app/src/main/assets/japan.ics
index f57351373..86c1afada 100755
--- a/app/src/main/assets/japan.ics
+++ b/app/src/main/assets/japan.ics
@@ -152,10 +152,10 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:天皇誕生日 / Tennō tanjōbi / Emperor Akihito's Birthday
+SUMMARY:天皇誕生日 / Tennō tanjōbi / The Emperor Naruhito's Birthday
UID:0f5a4799-4082-4921-95e5-183652982b2d
-DTSTART;VALUE=DATE:20071223
-DTEND;VALUE=DATE:20071224
+DTSTART;VALUE=DATE:20190223
+DTEND;VALUE=DATE:20190224
STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
diff --git a/app/src/main/assets/latvia.ics b/app/src/main/assets/latvia.ics
new file mode 100644
index 000000000..a9865a11c
--- /dev/null
+++ b/app/src/main/assets/latvia.ics
@@ -0,0 +1,326 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-lva-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Jaungada dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180330@kayaposoft.com
+DTSTART;VALUE=DATE:20180330
+DTEND;VALUE=DATE:20180331
+SUMMARY:Lielo Piektdienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180401@kayaposoft.com
+DTSTART;VALUE=DATE:20180401
+DTEND;VALUE=DATE:20180402
+SUMMARY:Pirmās Lieldienas
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180402@kayaposoft.com
+DTSTART;VALUE=DATE:20180402
+DTEND;VALUE=DATE:20180403
+SUMMARY:Otro Lieldienu dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180430@kayaposoft.com
+DTSTART;VALUE=DATE:20180430
+DTEND;VALUE=DATE:20180501
+SUMMARY:Darba svētkus
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Darba svētkus
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180504@kayaposoft.com
+DTSTART;VALUE=DATE:20180504
+DTEND;VALUE=DATE:20180505
+SUMMARY:Latvijas Republikas Neatkarības atjaunošanas dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180513@kayaposoft.com
+DTSTART;VALUE=DATE:20180513
+DTEND;VALUE=DATE:20180514
+SUMMARY:Mātes diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180520@kayaposoft.com
+DTSTART;VALUE=DATE:20180520
+DTEND;VALUE=DATE:20180521
+SUMMARY:Vasarsvētkus
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180623@kayaposoft.com
+DTSTART;VALUE=DATE:20180623
+DTEND;VALUE=DATE:20180624
+SUMMARY:Līgo dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20180624@kayaposoft.com
+DTSTART;VALUE=DATE:20180624
+DTEND;VALUE=DATE:20180625
+SUMMARY:Jāņu dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20181119@kayaposoft.com
+DTSTART;VALUE=DATE:20181119
+DTEND;VALUE=DATE:20181120
+SUMMARY:Latvijas Republikas proklamēšanas diena
+DESCRIPTION:Holiday in lieu of 18 Nov 2018
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20181224@kayaposoft.com
+DTSTART;VALUE=DATE:20181224
+DTEND;VALUE=DATE:20181225
+SUMMARY:Ziemassvētku vakars
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Ziemassvētki
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:2. Ziemassvētki
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20181231@kayaposoft.com
+DTSTART;VALUE=DATE:20181231
+DTEND;VALUE=DATE:20190101
+SUMMARY:Vecgada vakars
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Jaungada dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190419@kayaposoft.com
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+SUMMARY:Lielo Piektdienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190421@kayaposoft.com
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+SUMMARY:Pirmās Lieldienas
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:Otro Lieldienu dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Darba svētkus
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190506@kayaposoft.com
+DTSTART;VALUE=DATE:20190506
+DTEND;VALUE=DATE:20190507
+SUMMARY:Latvijas Republikas Neatkarības atjaunošanas dienu
+DESCRIPTION:Holiday in lieu of 4 May 2019
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190512@kayaposoft.com
+DTSTART;VALUE=DATE:20190512
+DTEND;VALUE=DATE:20190513
+SUMMARY:Mātes diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190609@kayaposoft.com
+DTSTART;VALUE=DATE:20190609
+DTEND;VALUE=DATE:20190610
+SUMMARY:Vasarsvētkus
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190623@kayaposoft.com
+DTSTART;VALUE=DATE:20190623
+DTEND;VALUE=DATE:20190624
+SUMMARY:Līgo dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20190624@kayaposoft.com
+DTSTART;VALUE=DATE:20190624
+DTEND;VALUE=DATE:20190625
+SUMMARY:Jāņu dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20191118@kayaposoft.com
+DTSTART;VALUE=DATE:20191118
+DTEND;VALUE=DATE:20191119
+SUMMARY:Latvijas Republikas proklamēšanas diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20191224@kayaposoft.com
+DTSTART;VALUE=DATE:20191224
+DTEND;VALUE=DATE:20191225
+SUMMARY:Ziemassvētku vakars
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Ziemassvētki
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:2. Ziemassvētki
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20191231@kayaposoft.com
+DTSTART;VALUE=DATE:20191231
+DTEND;VALUE=DATE:20200101
+SUMMARY:Vecgada vakars
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Jaungada dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200410@kayaposoft.com
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+SUMMARY:Lielo Piektdienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200412@kayaposoft.com
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+SUMMARY:Pirmās Lieldienas
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Otro Lieldienu dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Darba svētkus
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200504@kayaposoft.com
+DTSTART;VALUE=DATE:20200504
+DTEND;VALUE=DATE:20200505
+SUMMARY:Latvijas Republikas Neatkarības atjaunošanas dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200510@kayaposoft.com
+DTSTART;VALUE=DATE:20200510
+DTEND;VALUE=DATE:20200511
+SUMMARY:Mātes diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200531@kayaposoft.com
+DTSTART;VALUE=DATE:20200531
+DTEND;VALUE=DATE:20200601
+SUMMARY:Vasarsvētkus
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200623@kayaposoft.com
+DTSTART;VALUE=DATE:20200623
+DTEND;VALUE=DATE:20200624
+SUMMARY:Līgo dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20200624@kayaposoft.com
+DTSTART;VALUE=DATE:20200624
+DTEND;VALUE=DATE:20200625
+SUMMARY:Jāņu dienu
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20201118@kayaposoft.com
+DTSTART;VALUE=DATE:20201118
+DTEND;VALUE=DATE:20201119
+SUMMARY:Latvijas Republikas proklamēšanas diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20201224@kayaposoft.com
+DTSTART;VALUE=DATE:20201224
+DTEND;VALUE=DATE:20201225
+SUMMARY:Ziemassvētku vakars
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Ziemassvētki
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:2. Ziemassvētki
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lva-20201231@kayaposoft.com
+DTSTART;VALUE=DATE:20201231
+DTEND;VALUE=DATE:20210101
+SUMMARY:Vecgada vakars
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/lithuania.ics b/app/src/main/assets/lithuania.ics
new file mode 100644
index 000000000..7ea76b935
--- /dev/null
+++ b/app/src/main/assets/lithuania.ics
@@ -0,0 +1,317 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-ltu-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Naujieji metai
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180216@kayaposoft.com
+DTSTART;VALUE=DATE:20180216
+DTEND;VALUE=DATE:20180217
+SUMMARY:Lietuvos valstybės atkūrimo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180311@kayaposoft.com
+DTSTART;VALUE=DATE:20180311
+DTEND;VALUE=DATE:20180312
+SUMMARY:Lietuvos nepriklausomybės atkūrimo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180401@kayaposoft.com
+DTSTART;VALUE=DATE:20180401
+DTEND;VALUE=DATE:20180402
+SUMMARY:Velykos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180402@kayaposoft.com
+DTSTART;VALUE=DATE:20180402
+DTEND;VALUE=DATE:20180403
+SUMMARY:Velykos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Tarptautinė darbo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180506@kayaposoft.com
+DTSTART;VALUE=DATE:20180506
+DTEND;VALUE=DATE:20180507
+SUMMARY:Motinos diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180603@kayaposoft.com
+DTSTART;VALUE=DATE:20180603
+DTEND;VALUE=DATE:20180604
+SUMMARY:Tėvo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180624@kayaposoft.com
+DTSTART;VALUE=DATE:20180624
+DTEND;VALUE=DATE:20180625
+SUMMARY:Rasos (Joninės)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180706@kayaposoft.com
+DTSTART;VALUE=DATE:20180706
+DTEND;VALUE=DATE:20180707
+SUMMARY:Valstybės (Lietuvos karaliaus Mindaugo karūnavimo) diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20180815@kayaposoft.com
+DTSTART;VALUE=DATE:20180815
+DTEND;VALUE=DATE:20180816
+SUMMARY:Žolinė (Švč. Mergelės Marijos ėmimo į dangų diena)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20181101@kayaposoft.com
+DTSTART;VALUE=DATE:20181101
+DTEND;VALUE=DATE:20181102
+SUMMARY:Visų šventųjų diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20181224@kayaposoft.com
+DTSTART;VALUE=DATE:20181224
+DTEND;VALUE=DATE:20181225
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Naujieji metai
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190216@kayaposoft.com
+DTSTART;VALUE=DATE:20190216
+DTEND;VALUE=DATE:20190217
+SUMMARY:Lietuvos valstybės atkūrimo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190311@kayaposoft.com
+DTSTART;VALUE=DATE:20190311
+DTEND;VALUE=DATE:20190312
+SUMMARY:Lietuvos nepriklausomybės atkūrimo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190421@kayaposoft.com
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+SUMMARY:Velykos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:Velykos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Tarptautinė darbo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190505@kayaposoft.com
+DTSTART;VALUE=DATE:20190505
+DTEND;VALUE=DATE:20190506
+SUMMARY:Motinos diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190602@kayaposoft.com
+DTSTART;VALUE=DATE:20190602
+DTEND;VALUE=DATE:20190603
+SUMMARY:Tėvo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190624@kayaposoft.com
+DTSTART;VALUE=DATE:20190624
+DTEND;VALUE=DATE:20190625
+SUMMARY:Rasos (Joninės)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190706@kayaposoft.com
+DTSTART;VALUE=DATE:20190706
+DTEND;VALUE=DATE:20190707
+SUMMARY:Valstybės (Lietuvos karaliaus Mindaugo karūnavimo) diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20190815@kayaposoft.com
+DTSTART;VALUE=DATE:20190815
+DTEND;VALUE=DATE:20190816
+SUMMARY:Žolinė (Švč. Mergelės Marijos ėmimo į dangų diena)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20191101@kayaposoft.com
+DTSTART;VALUE=DATE:20191101
+DTEND;VALUE=DATE:20191102
+SUMMARY:Visų šventųjų diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20191224@kayaposoft.com
+DTSTART;VALUE=DATE:20191224
+DTEND;VALUE=DATE:20191225
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Naujieji metai
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200216@kayaposoft.com
+DTSTART;VALUE=DATE:20200216
+DTEND;VALUE=DATE:20200217
+SUMMARY:Lietuvos valstybės atkūrimo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200311@kayaposoft.com
+DTSTART;VALUE=DATE:20200311
+DTEND;VALUE=DATE:20200312
+SUMMARY:Lietuvos nepriklausomybės atkūrimo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200412@kayaposoft.com
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+SUMMARY:Velykos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Velykos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Tarptautinė darbo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200503@kayaposoft.com
+DTSTART;VALUE=DATE:20200503
+DTEND;VALUE=DATE:20200504
+SUMMARY:Motinos diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200607@kayaposoft.com
+DTSTART;VALUE=DATE:20200607
+DTEND;VALUE=DATE:20200608
+SUMMARY:Tėvo diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200624@kayaposoft.com
+DTSTART;VALUE=DATE:20200624
+DTEND;VALUE=DATE:20200625
+SUMMARY:Rasos (Joninės)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200706@kayaposoft.com
+DTSTART;VALUE=DATE:20200706
+DTEND;VALUE=DATE:20200707
+SUMMARY:Valstybės (Lietuvos karaliaus Mindaugo karūnavimo) diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20200815@kayaposoft.com
+DTSTART;VALUE=DATE:20200815
+DTEND;VALUE=DATE:20200816
+SUMMARY:Žolinė (Švč. Mergelės Marijos ėmimo į dangų diena)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20201101@kayaposoft.com
+DTSTART;VALUE=DATE:20201101
+DTEND;VALUE=DATE:20201102
+SUMMARY:Visų šventųjų diena
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20201224@kayaposoft.com
+DTSTART;VALUE=DATE:20201224
+DTEND;VALUE=DATE:20201225
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ltu-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:Šv. Kalėdos
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/luxembourg.ics b/app/src/main/assets/luxembourg.ics
new file mode 100644
index 000000000..985c6fee7
--- /dev/null
+++ b/app/src/main/assets/luxembourg.ics
@@ -0,0 +1,212 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-lux-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Neijoerschdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20180402@kayaposoft.com
+DTSTART;VALUE=DATE:20180402
+DTEND;VALUE=DATE:20180403
+SUMMARY:Ouschterméindeg
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Dag vun der Aarbecht
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20180510@kayaposoft.com
+DTSTART;VALUE=DATE:20180510
+DTEND;VALUE=DATE:20180511
+SUMMARY:Christi Himmelfaart
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20180521@kayaposoft.com
+DTSTART;VALUE=DATE:20180521
+DTEND;VALUE=DATE:20180522
+SUMMARY:Péngschtméindeg
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20180623@kayaposoft.com
+DTSTART;VALUE=DATE:20180623
+DTEND;VALUE=DATE:20180624
+SUMMARY:Nationalfeierdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20180815@kayaposoft.com
+DTSTART;VALUE=DATE:20180815
+DTEND;VALUE=DATE:20180816
+SUMMARY:Mariä Himmelfaart
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20181101@kayaposoft.com
+DTSTART;VALUE=DATE:20181101
+DTEND;VALUE=DATE:20181102
+SUMMARY:Allerhellgen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Chrëschtdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Stiefesdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Neijoerschdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:Ouschterméindeg
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Dag vun der Aarbecht
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20190530@kayaposoft.com
+DTSTART;VALUE=DATE:20190530
+DTEND;VALUE=DATE:20190531
+SUMMARY:Christi Himmelfaart
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20190610@kayaposoft.com
+DTSTART;VALUE=DATE:20190610
+DTEND;VALUE=DATE:20190611
+SUMMARY:Péngschtméindeg
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20190623@kayaposoft.com
+DTSTART;VALUE=DATE:20190623
+DTEND;VALUE=DATE:20190624
+SUMMARY:Nationalfeierdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20190815@kayaposoft.com
+DTSTART;VALUE=DATE:20190815
+DTEND;VALUE=DATE:20190816
+SUMMARY:Mariä Himmelfaart
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20191101@kayaposoft.com
+DTSTART;VALUE=DATE:20191101
+DTEND;VALUE=DATE:20191102
+SUMMARY:Allerhellgen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Chrëschtdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Stiefesdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Neijoerschdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Ouschterméindeg
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Dag vun der Aarbecht
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20200521@kayaposoft.com
+DTSTART;VALUE=DATE:20200521
+DTEND;VALUE=DATE:20200522
+SUMMARY:Christi Himmelfaart
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20200601@kayaposoft.com
+DTSTART;VALUE=DATE:20200601
+DTEND;VALUE=DATE:20200602
+SUMMARY:Péngschtméindeg
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20200623@kayaposoft.com
+DTSTART;VALUE=DATE:20200623
+DTEND;VALUE=DATE:20200624
+SUMMARY:Nationalfeierdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20200815@kayaposoft.com
+DTSTART;VALUE=DATE:20200815
+DTEND;VALUE=DATE:20200816
+SUMMARY:Mariä Himmelfaart
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20201101@kayaposoft.com
+DTSTART;VALUE=DATE:20201101
+DTEND;VALUE=DATE:20201102
+SUMMARY:Allerhellgen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Chrëschtdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-lux-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:Stiefesdag
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/macedonia.ics b/app/src/main/assets/macedonia.ics
new file mode 100644
index 000000000..be8f14122
--- /dev/null
+++ b/app/src/main/assets/macedonia.ics
@@ -0,0 +1,239 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-mkd-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Нова Година\, Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20180108@kayaposoft.com
+DTSTART;VALUE=DATE:20180108
+DTEND;VALUE=DATE:20180109
+SUMMARY:Прв ден Божик\, Prv den Božik
+DESCRIPTION:Holiday in lieu of 7 Jan 2018
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20180409@kayaposoft.com
+DTSTART;VALUE=DATE:20180409
+DTEND;VALUE=DATE:20180410
+SUMMARY:Втор ден Велигден\, Vtor den Veligden
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Ден на трудот\, Den na trudot
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20180524@kayaposoft.com
+DTSTART;VALUE=DATE:20180524
+DTEND;VALUE=DATE:20180525
+SUMMARY:Св. Кирил и Методиј\, Ден на сèсловенските просветители; Sv. Kiril i Metodij\, Den na sèslovenskite prosvetiteli
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20180615@kayaposoft.com
+DTSTART;VALUE=DATE:20180615
+DTEND;VALUE=DATE:20180616
+SUMMARY:Рамазан Бајрам\, Ramazan Bajram
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20180802@kayaposoft.com
+DTSTART;VALUE=DATE:20180802
+DTEND;VALUE=DATE:20180803
+SUMMARY:Ден на Републиката\, Den na Republikata
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20180908@kayaposoft.com
+DTSTART;VALUE=DATE:20180908
+DTEND;VALUE=DATE:20180909
+SUMMARY:Ден на независноста\, Den na nezavisnosta
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20181011@kayaposoft.com
+DTSTART;VALUE=DATE:20181011
+DTEND;VALUE=DATE:20181012
+SUMMARY:Ден на востанието\, Den na vostanieto
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20181023@kayaposoft.com
+DTSTART;VALUE=DATE:20181023
+DTEND;VALUE=DATE:20181024
+SUMMARY:Ден на македонската револуционерна борба\, Den na makedonskata revolucionarna borba
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20181208@kayaposoft.com
+DTSTART;VALUE=DATE:20181208
+DTEND;VALUE=DATE:20181209
+SUMMARY:Св. Климент Охридски\, Sv. Kliment Ohridski
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Нова Година\, Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190107@kayaposoft.com
+DTSTART;VALUE=DATE:20190107
+DTEND;VALUE=DATE:20190108
+SUMMARY:Прв ден Божик\, Prv den Božik
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190429@kayaposoft.com
+DTSTART;VALUE=DATE:20190429
+DTEND;VALUE=DATE:20190430
+SUMMARY:Втор ден Велигден\, Vtor den Veligden
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Ден на трудот\, Den na trudot
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190524@kayaposoft.com
+DTSTART;VALUE=DATE:20190524
+DTEND;VALUE=DATE:20190525
+SUMMARY:Св. Кирил и Методиј\, Ден на сèсловенските просветители; Sv. Kiril i Metodij\, Den na sèslovenskite prosvetiteli
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190604@kayaposoft.com
+DTSTART;VALUE=DATE:20190604
+DTEND;VALUE=DATE:20190605
+SUMMARY:Рамазан Бајрам\, Ramazan Bajram
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190802@kayaposoft.com
+DTSTART;VALUE=DATE:20190802
+DTEND;VALUE=DATE:20190803
+SUMMARY:Ден на Републиката\, Den na Republikata
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20190909@kayaposoft.com
+DTSTART;VALUE=DATE:20190909
+DTEND;VALUE=DATE:20190910
+SUMMARY:Ден на независноста\, Den na nezavisnosta
+DESCRIPTION:Holiday in lieu of 8 Sep 2019
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20191011@kayaposoft.com
+DTSTART;VALUE=DATE:20191011
+DTEND;VALUE=DATE:20191012
+SUMMARY:Ден на востанието\, Den na vostanieto
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20191023@kayaposoft.com
+DTSTART;VALUE=DATE:20191023
+DTEND;VALUE=DATE:20191024
+SUMMARY:Ден на македонската револуционерна борба\, Den na makedonskata revolucionarna borba
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20191209@kayaposoft.com
+DTSTART;VALUE=DATE:20191209
+DTEND;VALUE=DATE:20191210
+SUMMARY:Св. Климент Охридски\, Sv. Kliment Ohridski
+DESCRIPTION:Holiday in lieu of 8 Dec 2019
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Нова Година\, Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200107@kayaposoft.com
+DTSTART;VALUE=DATE:20200107
+DTEND;VALUE=DATE:20200108
+SUMMARY:Прв ден Божик\, Prv den Božik
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200420@kayaposoft.com
+DTSTART;VALUE=DATE:20200420
+DTEND;VALUE=DATE:20200421
+SUMMARY:Втор ден Велигден\, Vtor den Veligden
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Ден на трудот\, Den na trudot
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200524@kayaposoft.com
+DTSTART;VALUE=DATE:20200524
+DTEND;VALUE=DATE:20200525
+SUMMARY:Рамазан Бајрам\, Ramazan Bajram
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200525@kayaposoft.com
+DTSTART;VALUE=DATE:20200525
+DTEND;VALUE=DATE:20200526
+SUMMARY:Св. Кирил и Методиј\, Ден на сèсловенските просветители; Sv. Kiril i Metodij\, Den na sèslovenskite prosvetiteli
+DESCRIPTION:Holiday in lieu of 24 May 2020
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200803@kayaposoft.com
+DTSTART;VALUE=DATE:20200803
+DTEND;VALUE=DATE:20200804
+SUMMARY:Ден на Републиката\, Den na Republikata
+DESCRIPTION:Holiday in lieu of 2 Aug 2020
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20200908@kayaposoft.com
+DTSTART;VALUE=DATE:20200908
+DTEND;VALUE=DATE:20200909
+SUMMARY:Ден на независноста\, Den na nezavisnosta
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20201012@kayaposoft.com
+DTSTART;VALUE=DATE:20201012
+DTEND;VALUE=DATE:20201013
+SUMMARY:Ден на востанието\, Den na vostanieto
+DESCRIPTION:Holiday in lieu of 11 Oct 2020
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20201023@kayaposoft.com
+DTSTART;VALUE=DATE:20201023
+DTEND;VALUE=DATE:20201024
+SUMMARY:Ден на македонската револуционерна борба\, Den na makedonskata revolucionarna borba
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mkd-20201208@kayaposoft.com
+DTSTART;VALUE=DATE:20201208
+DTEND;VALUE=DATE:20201209
+SUMMARY:Св. Климент Охридски\, Sv. Kliment Ohridski
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/malaysia.ics b/app/src/main/assets/malaysia.ics
new file mode 100644
index 000000000..9bc933e40
--- /dev/null
+++ b/app/src/main/assets/malaysia.ics
@@ -0,0 +1,429 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190713
+DTEND;VALUE=DATE:20190714
+UID:20190713_60o30pb3cgo30e1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Penang Governor's Birthday (Penang)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+UID:20180101_60o30db16oo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:New Year's Day (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180531
+DTEND;VALUE=DATE:20180601
+UID:20180531_60o30p9k6oo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Harvest Festival Day 2 (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190605
+DTEND;VALUE=DATE:20190606
+UID:20190605_60o30db260o30c1g60o30db160@google.com
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Puasa
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+UID:20190419_60o30db26so30c1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Good Friday (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180707
+DTEND;VALUE=DATE:20180708
+UID:20180707_60o30pb3cco30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:George Town World Heritage City Day (Penang)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191027
+DTEND;VALUE=DATE:20191028
+UID:20191027_60o30db268o30c1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Diwali/Deepavali (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190506
+DTEND;VALUE=DATE:20190507
+UID:20190506_60o30ob5c4o30c1g60o30db160@google.com
+STATUS:CONFIRMED
+SUMMARY:Ramadan begins (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180530
+DTEND;VALUE=DATE:20180531
+UID:20180530_60o30p9k6ko30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Harvest Festival (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181015
+DTEND;VALUE=DATE:20181016
+UID:20181015_60o32e1icgo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Almarhum Sultan Iskandar Hol Day (Johor)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190603
+DTEND;VALUE=DATE:20190604
+UID:20190603_60o32e1i64o30e1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Gawai Dayak Holiday observed (Sarawak)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181211
+DTEND;VALUE=DATE:20181212
+UID:20181211_60o32e1j68o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Sultan of Selangor (Selangor)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181024
+DTEND;VALUE=DATE:20181025
+UID:20181024_60o32e1icko30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Sultan of Pahang (Pahang)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181111
+DTEND;VALUE=DATE:20181112
+UID:20181111_60o32e1j60o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Sultan of Kelantan (Kelantan)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180323
+DTEND;VALUE=DATE:20180324
+UID:20180323_60o32e1i6go30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Sultan of Johor (Johor)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181013
+DTEND;VALUE=DATE:20181014
+UID:20181013_60o32e1icco30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Governor of Sarawak (Sarawak)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191007
+DTEND;VALUE=DATE:20191008
+UID:20191007_60o32e1ic4o30e1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Birthday of the Governor of Sabah observed (Sabah)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180507
+DTEND;VALUE=DATE:20180508
+UID:20180507_60o32e1i6oo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Pahang State Holiday (Pahang)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190522
+DTEND;VALUE=DATE:20190523
+UID:20190522_60o30ob5c8o30c1g60o30db160@google.com
+STATUS:CONFIRMED
+SUMMARY:Nuzul Al-Quran (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190403
+DTEND;VALUE=DATE:20190404
+UID:20190403_60o30ob574o30c1g60o30db160@google.com
+STATUS:CONFIRMED
+SUMMARY:Isra and Mi'raj (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180415
+DTEND;VALUE=DATE:20180416
+UID:20180415_60o32e1i6so30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Declaration of Malacca as Historical City (Malacca)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180114
+DTEND;VALUE=DATE:20180115
+UID:20180114_60o32e1i6co30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of Yang di-Pertuan Besar (Negeri Sembilan)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180426
+DTEND;VALUE=DATE:20180427
+UID:20180426_60o32e1i70o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Sultan of Terengganu (Terengganu)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180517
+DTEND;VALUE=DATE:20180518
+UID:20180517_60o32e1i68o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Raja of Perlis (Perlis)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181012
+DTEND;VALUE=DATE:20181013
+UID:20181012_60o32e1ic8o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Governor of Malacca (Malacca)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180304
+DTEND;VALUE=DATE:20180305
+UID:20180304_60o32e1i6ko30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Anniversary of the coronation of the Sultan of Terengganu (Terengga
+ nu)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181120
+DTEND;VALUE=DATE:20181121
+UID:20181120_60o30db1c8o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+SUMMARY:The Prophet Muhammad's Birthday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180722
+DTEND;VALUE=DATE:20180723
+UID:20180722_60o32e1i74o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Sarawak Independence Day (Sarawak)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180917
+DTEND;VALUE=DATE:20180918
+UID:20180917_60o30db274o34c1g60o30dr568@google.com
+STATUS:CONFIRMED
+SUMMARY:Malaysia Day (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180916
+DTEND;VALUE=DATE:20180917
+UID:20180916_60o30db274o32c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Malaysia Day (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190813
+DTEND;VALUE=DATE:20190814
+UID:20190813_60o32e1hcko30c1g60o30db160@google.com
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Haji (Day 2) (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180601
+DTEND;VALUE=DATE:20180602
+UID:20180601_60o32e1i60o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Gawai Dayak (Sarawak)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180602
+DTEND;VALUE=DATE:20180603
+UID:20180602_60o32e1i64o30c1g60o30dr568@google.com
+CREATED:20180731T230440Z
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Gawai Dayak Holiday (Sarawak)
+TRANSP:TRANSPARENT
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190603
+DTEND;VALUE=DATE:20190604
+UID:20190603_60o32e1i64o30e1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Gawai Dayak Holiday observed (Sarawak)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181106
+DTEND;VALUE=DATE:20181107
+UID:20181106_60o30db268o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+SUMMARY:Diwali/Deepavali (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181112
+DTEND;VALUE=DATE:20181113
+UID:20181112_60o32e1j64o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Sultan of Kelantan (Day 2) (Kelantan)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180909
+DTEND;VALUE=DATE:20180910
+UID:20180909_60o30db1cko32c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:The Yang di-Pertuan Agong's Birthday (regional holiday)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190427
+DTEND;VALUE=DATE:20190428
+UID:20190427_60o32e1i70o30c1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Birthday of the Sultan of Terengganu (Terengganu)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181102
+DTEND;VALUE=DATE:20181103
+UID:20181102_60o32e1icoo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Sultan of Perak (Perak)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191014
+DTEND;VALUE=DATE:20191015
+UID:20191014_60o32e1icco30e1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Birthday of the Governor of Sarawak observed (Sarawak)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181006
+DTEND;VALUE=DATE:20181007
+UID:20181006_60o32e1ic4o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Birthday of the Governor of Sabah (Sabah)
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180214
+DTEND;VALUE=DATE:20180215
+UID:20180214_60o32dpo68o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Valentine's Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190901
+DTEND;VALUE=DATE:20190902
+UID:20190901_60o30or46oo30c1g60o30db164@google.com
+STATUS:CONFIRMED
+SUMMARY:Muharram/New Year
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190206
+DTEND;VALUE=DATE:20190207
+UID:20190206_60o30db170o30c1g60o32chmcc@google.com
+STATUS:CONFIRMED
+SUMMARY:Second day of Chinese Lunar New Year
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180831
+DTEND;VALUE=DATE:20180901
+UID:20180831_60o30db1coo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Malaysia's National Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+UID:20180501_60o30db1cco30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Labour Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181224
+DTEND;VALUE=DATE:20181225
+UID:20181224_60o30db26oo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Christmas Eve
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+UID:20181225_60o30db26ko30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Christmas Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190205
+DTEND;VALUE=DATE:20190206
+UID:20190205_60o30db16so30c1g60o32chmcc@google.com
+STATUS:CONFIRMED
+SUMMARY:Chinese Lunar New Year's Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191110
+DTEND;VALUE=DATE:20191111
+UID:20191110_60o30db1c8o30c1g60o30db164@google.com
+STATUS:CONFIRMED
+SUMMARY:The Prophet Muhammad's Birthday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180201
+DTEND;VALUE=DATE:20180202
+UID:20180201_60o30db174o30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Federal Territory Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190812
+DTEND;VALUE=DATE:20190813
+UID:20190812_60o30db26co30c1g60o30db160@google.com
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Haji
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190606
+DTEND;VALUE=DATE:20190607
+UID:20190606_60o30db264o30c1g60o30db160@google.com
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Puasa Day 2
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181231
+DTEND;VALUE=DATE:20190101
+UID:20181231_60o30db16ko30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:New Year's Eve
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+UID:20190421_60o30db270o30c1g60o30dr56c@google.com
+STATUS:CONFIRMED
+SUMMARY:Easter Sunday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190519
+DTEND;VALUE=DATE:20190520
+UID:20190519_60o30db1cgo30c1g60o30dr568@google.com
+STATUS:CONFIRMED
+SUMMARY:Wesak Day
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/mexico.ics b/app/src/main/assets/mexico.ics
new file mode 100644
index 000000000..e169ec3f8
--- /dev/null
+++ b/app/src/main/assets/mexico.ics
@@ -0,0 +1,156 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-mex-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Año Nuevo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20180205@kayaposoft.com
+DTSTART;VALUE=DATE:20180205
+DTEND;VALUE=DATE:20180206
+SUMMARY:Día de la Constitución Mexicana
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20180319@kayaposoft.com
+DTSTART;VALUE=DATE:20180319
+DTEND;VALUE=DATE:20180320
+SUMMARY:Natalicio de Benito Juárez
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Día del Trabajo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20180916@kayaposoft.com
+DTSTART;VALUE=DATE:20180916
+DTEND;VALUE=DATE:20180917
+SUMMARY:Día de la Independencia
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20181119@kayaposoft.com
+DTSTART;VALUE=DATE:20181119
+DTEND;VALUE=DATE:20181120
+SUMMARY:Revolución Mexicana
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20181201@kayaposoft.com
+DTSTART;VALUE=DATE:20181201
+DTEND;VALUE=DATE:20181202
+SUMMARY:Transmisión del Poder Ejecutivo Federal
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Día de Navidad
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Año Nuevo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20190204@kayaposoft.com
+DTSTART;VALUE=DATE:20190204
+DTEND;VALUE=DATE:20190205
+SUMMARY:Día de la Constitución Mexicana
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20190318@kayaposoft.com
+DTSTART;VALUE=DATE:20190318
+DTEND;VALUE=DATE:20190319
+SUMMARY:Natalicio de Benito Juárez
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Día del Trabajo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20190916@kayaposoft.com
+DTSTART;VALUE=DATE:20190916
+DTEND;VALUE=DATE:20190917
+SUMMARY:Día de la Independencia
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20191118@kayaposoft.com
+DTSTART;VALUE=DATE:20191118
+DTEND;VALUE=DATE:20191119
+SUMMARY:Revolución Mexicana
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Día de Navidad
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Año Nuevo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20200203@kayaposoft.com
+DTSTART;VALUE=DATE:20200203
+DTEND;VALUE=DATE:20200204
+SUMMARY:Día de la Constitución Mexicana
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20200316@kayaposoft.com
+DTSTART;VALUE=DATE:20200316
+DTEND;VALUE=DATE:20200317
+SUMMARY:Natalicio de Benito Juárez
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Día del Trabajo
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20200916@kayaposoft.com
+DTSTART;VALUE=DATE:20200916
+DTEND;VALUE=DATE:20200917
+SUMMARY:Día de la Independencia
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20201116@kayaposoft.com
+DTSTART;VALUE=DATE:20201116
+DTEND;VALUE=DATE:20201117
+SUMMARY:Revolución Mexicana
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-mex-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Día de Navidad
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/netherlands.ics b/app/src/main/assets/netherlands.ics
index 05c635afc..cf145463c 100755
--- a/app/src/main/assets/netherlands.ics
+++ b/app/src/main/assets/netherlands.ics
@@ -1,1717 +1,198 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0195-nl@katana
-DTSTART;VALUE=DATE:20170525
-DTEND;VALUE=DATE:20170526
+UID:enrico-nld-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Nieuwjaar
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0113-nl@katana
-DTSTART;VALUE=DATE:20170604
-DTEND;VALUE=DATE:20170605
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0314-nl@katana
-DTSTART;VALUE=DATE:20170605
-DTEND;VALUE=DATE:20170606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0314-nl@katana
-DTSTART;VALUE=DATE:20170605
-DTEND;VALUE=DATE:20170606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0032-nl@katana
-DTSTART;VALUE=DATE:20180211
-DTEND;VALUE=DATE:20180214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0155-nl@katana
-DTSTART;VALUE=DATE:20180330
-DTEND;VALUE=DATE:20180331
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0073-nl@katana
+UID:enrico-nld-20180401@kayaposoft.com
DTSTART;VALUE=DATE:20180401
DTEND;VALUE=DATE:20180402
+SUMMARY:Eerste Paasdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0274-nl@katana
+UID:enrico-nld-20180402@kayaposoft.com
DTSTART;VALUE=DATE:20180402
DTEND;VALUE=DATE:20180403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0274-nl@katana
-DTSTART;VALUE=DATE:20180402
-DTEND;VALUE=DATE:20180403
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0233-nl@katana
+UID:enrico-nld-20180427@kayaposoft.com
DTSTART;VALUE=DATE:20180427
DTEND;VALUE=DATE:20180428
+SUMMARY:Koningsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0196-nl@katana
+UID:enrico-nld-20180510@kayaposoft.com
DTSTART;VALUE=DATE:20180510
DTEND;VALUE=DATE:20180511
+SUMMARY:Hemelvaartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0114-nl@katana
+UID:enrico-nld-20180520@kayaposoft.com
DTSTART;VALUE=DATE:20180520
DTEND;VALUE=DATE:20180521
+SUMMARY:Eerste Pinksterdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0315-nl@katana
+UID:enrico-nld-20180521@kayaposoft.com
DTSTART;VALUE=DATE:20180521
DTEND;VALUE=DATE:20180522
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0315-nl@katana
-DTSTART;VALUE=DATE:20180521
-DTEND;VALUE=DATE:20180522
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0033-nl@katana
-DTSTART;VALUE=DATE:20190303
-DTEND;VALUE=DATE:20190306
+UID:enrico-nld-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Eerste Kerstdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0156-nl@katana
-DTSTART;VALUE=DATE:20190419
-DTEND;VALUE=DATE:20190420
+UID:enrico-nld-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Tweede Kerstdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0074-nl@katana
+UID:enrico-nld-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Nieuwjaar
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-nld-20190421@kayaposoft.com
DTSTART;VALUE=DATE:20190421
DTEND;VALUE=DATE:20190422
+SUMMARY:Eerste Paasdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0275-nl@katana
+UID:enrico-nld-20190422@kayaposoft.com
DTSTART;VALUE=DATE:20190422
DTEND;VALUE=DATE:20190423
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0275-nl@katana
-DTSTART;VALUE=DATE:20190422
-DTEND;VALUE=DATE:20190423
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0234-nl@katana
+UID:enrico-nld-20190427@kayaposoft.com
DTSTART;VALUE=DATE:20190427
DTEND;VALUE=DATE:20190428
+SUMMARY:Koningsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0197-nl@katana
+UID:enrico-nld-20190530@kayaposoft.com
DTSTART;VALUE=DATE:20190530
DTEND;VALUE=DATE:20190531
+SUMMARY:Hemelvaartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0115-nl@katana
+UID:enrico-nld-20190609@kayaposoft.com
DTSTART;VALUE=DATE:20190609
DTEND;VALUE=DATE:20190610
+SUMMARY:Eerste Pinksterdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0316-nl@katana
+UID:enrico-nld-20190610@kayaposoft.com
DTSTART;VALUE=DATE:20190610
DTEND;VALUE=DATE:20190611
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0316-nl@katana
-DTSTART;VALUE=DATE:20190610
-DTEND;VALUE=DATE:20190611
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0034-nl@katana
-DTSTART;VALUE=DATE:20200223
-DTEND;VALUE=DATE:20200226
+UID:enrico-nld-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Eerste Kerstdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0157-nl@katana
-DTSTART;VALUE=DATE:20200410
-DTEND;VALUE=DATE:20200411
+UID:enrico-nld-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Tweede Kerstdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0075-nl@katana
+UID:enrico-nld-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Nieuwjaar
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-nld-20200412@kayaposoft.com
DTSTART;VALUE=DATE:20200412
DTEND;VALUE=DATE:20200413
+SUMMARY:Eerste Paasdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0276-nl@katana
+UID:enrico-nld-20200413@kayaposoft.com
DTSTART;VALUE=DATE:20200413
DTEND;VALUE=DATE:20200414
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0276-nl@katana
-DTSTART;VALUE=DATE:20200413
-DTEND;VALUE=DATE:20200414
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0235-nl@katana
+UID:enrico-nld-20200427@kayaposoft.com
DTSTART;VALUE=DATE:20200427
DTEND;VALUE=DATE:20200428
+SUMMARY:Koningsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0198-nl@katana
+UID:enrico-nld-20200505@kayaposoft.com
+DTSTART;VALUE=DATE:20200505
+DTEND;VALUE=DATE:20200506
+SUMMARY:Bevrijdingsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-nld-20200521@kayaposoft.com
DTSTART;VALUE=DATE:20200521
DTEND;VALUE=DATE:20200522
+SUMMARY:Hemelvaartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0116-nl@katana
+UID:enrico-nld-20200531@kayaposoft.com
DTSTART;VALUE=DATE:20200531
DTEND;VALUE=DATE:20200601
+SUMMARY:Eerste Pinksterdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0317-nl@katana
+UID:enrico-nld-20200601@kayaposoft.com
DTSTART;VALUE=DATE:20200601
DTEND;VALUE=DATE:20200602
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0317-nl@katana
-DTSTART;VALUE=DATE:20200601
-DTEND;VALUE=DATE:20200602
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0035-nl@katana
-DTSTART;VALUE=DATE:20210214
-DTEND;VALUE=DATE:20210217
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0158-nl@katana
-DTSTART;VALUE=DATE:20210402
-DTEND;VALUE=DATE:20210403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0076-nl@katana
-DTSTART;VALUE=DATE:20210404
-DTEND;VALUE=DATE:20210405
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0277-nl@katana
-DTSTART;VALUE=DATE:20210405
-DTEND;VALUE=DATE:20210406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0277-nl@katana
-DTSTART;VALUE=DATE:20210405
-DTEND;VALUE=DATE:20210406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0236-nl@katana
-DTSTART;VALUE=DATE:20210427
-DTEND;VALUE=DATE:20210428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0199-nl@katana
-DTSTART;VALUE=DATE:20210513
-DTEND;VALUE=DATE:20210514
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0117-nl@katana
-DTSTART;VALUE=DATE:20210523
-DTEND;VALUE=DATE:20210524
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0318-nl@katana
-DTSTART;VALUE=DATE:20210524
-DTEND;VALUE=DATE:20210525
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0318-nl@katana
-DTSTART;VALUE=DATE:20210524
-DTEND;VALUE=DATE:20210525
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0036-nl@katana
-DTSTART;VALUE=DATE:20220227
-DTEND;VALUE=DATE:20220302
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0077-nl@katana
-DTSTART;VALUE=DATE:20220417
-DTEND;VALUE=DATE:20220418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0278-nl@katana
-DTSTART;VALUE=DATE:20220418
-DTEND;VALUE=DATE:20220419
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0278-nl@katana
-DTSTART;VALUE=DATE:20220418
-DTEND;VALUE=DATE:20220419
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0237-nl@katana
-DTSTART;VALUE=DATE:20220427
-DTEND;VALUE=DATE:20220428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0237-nl@katana
-DTSTART;VALUE=DATE:20220427
-DTEND;VALUE=DATE:20220428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0200-nl@katana
-DTSTART;VALUE=DATE:20220526
-DTEND;VALUE=DATE:20220527
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0118-nl@katana
-DTSTART;VALUE=DATE:20220605
-DTEND;VALUE=DATE:20220606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0319-nl@katana
-DTSTART;VALUE=DATE:20220606
-DTEND;VALUE=DATE:20220607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0319-nl@katana
-DTSTART;VALUE=DATE:20220606
-DTEND;VALUE=DATE:20220607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0037-nl@katana
-DTSTART;VALUE=DATE:20230219
-DTEND;VALUE=DATE:20230222
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0078-nl@katana
-DTSTART;VALUE=DATE:20230409
-DTEND;VALUE=DATE:20230410
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0160-nl@katana
-DTSTART;VALUE=DATE:20230409
-DTEND;VALUE=DATE:20230410
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0279-nl@katana
-DTSTART;VALUE=DATE:20230410
-DTEND;VALUE=DATE:20230411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0279-nl@katana
-DTSTART;VALUE=DATE:20230410
-DTEND;VALUE=DATE:20230411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0238-nl@katana
-DTSTART;VALUE=DATE:20230427
-DTEND;VALUE=DATE:20230428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0238-nl@katana
-DTSTART;VALUE=DATE:20230427
-DTEND;VALUE=DATE:20230428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0201-nl@katana
-DTSTART;VALUE=DATE:20230518
-DTEND;VALUE=DATE:20230519
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0119-nl@katana
-DTSTART;VALUE=DATE:20230528
-DTEND;VALUE=DATE:20230529
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0320-nl@katana
-DTSTART;VALUE=DATE:20230529
-DTEND;VALUE=DATE:20230530
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0320-nl@katana
-DTSTART;VALUE=DATE:20230529
-DTEND;VALUE=DATE:20230530
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0038-nl@katana
-DTSTART;VALUE=DATE:20240211
-DTEND;VALUE=DATE:20240214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0280-nl@katana
-DTSTART;VALUE=DATE:20240401
-DTEND;VALUE=DATE:20240402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0280-nl@katana
-DTSTART;VALUE=DATE:20240401
-DTEND;VALUE=DATE:20240402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0239-nl@katana
-DTSTART;VALUE=DATE:20240427
-DTEND;VALUE=DATE:20240428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0239-nl@katana
-DTSTART;VALUE=DATE:20240427
-DTEND;VALUE=DATE:20240428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0202-nl@katana
-DTSTART;VALUE=DATE:20240509
-DTEND;VALUE=DATE:20240510
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0120-nl@katana
-DTSTART;VALUE=DATE:20240519
-DTEND;VALUE=DATE:20240520
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0321-nl@katana
-DTSTART;VALUE=DATE:20240520
-DTEND;VALUE=DATE:20240521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0321-nl@katana
-DTSTART;VALUE=DATE:20240520
-DTEND;VALUE=DATE:20240521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0039-nl@katana
-DTSTART;VALUE=DATE:20250302
-DTEND;VALUE=DATE:20250305
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0080-nl@katana
-DTSTART;VALUE=DATE:20250420
-DTEND;VALUE=DATE:20250421
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0162-nl@katana
-DTSTART;VALUE=DATE:20250420
-DTEND;VALUE=DATE:20250421
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0281-nl@katana
-DTSTART;VALUE=DATE:20250421
-DTEND;VALUE=DATE:20250422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0281-nl@katana
-DTSTART;VALUE=DATE:20250421
-DTEND;VALUE=DATE:20250422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0240-nl@katana
-DTSTART;VALUE=DATE:20250426
-DTEND;VALUE=DATE:20250427
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0240-nl@katana
-DTSTART;VALUE=DATE:20250426
-DTEND;VALUE=DATE:20250427
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0203-nl@katana
-DTSTART;VALUE=DATE:20250529
-DTEND;VALUE=DATE:20250530
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0121-nl@katana
-DTSTART;VALUE=DATE:20250608
-DTEND;VALUE=DATE:20250609
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0322-nl@katana
-DTSTART;VALUE=DATE:20250609
-DTEND;VALUE=DATE:20250610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0322-nl@katana
-DTSTART;VALUE=DATE:20250609
-DTEND;VALUE=DATE:20250610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0040-nl@katana
-DTSTART;VALUE=DATE:20260215
-DTEND;VALUE=DATE:20260218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0081-nl@katana
-DTSTART;VALUE=DATE:20260405
-DTEND;VALUE=DATE:20260406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0282-nl@katana
-DTSTART;VALUE=DATE:20260406
-DTEND;VALUE=DATE:20260407
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0282-nl@katana
-DTSTART;VALUE=DATE:20260406
-DTEND;VALUE=DATE:20260407
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0163-nl@katana
-DTSTART;VALUE=DATE:20260412
-DTEND;VALUE=DATE:20260413
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0241-nl@katana
-DTSTART;VALUE=DATE:20260427
-DTEND;VALUE=DATE:20260428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0241-nl@katana
-DTSTART;VALUE=DATE:20260427
-DTEND;VALUE=DATE:20260428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0204-nl@katana
-DTSTART;VALUE=DATE:20260514
-DTEND;VALUE=DATE:20260515
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0122-nl@katana
-DTSTART;VALUE=DATE:20260524
-DTEND;VALUE=DATE:20260525
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0323-nl@katana
-DTSTART;VALUE=DATE:20260525
-DTEND;VALUE=DATE:20260526
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0323-nl@katana
-DTSTART;VALUE=DATE:20260525
-DTEND;VALUE=DATE:20260526
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0041-nl@katana
-DTSTART;VALUE=DATE:20270207
-DTEND;VALUE=DATE:20270210
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0283-nl@katana
-DTSTART;VALUE=DATE:20270329
-DTEND;VALUE=DATE:20270330
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0283-nl@katana
-DTSTART;VALUE=DATE:20270329
-DTEND;VALUE=DATE:20270330
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0242-nl@katana
-DTSTART;VALUE=DATE:20270427
-DTEND;VALUE=DATE:20270428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0242-nl@katana
-DTSTART;VALUE=DATE:20270427
-DTEND;VALUE=DATE:20270428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0205-nl@katana
-DTSTART;VALUE=DATE:20270506
-DTEND;VALUE=DATE:20270507
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0123-nl@katana
-DTSTART;VALUE=DATE:20270516
-DTEND;VALUE=DATE:20270517
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0324-nl@katana
-DTSTART;VALUE=DATE:20270517
-DTEND;VALUE=DATE:20270518
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0324-nl@katana
-DTSTART;VALUE=DATE:20270517
-DTEND;VALUE=DATE:20270518
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0042-nl@katana
-DTSTART;VALUE=DATE:20280227
-DTEND;VALUE=DATE:20280301
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0083-nl@katana
-DTSTART;VALUE=DATE:20280416
-DTEND;VALUE=DATE:20280417
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0165-nl@katana
-DTSTART;VALUE=DATE:20280416
-DTEND;VALUE=DATE:20280417
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0284-nl@katana
-DTSTART;VALUE=DATE:20280417
-DTEND;VALUE=DATE:20280418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0284-nl@katana
-DTSTART;VALUE=DATE:20280417
-DTEND;VALUE=DATE:20280418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0243-nl@katana
-DTSTART;VALUE=DATE:20280427
-DTEND;VALUE=DATE:20280428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0243-nl@katana
-DTSTART;VALUE=DATE:20280427
-DTEND;VALUE=DATE:20280428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0206-nl@katana
-DTSTART;VALUE=DATE:20280525
-DTEND;VALUE=DATE:20280526
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0124-nl@katana
-DTSTART;VALUE=DATE:20280604
-DTEND;VALUE=DATE:20280605
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0325-nl@katana
-DTSTART;VALUE=DATE:20280605
-DTEND;VALUE=DATE:20280606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0325-nl@katana
-DTSTART;VALUE=DATE:20280605
-DTEND;VALUE=DATE:20280606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0043-nl@katana
-DTSTART;VALUE=DATE:20290211
-DTEND;VALUE=DATE:20290214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0084-nl@katana
-DTSTART;VALUE=DATE:20290401
-DTEND;VALUE=DATE:20290402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0285-nl@katana
-DTSTART;VALUE=DATE:20290402
-DTEND;VALUE=DATE:20290403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0285-nl@katana
-DTSTART;VALUE=DATE:20290402
-DTEND;VALUE=DATE:20290403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0166-nl@katana
-DTSTART;VALUE=DATE:20290408
-DTEND;VALUE=DATE:20290409
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0244-nl@katana
-DTSTART;VALUE=DATE:20290427
-DTEND;VALUE=DATE:20290428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0244-nl@katana
-DTSTART;VALUE=DATE:20290427
-DTEND;VALUE=DATE:20290428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0207-nl@katana
-DTSTART;VALUE=DATE:20290510
-DTEND;VALUE=DATE:20290511
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0125-nl@katana
-DTSTART;VALUE=DATE:20290520
-DTEND;VALUE=DATE:20290521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0326-nl@katana
-DTSTART;VALUE=DATE:20290521
-DTEND;VALUE=DATE:20290522
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0326-nl@katana
-DTSTART;VALUE=DATE:20290521
-DTEND;VALUE=DATE:20290522
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0044-nl@katana
-DTSTART;VALUE=DATE:20300303
-DTEND;VALUE=DATE:20300306
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0167-nl@katana
-DTSTART;VALUE=DATE:20300324
-DTEND;VALUE=DATE:20300325
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0085-nl@katana
-DTSTART;VALUE=DATE:20300421
-DTEND;VALUE=DATE:20300422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0286-nl@katana
-DTSTART;VALUE=DATE:20300422
-DTEND;VALUE=DATE:20300423
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0286-nl@katana
-DTSTART;VALUE=DATE:20300422
-DTEND;VALUE=DATE:20300423
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0245-nl@katana
-DTSTART;VALUE=DATE:20300427
-DTEND;VALUE=DATE:20300428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0245-nl@katana
-DTSTART;VALUE=DATE:20300427
-DTEND;VALUE=DATE:20300428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0208-nl@katana
-DTSTART;VALUE=DATE:20300530
-DTEND;VALUE=DATE:20300531
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0126-nl@katana
-DTSTART;VALUE=DATE:20300609
-DTEND;VALUE=DATE:20300610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0327-nl@katana
-DTSTART;VALUE=DATE:20300610
-DTEND;VALUE=DATE:20300611
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0327-nl@katana
-DTSTART;VALUE=DATE:20300610
-DTEND;VALUE=DATE:20300611
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0045-nl@katana
-DTSTART;VALUE=DATE:20310223
-DTEND;VALUE=DATE:20310226
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0086-nl@katana
-DTSTART;VALUE=DATE:20310413
-DTEND;VALUE=DATE:20310414
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0168-nl@katana
-DTSTART;VALUE=DATE:20310413
-DTEND;VALUE=DATE:20310414
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0287-nl@katana
-DTSTART;VALUE=DATE:20310414
-DTEND;VALUE=DATE:20310415
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0287-nl@katana
-DTSTART;VALUE=DATE:20310414
-DTEND;VALUE=DATE:20310415
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0246-nl@katana
-DTSTART;VALUE=DATE:20310426
-DTEND;VALUE=DATE:20310427
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0246-nl@katana
-DTSTART;VALUE=DATE:20310426
-DTEND;VALUE=DATE:20310427
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0209-nl@katana
-DTSTART;VALUE=DATE:20310522
-DTEND;VALUE=DATE:20310523
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0127-nl@katana
-DTSTART;VALUE=DATE:20310601
-DTEND;VALUE=DATE:20310602
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0328-nl@katana
-DTSTART;VALUE=DATE:20310602
-DTEND;VALUE=DATE:20310603
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0328-nl@katana
-DTSTART;VALUE=DATE:20310602
-DTEND;VALUE=DATE:20310603
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0046-nl@katana
-DTSTART;VALUE=DATE:20320208
-DTEND;VALUE=DATE:20320211
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0288-nl@katana
-DTSTART;VALUE=DATE:20320329
-DTEND;VALUE=DATE:20320330
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0288-nl@katana
-DTSTART;VALUE=DATE:20320329
-DTEND;VALUE=DATE:20320330
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0169-nl@katana
-DTSTART;VALUE=DATE:20320404
-DTEND;VALUE=DATE:20320405
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0247-nl@katana
-DTSTART;VALUE=DATE:20320427
-DTEND;VALUE=DATE:20320428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0247-nl@katana
-DTSTART;VALUE=DATE:20320427
-DTEND;VALUE=DATE:20320428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0210-nl@katana
-DTSTART;VALUE=DATE:20320506
-DTEND;VALUE=DATE:20320507
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0128-nl@katana
-DTSTART;VALUE=DATE:20320516
-DTEND;VALUE=DATE:20320517
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0329-nl@katana
-DTSTART;VALUE=DATE:20320517
-DTEND;VALUE=DATE:20320518
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0329-nl@katana
-DTSTART;VALUE=DATE:20320517
-DTEND;VALUE=DATE:20320518
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0047-nl@katana
-DTSTART;VALUE=DATE:20330227
-DTEND;VALUE=DATE:20330302
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0088-nl@katana
-DTSTART;VALUE=DATE:20330417
-DTEND;VALUE=DATE:20330418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0170-nl@katana
-DTSTART;VALUE=DATE:20330417
-DTEND;VALUE=DATE:20330418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0289-nl@katana
-DTSTART;VALUE=DATE:20330418
-DTEND;VALUE=DATE:20330419
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0289-nl@katana
-DTSTART;VALUE=DATE:20330418
-DTEND;VALUE=DATE:20330419
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0248-nl@katana
-DTSTART;VALUE=DATE:20330427
-DTEND;VALUE=DATE:20330428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0248-nl@katana
-DTSTART;VALUE=DATE:20330427
-DTEND;VALUE=DATE:20330428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0211-nl@katana
-DTSTART;VALUE=DATE:20330526
-DTEND;VALUE=DATE:20330527
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0129-nl@katana
-DTSTART;VALUE=DATE:20330605
-DTEND;VALUE=DATE:20330606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0330-nl@katana
-DTSTART;VALUE=DATE:20330606
-DTEND;VALUE=DATE:20330607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0330-nl@katana
-DTSTART;VALUE=DATE:20330606
-DTEND;VALUE=DATE:20330607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0048-nl@katana
-DTSTART;VALUE=DATE:20340219
-DTEND;VALUE=DATE:20340222
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0089-nl@katana
-DTSTART;VALUE=DATE:20340409
-DTEND;VALUE=DATE:20340410
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0171-nl@katana
-DTSTART;VALUE=DATE:20340409
-DTEND;VALUE=DATE:20340410
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0290-nl@katana
-DTSTART;VALUE=DATE:20340410
-DTEND;VALUE=DATE:20340411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0290-nl@katana
-DTSTART;VALUE=DATE:20340410
-DTEND;VALUE=DATE:20340411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0249-nl@katana
-DTSTART;VALUE=DATE:20340427
-DTEND;VALUE=DATE:20340428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0249-nl@katana
-DTSTART;VALUE=DATE:20340427
-DTEND;VALUE=DATE:20340428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0212-nl@katana
-DTSTART;VALUE=DATE:20340518
-DTEND;VALUE=DATE:20340519
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0130-nl@katana
-DTSTART;VALUE=DATE:20340528
-DTEND;VALUE=DATE:20340529
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0331-nl@katana
-DTSTART;VALUE=DATE:20340529
-DTEND;VALUE=DATE:20340530
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0331-nl@katana
-DTSTART;VALUE=DATE:20340529
-DTEND;VALUE=DATE:20340530
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0049-nl@katana
-DTSTART;VALUE=DATE:20350204
-DTEND;VALUE=DATE:20350207
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0291-nl@katana
-DTSTART;VALUE=DATE:20350326
-DTEND;VALUE=DATE:20350327
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0291-nl@katana
-DTSTART;VALUE=DATE:20350326
-DTEND;VALUE=DATE:20350327
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0172-nl@katana
-DTSTART;VALUE=DATE:20350401
-DTEND;VALUE=DATE:20350402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0250-nl@katana
-DTSTART;VALUE=DATE:20350427
-DTEND;VALUE=DATE:20350428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0250-nl@katana
-DTSTART;VALUE=DATE:20350427
-DTEND;VALUE=DATE:20350428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0213-nl@katana
-DTSTART;VALUE=DATE:20350503
-DTEND;VALUE=DATE:20350504
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0131-nl@katana
-DTSTART;VALUE=DATE:20350513
-DTEND;VALUE=DATE:20350514
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0332-nl@katana
-DTSTART;VALUE=DATE:20350514
-DTEND;VALUE=DATE:20350515
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0332-nl@katana
-DTSTART;VALUE=DATE:20350514
-DTEND;VALUE=DATE:20350515
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0050-nl@katana
-DTSTART;VALUE=DATE:20360224
-DTEND;VALUE=DATE:20360227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0091-nl@katana
-DTSTART;VALUE=DATE:20360413
-DTEND;VALUE=DATE:20360414
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0292-nl@katana
-DTSTART;VALUE=DATE:20360414
-DTEND;VALUE=DATE:20360415
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0292-nl@katana
-DTSTART;VALUE=DATE:20360414
-DTEND;VALUE=DATE:20360415
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0173-nl@katana
-DTSTART;VALUE=DATE:20360420
-DTEND;VALUE=DATE:20360421
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0251-nl@katana
-DTSTART;VALUE=DATE:20360426
-DTEND;VALUE=DATE:20360427
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0251-nl@katana
-DTSTART;VALUE=DATE:20360426
-DTEND;VALUE=DATE:20360427
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0214-nl@katana
-DTSTART;VALUE=DATE:20360522
-DTEND;VALUE=DATE:20360523
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0132-nl@katana
-DTSTART;VALUE=DATE:20360601
-DTEND;VALUE=DATE:20360602
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0333-nl@katana
-DTSTART;VALUE=DATE:20360602
-DTEND;VALUE=DATE:20360603
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0333-nl@katana
-DTSTART;VALUE=DATE:20360602
-DTEND;VALUE=DATE:20360603
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Carnaval
-UID:20161002T100016Z-26501-0051-nl@katana
-DTSTART;VALUE=DATE:20370215
-DTEND;VALUE=DATE:20370218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Paasdag
-UID:20161002T100016Z-26501-0092-nl@katana
-DTSTART;VALUE=DATE:20370405
-DTEND;VALUE=DATE:20370406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede Vrijdag
-UID:20161002T100016Z-26501-0174-nl@katana
-DTSTART;VALUE=DATE:20370405
-DTEND;VALUE=DATE:20370406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0293-nl@katana
-DTSTART;VALUE=DATE:20370406
-DTEND;VALUE=DATE:20370407
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Paasdag
-UID:20161002T100016Z-26501-0293-nl@katana
-DTSTART;VALUE=DATE:20370406
-DTEND;VALUE=DATE:20370407
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0252-nl@katana
-DTSTART;VALUE=DATE:20370427
-DTEND;VALUE=DATE:20370428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20161002T100016Z-26501-0252-nl@katana
-DTSTART;VALUE=DATE:20370427
-DTEND;VALUE=DATE:20370428
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Hemelvaartsdag
-UID:20161002T100016Z-26501-0215-nl@katana
-DTSTART;VALUE=DATE:20370514
-DTEND;VALUE=DATE:20370515
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Eerste Pinksterdag
-UID:20161002T100016Z-26501-0133-nl@katana
-DTSTART;VALUE=DATE:20370524
-DTEND;VALUE=DATE:20370525
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0334-nl@katana
-DTSTART;VALUE=DATE:20370525
-DTEND;VALUE=DATE:20370526
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tweede Pinksterdag
-UID:20161002T100016Z-26501-0334-nl@katana
-DTSTART;VALUE=DATE:20370525
-DTEND;VALUE=DATE:20370526
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nieuwjaarsdag
-UID:20161002T100016Z-26501-0012-nl@katana
-DTSTART;VALUE=DATE:20100101
-DTEND;VALUE=DATE:20100102
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Driekoningen
-UID:20161002T100016Z-26501-0005-nl@katana
-DTSTART;VALUE=DATE:20100106
-DTEND;VALUE=DATE:20100107
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Valentijnsdag
-UID:20161002T100016Z-26501-0020-nl@katana
-DTSTART;VALUE=DATE:20100214
-DTEND;VALUE=DATE:20100215
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Secretaressedag
-UID:20161002T100016Z-26501-0016-nl@katana
-DTSTART;VALUE=DATE:20100415
-DTEND;VALUE=DATE:20100416
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dag van de Arbeid
-UID:20161002T100016Z-26501-0002-nl@katana
-DTSTART;VALUE=DATE:20100501
-DTEND;VALUE=DATE:20100502
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dodenherdenking
-UID:20161002T100016Z-26501-0004-nl@katana
-DTSTART;VALUE=DATE:20100504
-DTEND;VALUE=DATE:20100505
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Bevrijdingsdag
-UID:20161002T100016Z-26501-0001-nl@katana
-DTSTART;VALUE=DATE:20100505
-DTEND;VALUE=DATE:20100506
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Moederdag
-UID:20161002T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20100509
-DTEND;VALUE=DATE:20100510
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Vaderdag
-UID:20161002T100016Z-26501-0019-nl@katana
-DTSTART;VALUE=DATE:20100620
-DTEND;VALUE=DATE:20100621
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Veteranendag
-UID:20161002T100016Z-26501-0021-nl@katana
-DTSTART;VALUE=DATE:20100626
-DTEND;VALUE=DATE:20100627
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Gronings Ontzet
-UID:20161002T100016Z-26501-0007-nl@katana
-DTSTART;VALUE=DATE:20100828
-DTEND;VALUE=DATE:20100829
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Prinsjesdag
-UID:20161002T100016Z-26501-0015-nl@katana
-DTSTART;VALUE=DATE:20100921
-DTEND;VALUE=DATE:20100922
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Leids Ontzet
-UID:20161002T100016Z-26501-0009-nl@katana
-DTSTART;VALUE=DATE:20101003
-DTEND;VALUE=DATE:20101004
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dierendag
-UID:20161002T100016Z-26501-0003-nl@katana
-DTSTART;VALUE=DATE:20101004
-DTEND;VALUE=DATE:20101005
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Wintertijd - uur achteruit
-UID:20161002T100016Z-26501-0022-nl@katana
-DTSTART;VALUE=DATE:20101031
-DTEND;VALUE=DATE:20101101
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sint-Maarten
-UID:20161002T100016Z-26501-0017-nl@katana
-DTSTART;VALUE=DATE:20101111
-DTEND;VALUE=DATE:20101112
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Naturalisatiedag
-UID:20161002T100016Z-26501-0011-nl@katana
-DTSTART;VALUE=DATE:20101115
-DTEND;VALUE=DATE:20101116
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pakjesavond
-UID:20161002T100016Z-26501-0014-nl@katana
-DTSTART;VALUE=DATE:20101205
-DTEND;VALUE=DATE:20101206
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
+UID:enrico-nld-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
SUMMARY:Eerste Kerstdag
-UID:20161002T100016Z-26501-0006-nl@katana
-DTSTART;VALUE=DATE:20101225
-DTEND;VALUE=DATE:20101226
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
+UID:enrico-nld-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
SUMMARY:Tweede Kerstdag
-UID:20161002T100016Z-26501-0018-nl@katana
-DTSTART;VALUE=DATE:20101226
-DTEND;VALUE=DATE:20101227
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Oudejaarsavond
-UID:20161002T100016Z-26501-0013-nl@katana
-DTSTART;VALUE=DATE:20101231
-DTEND;VALUE=DATE:20110101
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Drie koningen
-UID:20100106T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20100106
-DTEND;VALUE=DATE:20100107
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Zomertijd
-UID:20160526T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20100526
-DTEND;VALUE=DATE:20100527
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Goede vrijdag
-UID:20160414T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20100414
-DTEND;VALUE=DATE:20100415
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Koningsdag
-UID:20160427T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20100427
-DTEND;VALUE=DATE:20100428
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Dierendag
-UID:20161004T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20101004
-DTEND;VALUE=DATE:20101005
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Wintertijd
-UID:20161029T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20101029
-DTEND;VALUE=DATE:20101030
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sint Maarten
-UID:20161111T100016Z-26501-0010-nl@katana
-DTSTART;VALUE=DATE:20101111
-DTEND;VALUE=DATE:20101112
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/assets/nigeria.ics b/app/src/main/assets/nigeria.ics
new file mode 100644
index 000000000..95d6da2ea
--- /dev/null
+++ b/app/src/main/assets/nigeria.ics
@@ -0,0 +1,70 @@
+BEGIN:VCALENDER
+BEGIN:VEVENT
+SUMMARY:New Year's Day
+UID:nig-20200101
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+DTEND;VALUE=DATE:20200414
+DTSTART;VALUE=DATE:20200413
+SUMMARY:Easter Monday
+UID:nig-20200413
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+DTEND;VALUE=DATE:20200411
+DTSTART;VALUE=DATE:20200410
+SUMMARY:Good Friday
+UID:nig-20200410
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Christmas Day
+UID:nig-20201225
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Democracy Day
+UID:nig-20200612
+DTEND;VALUE=DATE:20200613
+DTSTART;VALUE=DATE:20200612
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Sallah
+UID:nig-20200525
+DTEND;VALUE=DATE:20200526
+DTSTART;VALUE=DATE:20200525
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY: Worker's Day
+UID:nig-20200501
+DTEND;VALUE=DATE:20200502
+DTSTART;VALUE=DATE:20200501
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Independence Day
+UID:nig-20201001
+DTEND;VALUE=DATE:20201002
+DTSTART;VALUE=DATE:20201001
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Boxing Day
+UID:nig-20201226
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+STATUS:CONFIRMED
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+END:VCALENDER
diff --git a/app/src/main/assets/norway.ics b/app/src/main/assets/norway.ics
index 88f4edc2e..a7c0e8e96 100755
--- a/app/src/main/assets/norway.ics
+++ b/app/src/main/assets/norway.ics
@@ -1,420 +1,2102 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
-SUMMARY:Kristi himmelfartsdag
-UID:7f15028e-3ae0-43f8-bf9b-0e4a672f6fe5
+UID:nor-20110101
+DTSTART;VALUE=DATE:20110101
+DTEND;VALUE=DATE:20110102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110421
+DTSTART;VALUE=DATE:20110421
+DTEND;VALUE=DATE:20110422
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110422
+DTSTART;VALUE=DATE:20110422
+DTEND;VALUE=DATE:20110423
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110424
+DTSTART;VALUE=DATE:20110424
+DTEND;VALUE=DATE:20110425
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110425
+DTSTART;VALUE=DATE:20110425
+DTEND;VALUE=DATE:20110426
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110501
+DTSTART;VALUE=DATE:20110501
+DTEND;VALUE=DATE:20110502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110517
+DTSTART;VALUE=DATE:20110517
+DTEND;VALUE=DATE:20110518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110602
+DTSTART;VALUE=DATE:20110602
+DTEND;VALUE=DATE:20110603
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110612
+DTSTART;VALUE=DATE:20110612
+DTEND;VALUE=DATE:20110613
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20110613
+DTSTART;VALUE=DATE:20110613
+DTEND;VALUE=DATE:20110614
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20111225
+DTSTART;VALUE=DATE:20111225
+DTEND;VALUE=DATE:20111226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20111226
+DTSTART;VALUE=DATE:20111226
+DTEND;VALUE=DATE:20111227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120101
+DTSTART;VALUE=DATE:20120101
+DTEND;VALUE=DATE:20120102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120405
+DTSTART;VALUE=DATE:20120405
+DTEND;VALUE=DATE:20120406
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120406
+DTSTART;VALUE=DATE:20120406
+DTEND;VALUE=DATE:20120407
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120408
+DTSTART;VALUE=DATE:20120408
+DTEND;VALUE=DATE:20120409
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120409
+DTSTART;VALUE=DATE:20120409
+DTEND;VALUE=DATE:20120410
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120501
+DTSTART;VALUE=DATE:20120501
+DTEND;VALUE=DATE:20120502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120517
+DTSTART;VALUE=DATE:20120517
+DTEND;VALUE=DATE:20120518
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120517-660293
+DTSTART;VALUE=DATE:20120517
+DTEND;VALUE=DATE:20120518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120527
+DTSTART;VALUE=DATE:20120527
+DTEND;VALUE=DATE:20120528
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20120528
+DTSTART;VALUE=DATE:20120528
+DTEND;VALUE=DATE:20120529
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20121225
+DTSTART;VALUE=DATE:20121225
+DTEND;VALUE=DATE:20121226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20121226
+DTSTART;VALUE=DATE:20121226
+DTEND;VALUE=DATE:20121227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130101
+DTSTART;VALUE=DATE:20130101
+DTEND;VALUE=DATE:20130102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130328
+DTSTART;VALUE=DATE:20130328
+DTEND;VALUE=DATE:20130329
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130329
+DTSTART;VALUE=DATE:20130329
+DTEND;VALUE=DATE:20130330
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130331
+DTSTART;VALUE=DATE:20130331
+DTEND;VALUE=DATE:20130401
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130401
+DTSTART;VALUE=DATE:20130401
+DTEND;VALUE=DATE:20130402
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130501
+DTSTART;VALUE=DATE:20130501
+DTEND;VALUE=DATE:20130502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130509
+DTSTART;VALUE=DATE:20130509
+DTEND;VALUE=DATE:20130510
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130517
+DTSTART;VALUE=DATE:20130517
+DTEND;VALUE=DATE:20130518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130519
+DTSTART;VALUE=DATE:20130519
+DTEND;VALUE=DATE:20130520
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20130520
+DTSTART;VALUE=DATE:20130520
+DTEND;VALUE=DATE:20130521
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20131225
+DTSTART;VALUE=DATE:20131225
+DTEND;VALUE=DATE:20131226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20131226
+DTSTART;VALUE=DATE:20131226
+DTEND;VALUE=DATE:20131227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140101
+DTSTART;VALUE=DATE:20140101
+DTEND;VALUE=DATE:20140102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140417
+DTSTART;VALUE=DATE:20140417
+DTEND;VALUE=DATE:20140418
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140418
+DTSTART;VALUE=DATE:20140418
+DTEND;VALUE=DATE:20140419
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140420
+DTSTART;VALUE=DATE:20140420
+DTEND;VALUE=DATE:20140421
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140421
+DTSTART;VALUE=DATE:20140421
+DTEND;VALUE=DATE:20140422
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140501
+DTSTART;VALUE=DATE:20140501
+DTEND;VALUE=DATE:20140502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140517
+DTSTART;VALUE=DATE:20140517
+DTEND;VALUE=DATE:20140518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140529
+DTSTART;VALUE=DATE:20140529
+DTEND;VALUE=DATE:20140530
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140608
+DTSTART;VALUE=DATE:20140608
+DTEND;VALUE=DATE:20140609
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20140609
+DTSTART;VALUE=DATE:20140609
+DTEND;VALUE=DATE:20140610
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20141225
+DTSTART;VALUE=DATE:20141225
+DTEND;VALUE=DATE:20141226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20141226
+DTSTART;VALUE=DATE:20141226
+DTEND;VALUE=DATE:20141227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150101
+DTSTART;VALUE=DATE:20150101
+DTEND;VALUE=DATE:20150102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150402
+DTSTART;VALUE=DATE:20150402
+DTEND;VALUE=DATE:20150403
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150403
+DTSTART;VALUE=DATE:20150403
+DTEND;VALUE=DATE:20150404
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150405
+DTSTART;VALUE=DATE:20150405
+DTEND;VALUE=DATE:20150406
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150406
+DTSTART;VALUE=DATE:20150406
+DTEND;VALUE=DATE:20150407
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150501
+DTSTART;VALUE=DATE:20150501
+DTEND;VALUE=DATE:20150502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150514
+DTSTART;VALUE=DATE:20150514
+DTEND;VALUE=DATE:20150515
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150517
+DTSTART;VALUE=DATE:20150517
+DTEND;VALUE=DATE:20150518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150524
+DTSTART;VALUE=DATE:20150524
+DTEND;VALUE=DATE:20150525
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20150525
+DTSTART;VALUE=DATE:20150525
+DTEND;VALUE=DATE:20150526
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20151225
+DTSTART;VALUE=DATE:20151225
+DTEND;VALUE=DATE:20151226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20151226
+DTSTART;VALUE=DATE:20151226
+DTEND;VALUE=DATE:20151227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160101
+DTSTART;VALUE=DATE:20160101
+DTEND;VALUE=DATE:20160102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160324
+DTSTART;VALUE=DATE:20160324
+DTEND;VALUE=DATE:20160325
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160325
+DTSTART;VALUE=DATE:20160325
+DTEND;VALUE=DATE:20160326
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160327
+DTSTART;VALUE=DATE:20160327
+DTEND;VALUE=DATE:20160328
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160328
+DTSTART;VALUE=DATE:20160328
+DTEND;VALUE=DATE:20160329
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160501
+DTSTART;VALUE=DATE:20160501
+DTEND;VALUE=DATE:20160502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160505
+DTSTART;VALUE=DATE:20160505
+DTEND;VALUE=DATE:20160506
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160515
+DTSTART;VALUE=DATE:20160515
+DTEND;VALUE=DATE:20160516
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160516
+DTSTART;VALUE=DATE:20160516
+DTEND;VALUE=DATE:20160517
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20160517
+DTSTART;VALUE=DATE:20160517
+DTEND;VALUE=DATE:20160518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20161225
+DTSTART;VALUE=DATE:20161225
+DTEND;VALUE=DATE:20161226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20161226
+DTSTART;VALUE=DATE:20161226
+DTEND;VALUE=DATE:20161227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170101
+DTSTART;VALUE=DATE:20170101
+DTEND;VALUE=DATE:20170102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170413
+DTSTART;VALUE=DATE:20170413
+DTEND;VALUE=DATE:20170414
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170414
+DTSTART;VALUE=DATE:20170414
+DTEND;VALUE=DATE:20170415
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170416
+DTSTART;VALUE=DATE:20170416
+DTEND;VALUE=DATE:20170417
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170417
+DTSTART;VALUE=DATE:20170417
+DTEND;VALUE=DATE:20170418
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170501
+DTSTART;VALUE=DATE:20170501
+DTEND;VALUE=DATE:20170502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170517
+DTSTART;VALUE=DATE:20170517
+DTEND;VALUE=DATE:20170518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20170525
DTSTART;VALUE=DATE:20170525
DTEND;VALUE=DATE:20170526
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:9645b363-af9c-49ab-91a1-0cc072deb3dc
+UID:nor-20170604
DTSTART;VALUE=DATE:20170604
DTEND;VALUE=DATE:20170605
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:f6da2bb5-85c3-4edb-bb3e-79975ede524a
+UID:nor-20170605
DTSTART;VALUE=DATE:20170605
DTEND;VALUE=DATE:20170606
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Skjærtorsdag
-UID:af1cd706-130e-4659-bbe8-42e1c5c0df0f
+UID:nor-20171225
+DTSTART;VALUE=DATE:20171225
+DTEND;VALUE=DATE:20171226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20171226
+DTSTART;VALUE=DATE:20171226
+DTEND;VALUE=DATE:20171227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20180101
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20180329
DTSTART;VALUE=DATE:20180329
DTEND;VALUE=DATE:20180330
+SUMMARY:Skjærtorsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Langfredag
-UID:a5441ccd-1a9a-4bd0-a32d-57aabb242e57
+UID:nor-20180330
DTSTART;VALUE=DATE:20180330
DTEND;VALUE=DATE:20180331
+SUMMARY:Langfredag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Påskedag
-UID:43c65367-7890-47ff-9b71-932f29c6051a
+UID:nor-20180401
DTSTART;VALUE=DATE:20180401
DTEND;VALUE=DATE:20180402
+SUMMARY:1. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. påskedag
-UID:634ae48c-f550-494c-a460-fc2c689db36e
+UID:nor-20180402
DTSTART;VALUE=DATE:20180402
DTEND;VALUE=DATE:20180403
+SUMMARY:2. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Kristi himmelfartsdag
-UID:75d31850-787b-453c-bd79-b9b217bc80af
+UID:nor-20180501
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20180510
DTSTART;VALUE=DATE:20180510
DTEND;VALUE=DATE:20180511
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:1d1ea5bc-9d93-445e-8f15-5c5a06d67e24
+UID:nor-20180517
+DTSTART;VALUE=DATE:20180517
+DTEND;VALUE=DATE:20180518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20180520
DTSTART;VALUE=DATE:20180520
DTEND;VALUE=DATE:20180521
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:d3b76824-c2f2-4fc9-8af4-43db004a6d84
+UID:nor-20180521
DTSTART;VALUE=DATE:20180521
DTEND;VALUE=DATE:20180522
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Palmesøndag
-UID:8f1ebf54-a08b-4011-892d-6938edae0a8e
-DTSTART;VALUE=DATE:20190414
-DTEND;VALUE=DATE:20190415
+UID:nor-20181225
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:1. juledag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Skjærtorsdag
-UID:93683990-487d-4742-8102-cd4bb990ea97
+UID:nor-20181226
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20190101
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20190418
DTSTART;VALUE=DATE:20190418
DTEND;VALUE=DATE:20190419
+SUMMARY:Skjærtorsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Langfredag
-UID:32bc03a4-5113-42b0-8e4b-b0e72f2c009f
+UID:nor-20190419
DTSTART;VALUE=DATE:20190419
DTEND;VALUE=DATE:20190420
+SUMMARY:Langfredag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Påskedag
-UID:945edfd5-bff7-418d-a85f-db22587b57dd
+UID:nor-20190421
DTSTART;VALUE=DATE:20190421
DTEND;VALUE=DATE:20190422
+SUMMARY:1. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. påskedag
-UID:efa8ed4e-e762-43ea-b4ab-3e4fe895e160
+UID:nor-20190422
DTSTART;VALUE=DATE:20190422
DTEND;VALUE=DATE:20190423
+SUMMARY:2. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Kristi himmelfartsdag
-UID:3c2fb3c6-0055-41ef-bcd8-63b969be38dd
+UID:nor-20190501
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20190517
+DTSTART;VALUE=DATE:20190517
+DTEND;VALUE=DATE:20190518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20190530
DTSTART;VALUE=DATE:20190530
DTEND;VALUE=DATE:20190531
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:f9a20766-a0f7-4e70-8fa8-1e63fbc4ec29
+UID:nor-20190609
DTSTART;VALUE=DATE:20190609
DTEND;VALUE=DATE:20190610
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:9fb78540-a8c8-425c-8bf0-7706eb78908b
+UID:nor-20190610
DTSTART;VALUE=DATE:20190610
DTEND;VALUE=DATE:20190611
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Palmesøndag
-UID:c248d3ba-d695-47fc-856c-0379061ca6bf
-DTSTART;VALUE=DATE:20200405
-DTEND;VALUE=DATE:20200406
+UID:nor-20191225
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:1. juledag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Skjærtorsdag
-UID:cdce9a51-92f2-44db-8f88-0cfd10b6db29
+UID:nor-20191226
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20200101
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20200409
DTSTART;VALUE=DATE:20200409
DTEND;VALUE=DATE:20200410
+SUMMARY:Skjærtorsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Langfredag
-UID:23292ea1-2c22-42f7-92b3-9e8c65095157
+UID:nor-20200410
DTSTART;VALUE=DATE:20200410
DTEND;VALUE=DATE:20200411
+SUMMARY:Langfredag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Påskedag
-UID:e96985ee-579d-41f8-9239-88044c233e19
+UID:nor-20200412
DTSTART;VALUE=DATE:20200412
DTEND;VALUE=DATE:20200413
+SUMMARY:1. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. påskedag
-UID:f7535533-4e97-45d1-ab07-71be7a28d704
+UID:nor-20200413
DTSTART;VALUE=DATE:20200413
DTEND;VALUE=DATE:20200414
+SUMMARY:2. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Kristi himmelfartsdag
-UID:350b665f-1ffc-4fc6-ac5b-5e51a1ff98cd
+UID:nor-20200501
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20200517
+DTSTART;VALUE=DATE:20200517
+DTEND;VALUE=DATE:20200518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20200521
DTSTART;VALUE=DATE:20200521
DTEND;VALUE=DATE:20200522
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:f225f91a-6799-4e9a-a5f6-fd747ba664aa
+UID:nor-20200531
DTSTART;VALUE=DATE:20200531
DTEND;VALUE=DATE:20200601
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a32
+UID:nor-20200601
DTSTART;VALUE=DATE:20200601
DTEND;VALUE=DATE:20200602
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Skjærtorsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a33
+UID:nor-20201225
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20201226
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20210101
+DTSTART;VALUE=DATE:20210101
+DTEND;VALUE=DATE:20210102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20210401
DTSTART;VALUE=DATE:20210401
DTEND;VALUE=DATE:20210402
+SUMMARY:Skjærtorsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Langfredag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a34
+UID:nor-20210402
DTSTART;VALUE=DATE:20210402
DTEND;VALUE=DATE:20210403
+SUMMARY:Langfredag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a35
+UID:nor-20210404
DTSTART;VALUE=DATE:20210404
DTEND;VALUE=DATE:20210405
+SUMMARY:1. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a36
+UID:nor-20210405
DTSTART;VALUE=DATE:20210405
DTEND;VALUE=DATE:20210406
+SUMMARY:2. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Kr. himmelfartsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a37
+UID:nor-20210501
+DTSTART;VALUE=DATE:20210501
+DTEND;VALUE=DATE:20210502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20210513
DTSTART;VALUE=DATE:20210513
DTEND;VALUE=DATE:20210514
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a38
+UID:nor-20210517
+DTSTART;VALUE=DATE:20210517
+DTEND;VALUE=DATE:20210518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20210523
DTSTART;VALUE=DATE:20210523
DTEND;VALUE=DATE:20210524
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a39
+UID:nor-20210524
DTSTART;VALUE=DATE:20210524
DTEND;VALUE=DATE:20210525
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Skjærtorsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a40
+UID:nor-20211225
+DTSTART;VALUE=DATE:20211225
+DTEND;VALUE=DATE:20211226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20211226
+DTSTART;VALUE=DATE:20211226
+DTEND;VALUE=DATE:20211227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20220101
+DTSTART;VALUE=DATE:20220101
+DTEND;VALUE=DATE:20220102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20220414
DTSTART;VALUE=DATE:20220414
DTEND;VALUE=DATE:20220415
+SUMMARY:Skjærtorsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Langfredag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a41
+UID:nor-20220415
DTSTART;VALUE=DATE:20220415
DTEND;VALUE=DATE:20220416
+SUMMARY:Langfredag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a42
+UID:nor-20220417
DTSTART;VALUE=DATE:20220417
DTEND;VALUE=DATE:20220418
+SUMMARY:1. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a43
+UID:nor-20220418
DTSTART;VALUE=DATE:20220418
DTEND;VALUE=DATE:20220419
+SUMMARY:2. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Kr. himmelfartsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a44
+UID:nor-20220501
+DTSTART;VALUE=DATE:20220501
+DTEND;VALUE=DATE:20220502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20220517
+DTSTART;VALUE=DATE:20220517
+DTEND;VALUE=DATE:20220518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20220526
DTSTART;VALUE=DATE:20220526
DTEND;VALUE=DATE:20220527
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a45
+UID:nor-20220605
DTSTART;VALUE=DATE:20220605
DTEND;VALUE=DATE:20220606
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a46
+UID:nor-20220606
DTSTART;VALUE=DATE:20220606
DTEND;VALUE=DATE:20220607
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Skjærtorsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a47
+UID:nor-20221225
+DTSTART;VALUE=DATE:20221225
+DTEND;VALUE=DATE:20221226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20221226
+DTSTART;VALUE=DATE:20221226
+DTEND;VALUE=DATE:20221227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20230101
+DTSTART;VALUE=DATE:20230101
+DTEND;VALUE=DATE:20230102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20230406
DTSTART;VALUE=DATE:20230406
DTEND;VALUE=DATE:20230407
+SUMMARY:Skjærtorsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Langfredag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a48
+UID:nor-20230407
DTSTART;VALUE=DATE:20230407
DTEND;VALUE=DATE:20230408
+SUMMARY:Langfredag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a49
+UID:nor-20230409
DTSTART;VALUE=DATE:20230409
DTEND;VALUE=DATE:20230410
+SUMMARY:1. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a50
+UID:nor-20230410
DTSTART;VALUE=DATE:20230410
DTEND;VALUE=DATE:20230411
+SUMMARY:2. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Kr. himmelfartsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a51
+UID:nor-20230501
+DTSTART;VALUE=DATE:20230501
+DTEND;VALUE=DATE:20230502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20230517
+DTSTART;VALUE=DATE:20230517
+DTEND;VALUE=DATE:20230518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20230518
DTSTART;VALUE=DATE:20230518
DTEND;VALUE=DATE:20230519
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a52
+UID:nor-20230528
DTSTART;VALUE=DATE:20230528
DTEND;VALUE=DATE:20230529
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a53
+UID:nor-20230529
DTSTART;VALUE=DATE:20230529
DTEND;VALUE=DATE:20230530
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Skjærtorsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a54
+UID:nor-20231225
+DTSTART;VALUE=DATE:20231225
+DTEND;VALUE=DATE:20231226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20231226
+DTSTART;VALUE=DATE:20231226
+DTEND;VALUE=DATE:20231227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20240101
+DTSTART;VALUE=DATE:20240101
+DTEND;VALUE=DATE:20240102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20240328
DTSTART;VALUE=DATE:20240328
DTEND;VALUE=DATE:20240329
+SUMMARY:Skjærtorsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Langfredag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a55
+UID:nor-20240329
DTSTART;VALUE=DATE:20240329
DTEND;VALUE=DATE:20240330
+SUMMARY:Langfredag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a56
+UID:nor-20240331
DTSTART;VALUE=DATE:20240331
DTEND;VALUE=DATE:20240401
+SUMMARY:1. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. påskedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a57
+UID:nor-20240401
DTSTART;VALUE=DATE:20240401
DTEND;VALUE=DATE:20240402
+SUMMARY:2. påskedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Kr. himmelfartsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a58
+UID:nor-20240501
+DTSTART;VALUE=DATE:20240501
+DTEND;VALUE=DATE:20240502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20240509
DTSTART;VALUE=DATE:20240509
DTEND;VALUE=DATE:20240510
+SUMMARY:Kr. himmelfartsdag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a59
+UID:nor-20240517
+DTSTART;VALUE=DATE:20240517
+DTEND;VALUE=DATE:20240518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20240519
DTSTART;VALUE=DATE:20240519
DTEND;VALUE=DATE:20240520
+SUMMARY:1. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:2. pinsedag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a60
+UID:nor-20240520
DTSTART;VALUE=DATE:20240520
DTEND;VALUE=DATE:20240521
+SUMMARY:2. pinsedag
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Nyttårsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a61
-DTSTART;VALUE=DATE:20020101
-DTEND;VALUE=DATE:20020102
+UID:nor-20241225
+DTSTART;VALUE=DATE:20241225
+DTEND;VALUE=DATE:20241226
+SUMMARY:1. juledag
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Off. høytidsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a62
-DTSTART;VALUE=DATE:20020501
-DTEND;VALUE=DATE:20020502
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Grunnlovsdag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a63
-DTSTART;VALUE=DATE:20020517
-DTEND;VALUE=DATE:20020518
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Juledag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a64
-DTSTART;VALUE=DATE:20021225
-DTEND;VALUE=DATE:20021226
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
+UID:nor-20241226
+DTSTART;VALUE=DATE:20241226
+DTEND;VALUE=DATE:20241227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250101
+DTSTART;VALUE=DATE:20250101
+DTEND;VALUE=DATE:20250102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250417
+DTSTART;VALUE=DATE:20250417
+DTEND;VALUE=DATE:20250418
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250418
+DTSTART;VALUE=DATE:20250418
+DTEND;VALUE=DATE:20250419
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250420
+DTSTART;VALUE=DATE:20250420
+DTEND;VALUE=DATE:20250421
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250421
+DTSTART;VALUE=DATE:20250421
+DTEND;VALUE=DATE:20250422
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250501
+DTSTART;VALUE=DATE:20250501
+DTEND;VALUE=DATE:20250502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250517
+DTSTART;VALUE=DATE:20250517
+DTEND;VALUE=DATE:20250518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250529
+DTSTART;VALUE=DATE:20250529
+DTEND;VALUE=DATE:20250530
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250608
+DTSTART;VALUE=DATE:20250608
+DTEND;VALUE=DATE:20250609
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20250609
+DTSTART;VALUE=DATE:20250609
+DTEND;VALUE=DATE:20250610
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20251225
+DTSTART;VALUE=DATE:20251225
+DTEND;VALUE=DATE:20251226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20251226
+DTSTART;VALUE=DATE:20251226
+DTEND;VALUE=DATE:20251227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260101
+DTSTART;VALUE=DATE:20260101
+DTEND;VALUE=DATE:20260102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260402
+DTSTART;VALUE=DATE:20260402
+DTEND;VALUE=DATE:20260403
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260403
+DTSTART;VALUE=DATE:20260403
+DTEND;VALUE=DATE:20260404
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260405
+DTSTART;VALUE=DATE:20260405
+DTEND;VALUE=DATE:20260406
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260406
+DTSTART;VALUE=DATE:20260406
+DTEND;VALUE=DATE:20260407
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260501
+DTSTART;VALUE=DATE:20260501
+DTEND;VALUE=DATE:20260502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260514
+DTSTART;VALUE=DATE:20260514
+DTEND;VALUE=DATE:20260515
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260517
+DTSTART;VALUE=DATE:20260517
+DTEND;VALUE=DATE:20260518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260524
+DTSTART;VALUE=DATE:20260524
+DTEND;VALUE=DATE:20260525
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20260525
+DTSTART;VALUE=DATE:20260525
+DTEND;VALUE=DATE:20260526
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20261225
+DTSTART;VALUE=DATE:20261225
+DTEND;VALUE=DATE:20261226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20261226
+DTSTART;VALUE=DATE:20261226
+DTEND;VALUE=DATE:20261227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270101
+DTSTART;VALUE=DATE:20270101
+DTEND;VALUE=DATE:20270102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270325
+DTSTART;VALUE=DATE:20270325
+DTEND;VALUE=DATE:20270326
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270326
+DTSTART;VALUE=DATE:20270326
+DTEND;VALUE=DATE:20270327
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270328
+DTSTART;VALUE=DATE:20270328
+DTEND;VALUE=DATE:20270329
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270329
+DTSTART;VALUE=DATE:20270329
+DTEND;VALUE=DATE:20270330
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270501
+DTSTART;VALUE=DATE:20270501
+DTEND;VALUE=DATE:20270502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270506
+DTSTART;VALUE=DATE:20270506
+DTEND;VALUE=DATE:20270507
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270516
+DTSTART;VALUE=DATE:20270516
+DTEND;VALUE=DATE:20270517
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270517
+DTSTART;VALUE=DATE:20270517
+DTEND;VALUE=DATE:20270518
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20270517-188897
+DTSTART;VALUE=DATE:20270517
+DTEND;VALUE=DATE:20270518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20271225
+DTSTART;VALUE=DATE:20271225
+DTEND;VALUE=DATE:20271226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20271226
+DTSTART;VALUE=DATE:20271226
+DTEND;VALUE=DATE:20271227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280101
+DTSTART;VALUE=DATE:20280101
+DTEND;VALUE=DATE:20280102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280413
+DTSTART;VALUE=DATE:20280413
+DTEND;VALUE=DATE:20280414
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280414
+DTSTART;VALUE=DATE:20280414
+DTEND;VALUE=DATE:20280415
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280416
+DTSTART;VALUE=DATE:20280416
+DTEND;VALUE=DATE:20280417
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280417
+DTSTART;VALUE=DATE:20280417
+DTEND;VALUE=DATE:20280418
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280501
+DTSTART;VALUE=DATE:20280501
+DTEND;VALUE=DATE:20280502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280517
+DTSTART;VALUE=DATE:20280517
+DTEND;VALUE=DATE:20280518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280525
+DTSTART;VALUE=DATE:20280525
+DTEND;VALUE=DATE:20280526
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280604
+DTSTART;VALUE=DATE:20280604
+DTEND;VALUE=DATE:20280605
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20280605
+DTSTART;VALUE=DATE:20280605
+DTEND;VALUE=DATE:20280606
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20281225
+DTSTART;VALUE=DATE:20281225
+DTEND;VALUE=DATE:20281226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20281226
+DTSTART;VALUE=DATE:20281226
+DTEND;VALUE=DATE:20281227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290101
+DTSTART;VALUE=DATE:20290101
+DTEND;VALUE=DATE:20290102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290329
+DTSTART;VALUE=DATE:20290329
+DTEND;VALUE=DATE:20290330
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290330
+DTSTART;VALUE=DATE:20290330
+DTEND;VALUE=DATE:20290331
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290401
+DTSTART;VALUE=DATE:20290401
+DTEND;VALUE=DATE:20290402
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290402
+DTSTART;VALUE=DATE:20290402
+DTEND;VALUE=DATE:20290403
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290501
+DTSTART;VALUE=DATE:20290501
+DTEND;VALUE=DATE:20290502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290510
+DTSTART;VALUE=DATE:20290510
+DTEND;VALUE=DATE:20290511
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290517
+DTSTART;VALUE=DATE:20290517
+DTEND;VALUE=DATE:20290518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290520
+DTSTART;VALUE=DATE:20290520
+DTEND;VALUE=DATE:20290521
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20290521
+DTSTART;VALUE=DATE:20290521
+DTEND;VALUE=DATE:20290522
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20291225
+DTSTART;VALUE=DATE:20291225
+DTEND;VALUE=DATE:20291226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20291226
+DTSTART;VALUE=DATE:20291226
+DTEND;VALUE=DATE:20291227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300101
+DTSTART;VALUE=DATE:20300101
+DTEND;VALUE=DATE:20300102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300418
+DTSTART;VALUE=DATE:20300418
+DTEND;VALUE=DATE:20300419
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300419
+DTSTART;VALUE=DATE:20300419
+DTEND;VALUE=DATE:20300420
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300421
+DTSTART;VALUE=DATE:20300421
+DTEND;VALUE=DATE:20300422
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300422
+DTSTART;VALUE=DATE:20300422
+DTEND;VALUE=DATE:20300423
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300501
+DTSTART;VALUE=DATE:20300501
+DTEND;VALUE=DATE:20300502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300517
+DTSTART;VALUE=DATE:20300517
+DTEND;VALUE=DATE:20300518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300530
+DTSTART;VALUE=DATE:20300530
+DTEND;VALUE=DATE:20300531
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300609
+DTSTART;VALUE=DATE:20300609
+DTEND;VALUE=DATE:20300610
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20300610
+DTSTART;VALUE=DATE:20300610
+DTEND;VALUE=DATE:20300611
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20301225
+DTSTART;VALUE=DATE:20301225
+DTEND;VALUE=DATE:20301226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20301226
+DTSTART;VALUE=DATE:20301226
+DTEND;VALUE=DATE:20301227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310101
+DTSTART;VALUE=DATE:20310101
+DTEND;VALUE=DATE:20310102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310410
+DTSTART;VALUE=DATE:20310410
+DTEND;VALUE=DATE:20310411
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310411
+DTSTART;VALUE=DATE:20310411
+DTEND;VALUE=DATE:20310412
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310413
+DTSTART;VALUE=DATE:20310413
+DTEND;VALUE=DATE:20310414
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310414
+DTSTART;VALUE=DATE:20310414
+DTEND;VALUE=DATE:20310415
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310501
+DTSTART;VALUE=DATE:20310501
+DTEND;VALUE=DATE:20310502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310517
+DTSTART;VALUE=DATE:20310517
+DTEND;VALUE=DATE:20310518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310522
+DTSTART;VALUE=DATE:20310522
+DTEND;VALUE=DATE:20310523
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310601
+DTSTART;VALUE=DATE:20310601
+DTEND;VALUE=DATE:20310602
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20310602
+DTSTART;VALUE=DATE:20310602
+DTEND;VALUE=DATE:20310603
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20311225
+DTSTART;VALUE=DATE:20311225
+DTEND;VALUE=DATE:20311226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20311226
+DTSTART;VALUE=DATE:20311226
+DTEND;VALUE=DATE:20311227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320101
+DTSTART;VALUE=DATE:20320101
+DTEND;VALUE=DATE:20320102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320325
+DTSTART;VALUE=DATE:20320325
+DTEND;VALUE=DATE:20320326
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320326
+DTSTART;VALUE=DATE:20320326
+DTEND;VALUE=DATE:20320327
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320328
+DTSTART;VALUE=DATE:20320328
+DTEND;VALUE=DATE:20320329
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320329
+DTSTART;VALUE=DATE:20320329
+DTEND;VALUE=DATE:20320330
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320501
+DTSTART;VALUE=DATE:20320501
+DTEND;VALUE=DATE:20320502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320506
+DTSTART;VALUE=DATE:20320506
+DTEND;VALUE=DATE:20320507
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320516
+DTSTART;VALUE=DATE:20320516
+DTEND;VALUE=DATE:20320517
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320517
+DTSTART;VALUE=DATE:20320517
+DTEND;VALUE=DATE:20320518
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20320517-180467
+DTSTART;VALUE=DATE:20320517
+DTEND;VALUE=DATE:20320518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20321225
+DTSTART;VALUE=DATE:20321225
+DTEND;VALUE=DATE:20321226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20321226
+DTSTART;VALUE=DATE:20321226
+DTEND;VALUE=DATE:20321227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330101
+DTSTART;VALUE=DATE:20330101
+DTEND;VALUE=DATE:20330102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330414
+DTSTART;VALUE=DATE:20330414
+DTEND;VALUE=DATE:20330415
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330415
+DTSTART;VALUE=DATE:20330415
+DTEND;VALUE=DATE:20330416
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330417
+DTSTART;VALUE=DATE:20330417
+DTEND;VALUE=DATE:20330418
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330418
+DTSTART;VALUE=DATE:20330418
+DTEND;VALUE=DATE:20330419
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330501
+DTSTART;VALUE=DATE:20330501
+DTEND;VALUE=DATE:20330502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330517
+DTSTART;VALUE=DATE:20330517
+DTEND;VALUE=DATE:20330518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330526
+DTSTART;VALUE=DATE:20330526
+DTEND;VALUE=DATE:20330527
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330605
+DTSTART;VALUE=DATE:20330605
+DTEND;VALUE=DATE:20330606
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20330606
+DTSTART;VALUE=DATE:20330606
+DTEND;VALUE=DATE:20330607
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20331225
+DTSTART;VALUE=DATE:20331225
+DTEND;VALUE=DATE:20331226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20331226
+DTSTART;VALUE=DATE:20331226
+DTEND;VALUE=DATE:20331227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340101
+DTSTART;VALUE=DATE:20340101
+DTEND;VALUE=DATE:20340102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340406
+DTSTART;VALUE=DATE:20340406
+DTEND;VALUE=DATE:20340407
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340407
+DTSTART;VALUE=DATE:20340407
+DTEND;VALUE=DATE:20340408
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340409
+DTSTART;VALUE=DATE:20340409
+DTEND;VALUE=DATE:20340410
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340410
+DTSTART;VALUE=DATE:20340410
+DTEND;VALUE=DATE:20340411
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340501
+DTSTART;VALUE=DATE:20340501
+DTEND;VALUE=DATE:20340502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340517
+DTSTART;VALUE=DATE:20340517
+DTEND;VALUE=DATE:20340518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340518
+DTSTART;VALUE=DATE:20340518
+DTEND;VALUE=DATE:20340519
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340528
+DTSTART;VALUE=DATE:20340528
+DTEND;VALUE=DATE:20340529
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20340529
+DTSTART;VALUE=DATE:20340529
+DTEND;VALUE=DATE:20340530
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20341225
+DTSTART;VALUE=DATE:20341225
+DTEND;VALUE=DATE:20341226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20341226
+DTSTART;VALUE=DATE:20341226
+DTEND;VALUE=DATE:20341227
+SUMMARY:2. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350101
+DTSTART;VALUE=DATE:20350101
+DTEND;VALUE=DATE:20350102
+SUMMARY:1. nyttårsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350322
+DTSTART;VALUE=DATE:20350322
+DTEND;VALUE=DATE:20350323
+SUMMARY:Skjærtorsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350323
+DTSTART;VALUE=DATE:20350323
+DTEND;VALUE=DATE:20350324
+SUMMARY:Langfredag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350325
+DTSTART;VALUE=DATE:20350325
+DTEND;VALUE=DATE:20350326
+SUMMARY:1. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350326
+DTSTART;VALUE=DATE:20350326
+DTEND;VALUE=DATE:20350327
+SUMMARY:2. påskedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350501
+DTSTART;VALUE=DATE:20350501
+DTEND;VALUE=DATE:20350502
+SUMMARY:Off. høytidsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350503
+DTSTART;VALUE=DATE:20350503
+DTEND;VALUE=DATE:20350504
+SUMMARY:Kr. himmelfartsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350513
+DTSTART;VALUE=DATE:20350513
+DTEND;VALUE=DATE:20350514
+SUMMARY:1. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350514
+DTSTART;VALUE=DATE:20350514
+DTEND;VALUE=DATE:20350515
+SUMMARY:2. pinsedag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20350517
+DTSTART;VALUE=DATE:20350517
+DTEND;VALUE=DATE:20350518
+SUMMARY:Grunnlovsdag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20351225
+DTSTART;VALUE=DATE:20351225
+DTEND;VALUE=DATE:20351226
+SUMMARY:1. juledag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:nor-20351226
+DTSTART;VALUE=DATE:20351226
+DTEND;VALUE=DATE:20351227
SUMMARY:2. juledag
-UID:1c278a1f-8dd5-46cd-ad16-7dfacf731a65
-DTSTART;VALUE=DATE:20021226
-DTEND;VALUE=DATE:20021227
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/assets/romania.ics b/app/src/main/assets/romania.ics
new file mode 100644
index 000000000..8e47694ce
--- /dev/null
+++ b/app/src/main/assets/romania.ics
@@ -0,0 +1,296 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-rou-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Anul nou
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180102@kayaposoft.com
+DTSTART;VALUE=DATE:20180102
+DTEND;VALUE=DATE:20180103
+SUMMARY:Anul nou
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180124@kayaposoft.com
+DTSTART;VALUE=DATE:20180124
+DTEND;VALUE=DATE:20180125
+SUMMARY:Unirea Principatelor Române/Mica Unire
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180408@kayaposoft.com
+DTSTART;VALUE=DATE:20180408
+DTEND;VALUE=DATE:20180409
+SUMMARY:Paştele
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180409@kayaposoft.com
+DTSTART;VALUE=DATE:20180409
+DTEND;VALUE=DATE:20180410
+SUMMARY:Doua zi de Pasti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Ziua muncii
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180527@kayaposoft.com
+DTSTART;VALUE=DATE:20180527
+DTEND;VALUE=DATE:20180528
+SUMMARY:Rusaliile
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180528@kayaposoft.com
+DTSTART;VALUE=DATE:20180528
+DTEND;VALUE=DATE:20180529
+SUMMARY:Doua zi de Rusaliile
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180601@kayaposoft.com
+DTSTART;VALUE=DATE:20180601
+DTEND;VALUE=DATE:20180602
+SUMMARY:Ziua Copilului
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20180815@kayaposoft.com
+DTSTART;VALUE=DATE:20180815
+DTEND;VALUE=DATE:20180816
+SUMMARY:Adormirea Maicii Domnului/Sfânta Maria Mare
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20181130@kayaposoft.com
+DTSTART;VALUE=DATE:20181130
+DTEND;VALUE=DATE:20181201
+SUMMARY:Sfântul Andrei
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20181201@kayaposoft.com
+DTSTART;VALUE=DATE:20181201
+DTEND;VALUE=DATE:20181202
+SUMMARY:Ziua Națională/Marea Unire
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Crăciunul
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Crăciunul
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Anul nou
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190102@kayaposoft.com
+DTSTART;VALUE=DATE:20190102
+DTEND;VALUE=DATE:20190103
+SUMMARY:Anul nou
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190124@kayaposoft.com
+DTSTART;VALUE=DATE:20190124
+DTEND;VALUE=DATE:20190125
+SUMMARY:Unirea Principatelor Române/Mica Unire
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190428@kayaposoft.com
+DTSTART;VALUE=DATE:20190428
+DTEND;VALUE=DATE:20190429
+SUMMARY:Paştele
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190429@kayaposoft.com
+DTSTART;VALUE=DATE:20190429
+DTEND;VALUE=DATE:20190430
+SUMMARY:Doua zi de Pasti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Ziua muncii
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190601@kayaposoft.com
+DTSTART;VALUE=DATE:20190601
+DTEND;VALUE=DATE:20190602
+SUMMARY:Ziua Copilului
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190616@kayaposoft.com
+DTSTART;VALUE=DATE:20190616
+DTEND;VALUE=DATE:20190617
+SUMMARY:Rusaliile
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190617@kayaposoft.com
+DTSTART;VALUE=DATE:20190617
+DTEND;VALUE=DATE:20190618
+SUMMARY:Doua zi de Rusaliile
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20190815@kayaposoft.com
+DTSTART;VALUE=DATE:20190815
+DTEND;VALUE=DATE:20190816
+SUMMARY:Adormirea Maicii Domnului/Sfânta Maria Mare
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20191130@kayaposoft.com
+DTSTART;VALUE=DATE:20191130
+DTEND;VALUE=DATE:20191201
+SUMMARY:Sfântul Andrei
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20191201@kayaposoft.com
+DTSTART;VALUE=DATE:20191201
+DTEND;VALUE=DATE:20191202
+SUMMARY:Ziua Națională/Marea Unire
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Crăciunul
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Crăciunul
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Anul nou
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200102@kayaposoft.com
+DTSTART;VALUE=DATE:20200102
+DTEND;VALUE=DATE:20200103
+SUMMARY:Anul nou
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200124@kayaposoft.com
+DTSTART;VALUE=DATE:20200124
+DTEND;VALUE=DATE:20200125
+SUMMARY:Unirea Principatelor Române/Mica Unire
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200419@kayaposoft.com
+DTSTART;VALUE=DATE:20200419
+DTEND;VALUE=DATE:20200420
+SUMMARY:Paştele
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200420@kayaposoft.com
+DTSTART;VALUE=DATE:20200420
+DTEND;VALUE=DATE:20200421
+SUMMARY:Doua zi de Pasti
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Ziua muncii
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200601@kayaposoft.com
+DTSTART;VALUE=DATE:20200601
+DTEND;VALUE=DATE:20200602
+SUMMARY:Ziua Copilului
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200607@kayaposoft.com
+DTSTART;VALUE=DATE:20200607
+DTEND;VALUE=DATE:20200608
+SUMMARY:Rusaliile
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200608@kayaposoft.com
+DTSTART;VALUE=DATE:20200608
+DTEND;VALUE=DATE:20200609
+SUMMARY:Doua zi de Rusaliile
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20200815@kayaposoft.com
+DTSTART;VALUE=DATE:20200815
+DTEND;VALUE=DATE:20200816
+SUMMARY:Adormirea Maicii Domnului/Sfânta Maria Mare
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20201130@kayaposoft.com
+DTSTART;VALUE=DATE:20201130
+DTEND;VALUE=DATE:20201201
+SUMMARY:Sfântul Andrei
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20201201@kayaposoft.com
+DTSTART;VALUE=DATE:20201201
+DTEND;VALUE=DATE:20201202
+SUMMARY:Ziua Națională/Marea Unire
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Crăciunul
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-rou-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:Crăciunul
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/serbia.ics b/app/src/main/assets/serbia.ics
new file mode 100644
index 000000000..fcc5199b4
--- /dev/null
+++ b/app/src/main/assets/serbia.ics
@@ -0,0 +1,247 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-srb-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180102@kayaposoft.com
+DTSTART;VALUE=DATE:20180102
+DTEND;VALUE=DATE:20180103
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180107@kayaposoft.com
+DTSTART;VALUE=DATE:20180107
+DTEND;VALUE=DATE:20180108
+SUMMARY:Božić
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180215@kayaposoft.com
+DTSTART;VALUE=DATE:20180215
+DTEND;VALUE=DATE:20180216
+SUMMARY:Dan državnosti Srbije
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180216@kayaposoft.com
+DTSTART;VALUE=DATE:20180216
+DTEND;VALUE=DATE:20180217
+SUMMARY:Dan državnosti Srbije
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180406@kayaposoft.com
+DTSTART;VALUE=DATE:20180406
+DTEND;VALUE=DATE:20180407
+SUMMARY:Veliki petak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180408@kayaposoft.com
+DTSTART;VALUE=DATE:20180408
+DTEND;VALUE=DATE:20180409
+SUMMARY:Vaskrs
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180409@kayaposoft.com
+DTSTART;VALUE=DATE:20180409
+DTEND;VALUE=DATE:20180410
+SUMMARY:Vaskrsni ponedeljak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20180502@kayaposoft.com
+DTSTART;VALUE=DATE:20180502
+DTEND;VALUE=DATE:20180503
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20181111@kayaposoft.com
+DTSTART;VALUE=DATE:20181111
+DTEND;VALUE=DATE:20181112
+SUMMARY:Dan primirja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20181112@kayaposoft.com
+DTSTART;VALUE=DATE:20181112
+DTEND;VALUE=DATE:20181113
+SUMMARY:Dan primirja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190102@kayaposoft.com
+DTSTART;VALUE=DATE:20190102
+DTEND;VALUE=DATE:20190103
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190107@kayaposoft.com
+DTSTART;VALUE=DATE:20190107
+DTEND;VALUE=DATE:20190108
+SUMMARY:Božić
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190215@kayaposoft.com
+DTSTART;VALUE=DATE:20190215
+DTEND;VALUE=DATE:20190216
+SUMMARY:Dan državnosti Srbije
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190216@kayaposoft.com
+DTSTART;VALUE=DATE:20190216
+DTEND;VALUE=DATE:20190217
+SUMMARY:Dan državnosti Srbije
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190426@kayaposoft.com
+DTSTART;VALUE=DATE:20190426
+DTEND;VALUE=DATE:20190427
+SUMMARY:Veliki petak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190428@kayaposoft.com
+DTSTART;VALUE=DATE:20190428
+DTEND;VALUE=DATE:20190429
+SUMMARY:Vaskrs
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190429@kayaposoft.com
+DTSTART;VALUE=DATE:20190429
+DTEND;VALUE=DATE:20190430
+SUMMARY:Vaskrsni ponedeljak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20190502@kayaposoft.com
+DTSTART;VALUE=DATE:20190502
+DTEND;VALUE=DATE:20190503
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20191111@kayaposoft.com
+DTSTART;VALUE=DATE:20191111
+DTEND;VALUE=DATE:20191112
+SUMMARY:Dan primirja
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200102@kayaposoft.com
+DTSTART;VALUE=DATE:20200102
+DTEND;VALUE=DATE:20200103
+SUMMARY:Nova Godina
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200107@kayaposoft.com
+DTSTART;VALUE=DATE:20200107
+DTEND;VALUE=DATE:20200108
+SUMMARY:Božić
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200215@kayaposoft.com
+DTSTART;VALUE=DATE:20200215
+DTEND;VALUE=DATE:20200216
+SUMMARY:Dan državnosti Srbije
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200216@kayaposoft.com
+DTSTART;VALUE=DATE:20200216
+DTEND;VALUE=DATE:20200217
+SUMMARY:Dan državnosti Srbije
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200217@kayaposoft.com
+DTSTART;VALUE=DATE:20200217
+DTEND;VALUE=DATE:20200218
+SUMMARY:Dan državnosti Srbije
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200417@kayaposoft.com
+DTSTART;VALUE=DATE:20200417
+DTEND;VALUE=DATE:20200418
+SUMMARY:Veliki petak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200419@kayaposoft.com
+DTSTART;VALUE=DATE:20200419
+DTEND;VALUE=DATE:20200420
+SUMMARY:Vaskrs
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200420@kayaposoft.com
+DTSTART;VALUE=DATE:20200420
+DTEND;VALUE=DATE:20200421
+SUMMARY:Vaskrsni ponedeljak
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20200502@kayaposoft.com
+DTSTART;VALUE=DATE:20200502
+DTEND;VALUE=DATE:20200503
+SUMMARY:Praznik rada
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-srb-20201111@kayaposoft.com
+DTSTART;VALUE=DATE:20201111
+DTEND;VALUE=DATE:20201112
+SUMMARY:Dan primirja
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/singapore.ics b/app/src/main/assets/singapore.ics
new file mode 100755
index 000000000..cddb5cb13
--- /dev/null
+++ b/app/src/main/assets/singapore.ics
@@ -0,0 +1,125 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+UID:20181225-christmas-day@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Christmas Day
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181106
+DTEND;VALUE=DATE:20181107
+UID:20181106-deepavali@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Deepavali
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191027
+DTEND;VALUE=DATE:20191028
+UID:20191027-deepavali@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Deepavali
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180822
+DTEND;VALUE=DATE:20180823
+UID:20180822-hari-raya-haji@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Haji
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190811
+DTEND;VALUE=DATE:20190812
+UID:20190811-hari-raya-haji@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Haji
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180809
+DTEND;VALUE=DATE:20180810
+UID:20180809-national-day@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:National Day
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180615
+DTEND;VALUE=DATE:20180616
+UID:20180615-hari-raya-puasa@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Puasa
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190605
+DTEND;VALUE=DATE:20190606
+UID:20190605-hari-raya-puasa@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Hari Raya Puasa
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180529
+DTEND;VALUE=DATE:20180530
+UID:20180529-vesak-day@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Vesak Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190519
+DTEND;VALUE=DATE:20190520
+UID:20190519-vesak-day@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Vesak Day
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+UID:20180501-labour-day@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Labour Day
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180330
+DTEND;VALUE=DATE:20180331
+UID:20180330-good-friday@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Good Friday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+UID:20190419-good-friday@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Good Friday
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180217
+DTEND;VALUE=DATE:20180218
+UID:20180217-chinese-new-year@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Chinese New Year
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180216
+DTEND;VALUE=DATE:20180217
+UID:20180216-chinese-new-year@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Chinese New Year
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+UID:20180101-new-years-day@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:New Year's Day
+RRULE:FREQ=YEARLY;INTERVAL=1
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190205
+DTEND;VALUE=DATE:20190206
+UID:20190205-chinese-new-year@www.mom.gov.sg
+STATUS:CONFIRMED
+SUMMARY:Chinese New Year
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/southafrica.ics b/app/src/main/assets/southafrica.ics
new file mode 100644
index 000000000..f63d6c694
--- /dev/null
+++ b/app/src/main/assets/southafrica.ics
@@ -0,0 +1,275 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-zaf-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:New Year's Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180321@kayaposoft.com
+DTSTART;VALUE=DATE:20180321
+DTEND;VALUE=DATE:20180322
+SUMMARY:Human Rights Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180330@kayaposoft.com
+DTSTART;VALUE=DATE:20180330
+DTEND;VALUE=DATE:20180331
+SUMMARY:Good Friday
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180402@kayaposoft.com
+DTSTART;VALUE=DATE:20180402
+DTEND;VALUE=DATE:20180403
+SUMMARY:Family Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180427@kayaposoft.com
+DTSTART;VALUE=DATE:20180427
+DTEND;VALUE=DATE:20180428
+SUMMARY:Freedom Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Workers' Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180616@kayaposoft.com
+DTSTART;VALUE=DATE:20180616
+DTEND;VALUE=DATE:20180617
+SUMMARY:Youth Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180809@kayaposoft.com
+DTSTART;VALUE=DATE:20180809
+DTEND;VALUE=DATE:20180810
+SUMMARY:National Women's Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20180924@kayaposoft.com
+DTSTART;VALUE=DATE:20180924
+DTEND;VALUE=DATE:20180925
+SUMMARY:Heritage Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20181216@kayaposoft.com
+DTSTART;VALUE=DATE:20181216
+DTEND;VALUE=DATE:20181217
+SUMMARY:Day of Reconciliation
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20181217@kayaposoft.com
+DTSTART;VALUE=DATE:20181217
+DTEND;VALUE=DATE:20181218
+SUMMARY:Day of Reconciliation
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Christmas Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Day of Goodwill
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:New Year's Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190321@kayaposoft.com
+DTSTART;VALUE=DATE:20190321
+DTEND;VALUE=DATE:20190322
+SUMMARY:Human Rights Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190419@kayaposoft.com
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+SUMMARY:Good Friday
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:Family Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190427@kayaposoft.com
+DTSTART;VALUE=DATE:20190427
+DTEND;VALUE=DATE:20190428
+SUMMARY:Freedom Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Workers' Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190616@kayaposoft.com
+DTSTART;VALUE=DATE:20190616
+DTEND;VALUE=DATE:20190617
+SUMMARY:Youth Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190617@kayaposoft.com
+DTSTART;VALUE=DATE:20190617
+DTEND;VALUE=DATE:20190618
+SUMMARY:Youth Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190809@kayaposoft.com
+DTSTART;VALUE=DATE:20190809
+DTEND;VALUE=DATE:20190810
+SUMMARY:National Women's Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20190924@kayaposoft.com
+DTSTART;VALUE=DATE:20190924
+DTEND;VALUE=DATE:20190925
+SUMMARY:Heritage Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20191216@kayaposoft.com
+DTSTART;VALUE=DATE:20191216
+DTEND;VALUE=DATE:20191217
+SUMMARY:Day of Reconciliation
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Christmas Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Day of Goodwill
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:New Year's Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200321@kayaposoft.com
+DTSTART;VALUE=DATE:20200321
+DTEND;VALUE=DATE:20200322
+SUMMARY:Human Rights Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200410@kayaposoft.com
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+SUMMARY:Good Friday
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Family Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200427@kayaposoft.com
+DTSTART;VALUE=DATE:20200427
+DTEND;VALUE=DATE:20200428
+SUMMARY:Freedom Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Workers' Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200616@kayaposoft.com
+DTSTART;VALUE=DATE:20200616
+DTEND;VALUE=DATE:20200617
+SUMMARY:Youth Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200809@kayaposoft.com
+DTSTART;VALUE=DATE:20200809
+DTEND;VALUE=DATE:20200810
+SUMMARY:National Women's Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200810@kayaposoft.com
+DTSTART;VALUE=DATE:20200810
+DTEND;VALUE=DATE:20200811
+SUMMARY:National Women's Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20200924@kayaposoft.com
+DTSTART;VALUE=DATE:20200924
+DTEND;VALUE=DATE:20200925
+SUMMARY:Heritage Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20201216@kayaposoft.com
+DTSTART;VALUE=DATE:20201216
+DTEND;VALUE=DATE:20201217
+SUMMARY:Day of Reconciliation
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Christmas Day
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-zaf-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:Day of Goodwill
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/southkorea.ics b/app/src/main/assets/southkorea.ics
index 014e7487d..36b09edcb 100755
--- a/app/src/main/assets/southkorea.ics
+++ b/app/src/main/assets/southkorea.ics
@@ -23,8 +23,8 @@ END:VEVENT
BEGIN:VEVENT
SUMMARY:설날 Lunar New Year's Day
UID:3d064a26-70f2-431a-985a-ffd27be9e210
-DTSTART;VALUE=DATE:20190205
-DTEND;VALUE=DATE:20190206
+DTSTART;VALUE=DATE:20190204
+DTEND;VALUE=DATE:20190207
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
@@ -44,8 +44,15 @@ END:VEVENT
BEGIN:VEVENT
SUMMARY:추석(한가위) Harvest Festival
UID:14bd697c-a319-47fd-9abc-fdff74be58e5
-DTSTART;VALUE=DATE:20201001
-DTEND;VALUE=DATE:20201002
+DTSTART;VALUE=DATE:20200930
+DTEND;VALUE=DATE:20201003
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:추석(한가위) Harvest Festival
+UID:14bd697c-a319-47fd-9abc-fdffse58aa
+DTSTART;VALUE=DATE:20210920
+DTEND;VALUE=DATE:20210923
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
@@ -65,14 +72,6 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:식목일 Arbor Day
-UID:5ae39a0f-5ef9-44d9-9dd5-e90049578e07
-DTSTART;VALUE=DATE:20000405
-DTEND;VALUE=DATE:20000406
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:어린이 날 Children's Day
UID:5a0c39c0-da3b-407d-824b-890c6b82bd95
DTSTART;VALUE=DATE:20000505
@@ -81,22 +80,6 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:어버이 날 Paren't Day
-UID:741d7127-995f-43e0-a014-04280dcbb661
-DTSTART;VALUE=DATE:20000510
-DTEND;VALUE=DATE:20000511
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:스승의 날 Teacher's Day
-UID:5309e45c-acca-4b4f-b7c4-fe7b3ddd545f
-DTSTART;VALUE=DATE:20000515
-DTEND;VALUE=DATE:20000516
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:현충일 Memorial Day
UID:c67d164c-c91c-4b46-a0fa-bff756119cde
DTSTART;VALUE=DATE:20000606
@@ -105,22 +88,6 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:6-25 사변일 6/25 War Memorial
-UID:c5907e9d-4314-423f-9441-d7309d39db45
-DTSTART;VALUE=DATE:20000625
-DTEND;VALUE=DATE:20000626
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:제헌절 Constitution Day
-UID:4f97c701-816b-4c23-a71e-13d2d2419c8f
-DTSTART;VALUE=DATE:20000717
-DTEND;VALUE=DATE:20000718
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:광복절 Liberation Day
UID:1aba5d05-a8a7-4253-a6d2-66665bd2490d
DTSTART;VALUE=DATE:20000815
@@ -129,14 +96,6 @@ STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:국군의 날 Armed Forces Day
-UID:99c12465-6366-4c9e-99c8-8635969cc7ee
-DTSTART;VALUE=DATE:20001001
-DTEND;VALUE=DATE:20001002
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:개천절 Foundation Day
UID:220f5ee6-10a6-4227-87ca-197628adb976
DTSTART;VALUE=DATE:20001003
diff --git a/app/src/main/assets/sweden.ics b/app/src/main/assets/sweden.ics
old mode 100755
new mode 100644
index 9d8c190ca..6ec8f27e4
--- a/app/src/main/assets/sweden.ics
+++ b/app/src/main/assets/sweden.ics
@@ -1,380 +1,275 @@
-BEGIN:VCALENDAR
-BEGIN:VEVENT
-SUMMARY:Kristi himmelfärdsdag
-UID:uuid1211149223742
-DTSTART;VALUE=DATE:20170525
-DTEND;VALUE=DATE:20170526
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pingstafton
-UID:uuid1211149223743
-DTSTART;VALUE=DATE:20170603
-DTEND;VALUE=DATE:20170604
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pingstdagen
-UID:uuid1211149223744
-DTSTART;VALUE=DATE:20170604
-DTEND;VALUE=DATE:20170605
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nationaldagen
-UID:uuid1211149223745
-DTSTART;VALUE=DATE:20170606
-DTEND;VALUE=DATE:20170607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Midsommarafton
-UID:a076d1a7-69bb-4af5-9a60-89a3c533e6bf
-DTSTART;VALUE=DATE:20170623
-DTEND;VALUE=DATE:20170624
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Midsommardagen
-UID:uuid1211149223746
-DTSTART;VALUE=DATE:20170624
-DTEND;VALUE=DATE:20170625
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Övergång till vintertid
-UID:uuid1211149223747
-DTSTART;VALUE=DATE:20171029
-DTEND;VALUE=DATE:20171030
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Alla helgons dag
-UID:uuid1211149223748
-DTSTART;VALUE=DATE:20171104
-DTEND;VALUE=DATE:20171105
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Julafton
-UID:uuid1211149223749
-DTSTART;VALUE=DATE:20171224
-DTEND;VALUE=DATE:20171225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Juldagen
-UID:uuid1211149223750
-DTSTART;VALUE=DATE:20171225
-DTEND;VALUE=DATE:20171226
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Annandag jul
-UID:uuid1211149223751
-DTSTART;VALUE=DATE:20171226
-DTEND;VALUE=DATE:20171227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nyårsafton
-UID:uuid1211149223752
-DTSTART;VALUE=DATE:20171231
-DTEND;VALUE=DATE:20180101
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nyårsdagen
-UID:uuid1211149223753
-DTSTART;VALUE=DATE:20180101
-DTEND;VALUE=DATE:20180102
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trettondagsafton
-UID:uuid1211149223754
-DTSTART;VALUE=DATE:20180105
-DTEND;VALUE=DATE:20180106
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trettondag jul
-UID:uuid1211149223755
-DTSTART;VALUE=DATE:20180106
-DTEND;VALUE=DATE:20180107
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Skärtorsdag
-UID:uuid1211149223757
-DTSTART;VALUE=DATE:20180329
-DTEND;VALUE=DATE:20180330
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Långfredag
-UID:uuid1211149223758
-DTSTART;VALUE=DATE:20180330
-DTEND;VALUE=DATE:20180331
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Påskdagen
-UID:uuid1211149223759
-DTSTART;VALUE=DATE:20180401
-DTEND;VALUE=DATE:20180402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Annandag påsk
-UID:uuid1211149223760
-DTSTART;VALUE=DATE:20180402
-DTEND;VALUE=DATE:20180403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Valborgsmässoafton
-UID:uuid1211149223761
-DTSTART;VALUE=DATE:20180430
-DTEND;VALUE=DATE:20180501
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Första maj
-UID:uuid1211149223762
-DTSTART;VALUE=DATE:20180501
-DTEND;VALUE=DATE:20180502
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Kristi himmelfärdsdag
-UID:uuid1211149223763
-DTSTART;VALUE=DATE:20180510
-DTEND;VALUE=DATE:20180511
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pingstafton
-UID:uuid1211149223764
-DTSTART;VALUE=DATE:20180519
-DTEND;VALUE=DATE:20180520
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pingstdagen
-UID:uuid1211149223765
-DTSTART;VALUE=DATE:20180520
-DTEND;VALUE=DATE:20180521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nationaldagen
-UID:uuid1211149223766
-DTSTART;VALUE=DATE:20180606
-DTEND;VALUE=DATE:20180607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Midsommarafton
-UID:ed74665e-e6b5-4dc8-8788-48eaab5640eb
-DTSTART;VALUE=DATE:20180622
-DTEND;VALUE=DATE:20180623
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Midsommardagen
-UID:uuid1211149223767
-DTSTART;VALUE=DATE:20180623
-DTEND;VALUE=DATE:20180624
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Övergång till vintertid
-UID:uuid1211149223768
-DTSTART;VALUE=DATE:20181028
-DTEND;VALUE=DATE:20181029
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Alla helgons dag
-UID:uuid1211149223769
-DTSTART;VALUE=DATE:20181103
-DTEND;VALUE=DATE:20181104
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Julafton
-UID:uuid1211149223770
-DTSTART;VALUE=DATE:20181224
-DTEND;VALUE=DATE:20181225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Juldagen
-UID:uuid1211149223771
-DTSTART;VALUE=DATE:20181225
-DTEND;VALUE=DATE:20181226
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Annandag jul
-UID:uuid1211149223772
-DTSTART;VALUE=DATE:20181226
-DTEND;VALUE=DATE:20181227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nyårsafton
-UID:uuid1211149223773
-DTSTART;VALUE=DATE:20181231
-DTEND;VALUE=DATE:20190101
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nyårsdagen
-UID:uuid1211149223774
-DTSTART;VALUE=DATE:20190101
-DTEND;VALUE=DATE:20190102
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trettondagsafton
-UID:uuid1211149223775
-DTSTART;VALUE=DATE:20190105
-DTEND;VALUE=DATE:20190106
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trettondag jul
-UID:uuid1211149223776
-DTSTART;VALUE=DATE:20190106
-DTEND;VALUE=DATE:20190107
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Skärtorsdag
-UID:uuid1211149223778
-DTSTART;VALUE=DATE:20190418
-DTEND;VALUE=DATE:20190419
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Långfredag
-UID:uuid1211149223779
-DTSTART;VALUE=DATE:20190419
-DTEND;VALUE=DATE:20190420
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Påskdagen
-UID:uuid1211149223780
-DTSTART;VALUE=DATE:20190421
-DTEND;VALUE=DATE:20190422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Annandag påsk
-UID:uuid1211149223781
-DTSTART;VALUE=DATE:20190422
-DTEND;VALUE=DATE:20190423
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Valborgsmässoafton
-UID:uuid1211149223782
-DTSTART;VALUE=DATE:20190430
-DTEND;VALUE=DATE:20190501
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Första maj
-UID:uuid1211149223783
-DTSTART;VALUE=DATE:20190501
-DTEND;VALUE=DATE:20190502
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Kristi himmelfärdsdag
-UID:uuid1211149223784
-DTSTART;VALUE=DATE:20190530
-DTEND;VALUE=DATE:20190531
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nationaldagen
-UID:uuid1211149223785
-DTSTART;VALUE=DATE:20190606
-DTEND;VALUE=DATE:20190607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pingstafton
-UID:uuid1211149223786
-DTSTART;VALUE=DATE:20190608
-DTEND;VALUE=DATE:20190609
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pingstdagen
-UID:uuid1211149223787
-DTSTART;VALUE=DATE:20190609
-DTEND;VALUE=DATE:20190610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Midsommarafton
-UID:eeea5d95-9cd0-46ff-84a9-e6c575b3fca1
-DTSTART;VALUE=DATE:20190621
-DTEND;VALUE=DATE:20190622
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Midsommardagen
-UID:uuid1211149223788
-DTSTART;VALUE=DATE:20190622
-DTEND;VALUE=DATE:20190623
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Övergång till vintertid
-UID:uuid1211149223789
-DTSTART;VALUE=DATE:20191027
-DTEND;VALUE=DATE:20191028
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Alla helgons dag
-UID:uuid1211149223790
-DTSTART;VALUE=DATE:20191102
-DTEND;VALUE=DATE:20191103
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Julafton
-UID:uuid1211149223791
-DTSTART;VALUE=DATE:20191224
-DTEND;VALUE=DATE:20191225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Juldagen
-UID:uuid1211149223792
-DTSTART;VALUE=DATE:20191225
-DTEND;VALUE=DATE:20191226
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Annandag jul
-UID:uuid1211149223793
-DTSTART;VALUE=DATE:20191226
-DTEND;VALUE=DATE:20191227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Nyårsafton
-UID:uuid1211149223794
-DTSTART;VALUE=DATE:20191231
-DTEND;VALUE=DATE:20200101
-STATUS:CONFIRMED
-END:VEVENT
-END:VCALENDAR
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-swe-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Nyårsdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180106@kayaposoft.com
+DTSTART;VALUE=DATE:20180106
+DTEND;VALUE=DATE:20180107
+SUMMARY:Trettondagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180330@kayaposoft.com
+DTSTART;VALUE=DATE:20180330
+DTEND;VALUE=DATE:20180331
+SUMMARY:Långfredagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180401@kayaposoft.com
+DTSTART;VALUE=DATE:20180401
+DTEND;VALUE=DATE:20180402
+SUMMARY:Påskdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180402@kayaposoft.com
+DTSTART;VALUE=DATE:20180402
+DTEND;VALUE=DATE:20180403
+SUMMARY:Annandag påsk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:Första maj
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180510@kayaposoft.com
+DTSTART;VALUE=DATE:20180510
+DTEND;VALUE=DATE:20180511
+SUMMARY:Kristi himmelfärds dag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180520@kayaposoft.com
+DTSTART;VALUE=DATE:20180520
+DTEND;VALUE=DATE:20180521
+SUMMARY:Pingstdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180606@kayaposoft.com
+DTSTART;VALUE=DATE:20180606
+DTEND;VALUE=DATE:20180607
+SUMMARY:Sveriges nationaldag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20180623@kayaposoft.com
+DTSTART;VALUE=DATE:20180623
+DTEND;VALUE=DATE:20180624
+SUMMARY:Midsommardagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20181103@kayaposoft.com
+DTSTART;VALUE=DATE:20181103
+DTEND;VALUE=DATE:20181104
+SUMMARY:Alla helgons dag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Juldagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20181226@kayaposoft.com
+DTSTART;VALUE=DATE:20181226
+DTEND;VALUE=DATE:20181227
+SUMMARY:Annandag jul
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Nyårsdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190106@kayaposoft.com
+DTSTART;VALUE=DATE:20190106
+DTEND;VALUE=DATE:20190107
+SUMMARY:Trettondagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190419@kayaposoft.com
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+SUMMARY:Långfredagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190421@kayaposoft.com
+DTSTART;VALUE=DATE:20190421
+DTEND;VALUE=DATE:20190422
+SUMMARY:Påskdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190422@kayaposoft.com
+DTSTART;VALUE=DATE:20190422
+DTEND;VALUE=DATE:20190423
+SUMMARY:Annandag påsk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:Första maj
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190530@kayaposoft.com
+DTSTART;VALUE=DATE:20190530
+DTEND;VALUE=DATE:20190531
+SUMMARY:Kristi himmelfärds dag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190606@kayaposoft.com
+DTSTART;VALUE=DATE:20190606
+DTEND;VALUE=DATE:20190607
+SUMMARY:Sveriges nationaldag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190609@kayaposoft.com
+DTSTART;VALUE=DATE:20190609
+DTEND;VALUE=DATE:20190610
+SUMMARY:Pingstdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20190622@kayaposoft.com
+DTSTART;VALUE=DATE:20190622
+DTEND;VALUE=DATE:20190623
+SUMMARY:Midsommardagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20191102@kayaposoft.com
+DTSTART;VALUE=DATE:20191102
+DTEND;VALUE=DATE:20191103
+SUMMARY:Alla helgons dag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Juldagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20191226@kayaposoft.com
+DTSTART;VALUE=DATE:20191226
+DTEND;VALUE=DATE:20191227
+SUMMARY:Annandag jul
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Nyårsdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200106@kayaposoft.com
+DTSTART;VALUE=DATE:20200106
+DTEND;VALUE=DATE:20200107
+SUMMARY:Trettondagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200410@kayaposoft.com
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+SUMMARY:Långfredagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200412@kayaposoft.com
+DTSTART;VALUE=DATE:20200412
+DTEND;VALUE=DATE:20200413
+SUMMARY:Påskdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200413@kayaposoft.com
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Annandag påsk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:Första maj
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200521@kayaposoft.com
+DTSTART;VALUE=DATE:20200521
+DTEND;VALUE=DATE:20200522
+SUMMARY:Kristi himmelfärds dag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200531@kayaposoft.com
+DTSTART;VALUE=DATE:20200531
+DTEND;VALUE=DATE:20200601
+SUMMARY:Pingstdagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200606@kayaposoft.com
+DTSTART;VALUE=DATE:20200606
+DTEND;VALUE=DATE:20200607
+SUMMARY:Sveriges nationaldag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20200620@kayaposoft.com
+DTSTART;VALUE=DATE:20200620
+DTEND;VALUE=DATE:20200621
+SUMMARY:Midsommardagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20201031@kayaposoft.com
+DTSTART;VALUE=DATE:20201031
+DTEND;VALUE=DATE:20201101
+SUMMARY:Alla helgons dag
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Juldagen
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-swe-20201226@kayaposoft.com
+DTSTART;VALUE=DATE:20201226
+DTEND;VALUE=DATE:20201227
+SUMMARY:Annandag jul
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/switzerland.ics b/app/src/main/assets/switzerland.ics
index 55a506588..1d8435abe 100755
--- a/app/src/main/assets/switzerland.ics
+++ b/app/src/main/assets/switzerland.ics
@@ -1,1535 +1,219 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:VzMUzCV8Y6xdjaBaTwxzMr-CzA-KgMJZP5VLdrUTsK9SYBRJP-GA3@rb.dd
+SUMMARY:Christmas holidays
+DTSTART;VALUE=DATE:20161226
+DTEND;VALUE=DATE:20170107
+UID:ferien2017-420642-fcal.ch
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Sport holiday
+DTSTART;VALUE=DATE:20170213
+DTEND;VALUE=DATE:20170225
+UID:ferien2017-245772-fcal.ch
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Easter holidays
+DTSTART;VALUE=DATE:20170413
+DTEND;VALUE=DATE:20170418
+UID:ferien2017-420643-fcal.ch
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Spring time holidays
+DTSTART;VALUE=DATE:20170418
+DTEND;VALUE=DATE:20170429
+UID:ferien2017-420644-fcal.ch
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Labour Day
+DTSTART;VALUE=DATE:20170501
+DTEND;VALUE=DATE:20170501
+UID:ferien2017-420645-fcal.ch
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Ascension Day
DTSTART;VALUE=DATE:20170525
-DTEND;VALUE=DATE:20170526
+DTEND;VALUE=DATE:20170529
+UID:ferien2017-420646-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:J29kc7SYqSIVkoYSscdUxd-C3A-ZBgZQ8RiGENtpkzpcnK3iA-BHA@rb.dd
-DTSTART;VALUE=DATE:20170604
-DTEND;VALUE=DATE:20170605
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:Gx5IVgsNd2tW4z6VRhLGOY-ECA-C7Ewi885Faac4VUu7ILWxV-FSW@rb.dd
+SUMMARY:Whit Monday
DTSTART;VALUE=DATE:20170605
-DTEND;VALUE=DATE:20170606
+DTEND;VALUE=DATE:20170605
+UID:ferien2017-420647-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:IPMYPozyFcTKZGiidVuMEx-JtA-HuZkeqnRJwKwduoAXVUmAc-Q7q@rb.dd
-DTSTART;VALUE=DATE:20170615
-DTEND;VALUE=DATE:20170616
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:RgJx7rVnbKHbiVokdgatAA-DVA-hX6t2ciAhUGDgqXXmHcwyA-DfO@rb.dd
-DTSTART;VALUE=DATE:20170828
-DTEND;VALUE=DATE:20170829
+SUMMARY:Summer holidays
+DTSTART;VALUE=DATE:20170717
+DTEND;VALUE=DATE:20170819
+UID:ferien2017-245774-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
SUMMARY:Knabenschiessen
-UID:UmfnXP3PJIRNGbMUYdLxpx-DIA-KjDiqVcZBcfOQVs96Rr253-M2V@rb.dd
-DTSTART;VALUE=DATE:20170909
-DTEND;VALUE=DATE:20170910
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:JrqhqhPtDAfD7qoiVJr4BA-DIA-EpKuV2B6dhSo55ARhSsN8P-bqA@rb.dd
-DTSTART;VALUE=DATE:20170910
-DTEND;VALUE=DATE:20170911
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:MQ9LyqFHmaJoAIENXtfywI-D6A-Hh3zkqEXHTUFwBqsKKgI5T-EzR@rb.dd
DTSTART;VALUE=DATE:20170911
-DTEND;VALUE=DATE:20170912
+DTEND;VALUE=DATE:20170911
+UID:ferien2017-453610-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:ErdS2jDrwJPDP5khTTI3TI-EFA-HhnWRoNQ5rZYojn8uNcrPo-GD5@rb.dd
-DTSTART;VALUE=DATE:20170918
-DTEND;VALUE=DATE:20170919
+SUMMARY:Autumn holidays
+DTSTART;VALUE=DATE:20171009
+DTEND;VALUE=DATE:20171021
+UID:ferien2017-245775-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:H63gs3cnrQd49Zs5awFi9t-DRA-IutCQCNLnUQaH9ZEKDzrzM-BV9@rb.dd
-DTSTART;VALUE=DATE:20171203
-DTEND;VALUE=DATE:20171204
+SUMMARY:Christmas holidays
+DTSTART;VALUE=DATE:20171225
+DTEND;VALUE=DATE:20180106
+UID:ferien2017-453611-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:Rh5rRwKQAVSueW5PXWHGqZ-DTA-DLxuqVcFC33hDkfqgmVEAL-HzA@rb.dd
-DTSTART;VALUE=DATE:20171210
-DTEND;VALUE=DATE:20171211
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:MsWSah4UJSVK6DQ4GWPOoR-DSA-BruiWLKsCkWAINZNFHphno-StF@rb.dd
-DTSTART;VALUE=DATE:20171217
-DTEND;VALUE=DATE:20171218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:HIAnXRhDt4VRgZsD3U4EPh-DTA-BRIKdhf95UiojHeDaBLaIb-PDs@rb.dd
-DTSTART;VALUE=DATE:20171224
-DTEND;VALUE=DATE:20171225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:EnHZEKouJNaMQoZ7tumn9H-DmA-KVEsjCNBIggm65KO4CGjUO-QmV@rb.dd
-DTSTART;VALUE=DATE:20180208
-DTEND;VALUE=DATE:20180209
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:NzkdiFnHCfceLBgJdoVooP-DcA-IOk9eXZMxgWbnQYIoGFBOw-KMo@rb.dd
+SUMMARY:Sport holiday
DTSTART;VALUE=DATE:20180212
-DTEND;VALUE=DATE:20180213
+DTEND;VALUE=DATE:20180224
+UID:ferien2018-245776-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:NLhLufmC7cqMEtCNRP9gKs-DRA-J6NDnrsaNq8IkYTo2wtTgg-G3r@rb.dd
-DTSTART;VALUE=DATE:20180213
-DTEND;VALUE=DATE:20180214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:QVSZIkMciXZoVIDajUcsIf-DcA-HkssTeZamAo7DuJeXaVHaS-MJ7@rb.dd
-DTSTART;VALUE=DATE:20180214
-DTEND;VALUE=DATE:20180215
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:TMm2bNULkLXqqInxAHrBDk-CpA-DpQRSwjgU8bweqZJuLYQIa-MbI@rb.dd
-DTSTART;VALUE=DATE:20180218
-DTEND;VALUE=DATE:20180219
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:TLwhGpUBhuQCh7oKqj7WxE-DFA-xccK3RryEsgdKXrim58OzA-PCn@rb.dd
-DTSTART;VALUE=DATE:20180219
-DTEND;VALUE=DATE:20180220
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:EUtPDFV7rwawuIzNqNP2CT-CmA-Jy2IAwYMAtHnFztKuXBN6O-Fg6@rb.dd
-DTSTART;VALUE=DATE:20180220
-DTEND;VALUE=DATE:20180221
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Ändstraich)
-UID:EK8d2E5tPiHZTGoIDfuaBo-DEA-J5WQUsOAE5548JQSTJSTFM-H5D@rb.dd
-DTSTART;VALUE=DATE:20180221
-DTEND;VALUE=DATE:20180222
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:PX8mBZQAdCAYhFSVXOaBeS-DXA-FDYDrNeTQDpZEUTXfRirhG-BEf@rb.dd
-DTSTART;VALUE=DATE:20180330
-DTEND;VALUE=DATE:20180331
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:CKdbqUz35iDmYOWMbY9KWB-CqA-ExL6qLwZhBtnKDrZxEpYjA-E7Q@rb.dd
-DTSTART;VALUE=DATE:20180401
-DTEND;VALUE=DATE:20180402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:VkuOxxHj8gIosMJi5LFLxQ-DyA-GJPoENMh7S2M8Jx6NouqYs-G6A@rb.dd
-DTSTART;VALUE=DATE:20180402
+SUMMARY:Easter holidays
+DTSTART;VALUE=DATE:20180329
DTEND;VALUE=DATE:20180403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:KVGrKDjD8ARMWq3f67k2b3-DeA-DKmVIVforuoNMaEujiYE4Y-Rt7@rb.dd
-DTSTART;VALUE=DATE:20180405
-DTEND;VALUE=DATE:20180406
+UID:ferien2018-453612-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
SUMMARY:Sechseläuten
-UID:N69TPAgLkuxBsyVtoL2GF3-DjA-B299pI3uSSJxCnY3V8qLaZ-QuJ@rb.dd
DTSTART;VALUE=DATE:20180416
-DTEND;VALUE=DATE:20180417
+DTEND;VALUE=DATE:20180416
+UID:ferien2018-453613-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:IHeXTKYLWz6AAMVyFpL3fI-CzA-HAdBc7J6eceLFSG26W7EVV-Qpp@rb.dd
+SUMMARY:Spring time holidays
+DTSTART;VALUE=DATE:20180423
+DTEND;VALUE=DATE:20180505
+UID:ferien2018-245777-fcal.ch
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Ascension Day
DTSTART;VALUE=DATE:20180510
-DTEND;VALUE=DATE:20180511
+DTEND;VALUE=DATE:20180514
+UID:ferien2018-453614-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:QVyfjjbdZXHS5bQpVMtrPU-C3A-ExKmdIOGroQskNqwUyWPfb-N6L@rb.dd
-DTSTART;VALUE=DATE:20180520
-DTEND;VALUE=DATE:20180521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:VznfNHAfh8s7nbdpqQp3RS-ECA-amzrf6gmpuSkBYdjtFVWxA-Mdf@rb.dd
+SUMMARY:Whit Monday
DTSTART;VALUE=DATE:20180521
-DTEND;VALUE=DATE:20180522
+DTEND;VALUE=DATE:20180521
+UID:ferien2018-453615-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:H7HiFzOU8kGn5Pnk8k3e2n-JtA-IAqIqxqmf8pVbXYiFkeQMc-DRR@rb.dd
-DTSTART;VALUE=DATE:20180531
-DTEND;VALUE=DATE:20180601
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:QNcYWEiGUVeE36qZbFaNd3-DVA-DQV4Rrtz9XtIWDfQMLNbeW-JiA@rb.dd
-DTSTART;VALUE=DATE:20180827
-DTEND;VALUE=DATE:20180828
+SUMMARY:Summer holidays
+DTSTART;VALUE=DATE:20180716
+DTEND;VALUE=DATE:20180818
+UID:ferien2018-245778-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
SUMMARY:Knabenschiessen
-UID:OVX6rgYHRhAoVpW3ugEUQT-DIA-JVSOPTjhN5E6ZwDjWLg2OO-xxA@rb.dd
-DTSTART;VALUE=DATE:20180908
-DTEND;VALUE=DATE:20180909
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:NGT8nyz69WNEaeOP3VyZjc-DIA-CxiIXXH3eExw7d9fR7FVos-LRE@rb.dd
-DTSTART;VALUE=DATE:20180909
-DTEND;VALUE=DATE:20180910
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:RpT3qaIVzk46KdUwrGNsN3-D6A-KyVB2XqNKCcRDVGFhCgkxa-L2I@rb.dd
DTSTART;VALUE=DATE:20180910
-DTEND;VALUE=DATE:20180911
+DTEND;VALUE=DATE:20180910
+UID:ferien2018-453616-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:EZGXN5ffO6hDasBCY2mYT4-EFA-B6K2AVz95NXihtheGSgjS3-QSw@rb.dd
-DTSTART;VALUE=DATE:20180917
-DTEND;VALUE=DATE:20180918
+SUMMARY:Autumn holidays
+DTSTART;VALUE=DATE:20181008
+DTEND;VALUE=DATE:20181020
+UID:ferien2018-245779-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:PVNy5Fhh5wPHGFWI49XV9z-DRA-I5gHws9VLXTELIOhDM3tKG-JPp@rb.dd
-DTSTART;VALUE=DATE:20181202
-DTEND;VALUE=DATE:20181203
+SUMMARY:Christmas holidays
+DTSTART;VALUE=DATE:20181224
+DTEND;VALUE=DATE:20190105
+UID:ferien2018-453617-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:KGtK5Vy6KpBIwoVnuVLmyw-DTA-BpjXedPoaiyt6XBDZ3ioWg-SHW@rb.dd
-DTSTART;VALUE=DATE:20181209
-DTEND;VALUE=DATE:20181210
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:VieIV2SAEkcIhFMaa9VQ9j-DSA-Bqo75ogF8PKcgMSz5Kys8r-Lcb@rb.dd
-DTSTART;VALUE=DATE:20181216
-DTEND;VALUE=DATE:20181217
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:QaZbg2Ig3zHJHeoQ25qNQB-DTA-yBrytI6O8UyiV7ALtjJfaA-Ffm@rb.dd
-DTSTART;VALUE=DATE:20181223
-DTEND;VALUE=DATE:20181224
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:Bq9YH58fJ9RKyd4WVPgVuX-DmA-EZ894FVQ67ZkOnau9Uy9RC-IVa@rb.dd
-DTSTART;VALUE=DATE:20190228
-DTEND;VALUE=DATE:20190301
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:KITekW2bqERzpW5ZcbV2Sz-DcA-EajirMRGFreGjFOxQ6SzjL-CcV@rb.dd
-DTSTART;VALUE=DATE:20190304
-DTEND;VALUE=DATE:20190305
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:VDo5dTRXpiOPETTHiTkudB-DRA-Cz7WhPDM9LrHdOLPiJmmPL-BIA@rb.dd
-DTSTART;VALUE=DATE:20190305
-DTEND;VALUE=DATE:20190306
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:OOByJ8bkDaVs9RgBTtVjDD-DcA-GzQBZeidhhT8HIRtnwUsMY-OV2@rb.dd
-DTSTART;VALUE=DATE:20190306
-DTEND;VALUE=DATE:20190307
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:BReTEcjQmPCwdCgYmL9QYp-CpA-ELOAQfV7B4LXxnJfHTVJSy-QXY@rb.dd
-DTSTART;VALUE=DATE:20190310
-DTEND;VALUE=DATE:20190311
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:I34DyY4dIWKEfzgEdCgFDT-DFA-JxcC9ZrwCJNW8mMosA6OQB-QON@rb.dd
-DTSTART;VALUE=DATE:20190311
-DTEND;VALUE=DATE:20190312
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:KREBXig9uL9h3L9ssJGJKT-CmA-I87jd7Ly5BuTtMQz9gPskZ-BOE@rb.dd
-DTSTART;VALUE=DATE:20190312
-DTEND;VALUE=DATE:20190313
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Ändstraich)
-UID:EXnJEgmodPpG6YIYgrFRZk-DEA-Ihk7PR2TjrDZTMBXrTfrN7-EW7@rb.dd
-DTSTART;VALUE=DATE:20190313
-DTEND;VALUE=DATE:20190314
+SUMMARY:Sport holiday
+DTSTART;VALUE=DATE:20190211
+DTEND;VALUE=DATE:20190223
+UID:ferien2019-245780-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
SUMMARY:Sechseläuten
-UID:SxbOCftaDZqg8Kb9qMJ3Vg-DjA-GIdhBWGHHJ3EtqKdIeUSoi-RPU@rb.dd
DTSTART;VALUE=DATE:20190408
-DTEND;VALUE=DATE:20190409
+DTEND;VALUE=DATE:20190408
+UID:ferien2019-453618-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:TeMqjVSeNELiUfa3KHNzQz-DeA-Iwt4QnXqe9UzEX8VHGUHxC-JbP@rb.dd
-DTSTART;VALUE=DATE:20190411
-DTEND;VALUE=DATE:20190412
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:IaWrW6monLaQXKV8J2WCIH-DXA-IHrOji9HRQG9WchFxCD7wo-CMT@rb.dd
-DTSTART;VALUE=DATE:20190419
-DTEND;VALUE=DATE:20190420
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:S5O2VrCpnZCJriHpKB5Esj-CqA-BebWVdWGdPxw9zUJ2oYUiB-BO4@rb.dd
-DTSTART;VALUE=DATE:20190421
-DTEND;VALUE=DATE:20190422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:NszGRcWSKcF9IHJVidALYV-DyA-EwrDfQY9FM3dsYWkz3fZBi-C8B@rb.dd
-DTSTART;VALUE=DATE:20190422
+SUMMARY:Easter holidays
+DTSTART;VALUE=DATE:20190418
DTEND;VALUE=DATE:20190423
+UID:ferien2019-453619-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:J9m9CHXcIpBLDF4taWBgkQ-CzA-JSgtMFdgafW3mPRgHCogHg-Fj8@rb.dd
+SUMMARY:Spring time holidays
+DTSTART;VALUE=DATE:20190423
+DTEND;VALUE=DATE:20190504
+UID:ferien2019-453620-fcal.ch
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Ascension Day
DTSTART;VALUE=DATE:20190530
-DTEND;VALUE=DATE:20190531
+DTEND;VALUE=DATE:20190603
+UID:ferien2019-453621-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:OQwZ2p6AZZoN3bBVZQoLzP-C3A-K6NyF8NNWxV75PCYQMeVSz-88A@rb.dd
-DTSTART;VALUE=DATE:20190609
-DTEND;VALUE=DATE:20190610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:RjQ3VwjynHVdgn7q9uDb9m-ECA-Kiwf6WDujfMzMkcjhMMUCr-KQZ@rb.dd
+SUMMARY:Whit Monday
DTSTART;VALUE=DATE:20190610
-DTEND;VALUE=DATE:20190611
+DTEND;VALUE=DATE:20190610
+UID:ferien2019-453622-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:FAJ6oonaCgI2R5KzZ2qVNX-JtA-BJQVNIDkyrkSPhPNBJRiVA-I35@rb.dd
-DTSTART;VALUE=DATE:20190620
-DTEND;VALUE=DATE:20190621
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:QPQXIxxmZ7R2OErWfOq7zc-DVA-FGbn5iNkmU5tYFHGTgb9xV-EqV@rb.dd
-DTSTART;VALUE=DATE:20190826
-DTEND;VALUE=DATE:20190827
+SUMMARY:Summer holidays
+DTSTART;VALUE=DATE:20190715
+DTEND;VALUE=DATE:20190817
+UID:ferien2019-245782-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
SUMMARY:Knabenschiessen
-UID:EfNZMnSJDoqMNfCq9kgLDF-DIA-GOXJRP9rb8CB67wLZ7iX27-PNY@rb.dd
-DTSTART;VALUE=DATE:20190907
-DTEND;VALUE=DATE:20190908
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:NSZdV2GbCnmOVKUcASaTyH-DIA-B7F6WVMuacuMJwaJxP4rrY-EVu@rb.dd
-DTSTART;VALUE=DATE:20190908
-DTEND;VALUE=DATE:20190909
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:JrrEnKNgp2MJYBp86dzYVt-D6A-FujjZeZ9WNggAAnGzawIAV-REk@rb.dd
DTSTART;VALUE=DATE:20190909
-DTEND;VALUE=DATE:20190910
+DTEND;VALUE=DATE:20190909
+UID:ferien2019-453623-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:GVo8GnitPBGCGYTwDPFwn6-EFA-Jg4YxIrjkSojSOLKFNqNuK-EpJ@rb.dd
-DTSTART;VALUE=DATE:20190916
-DTEND;VALUE=DATE:20190917
+SUMMARY:Autumn holidays
+DTSTART;VALUE=DATE:20191007
+DTEND;VALUE=DATE:20191019
+UID:ferien2019-245783-fcal.ch
STATUS:CONFIRMED
END:VEVENT
BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:C59eb6AwKOBBpeMyVaZYg8-DRA-BQ4HVjQnQeTiVPGZ4RTENf-Fcr@rb.dd
-DTSTART;VALUE=DATE:20191201
-DTEND;VALUE=DATE:20191202
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:VfMpYXskqtDMhSSogLTjzs-DTA-DuFzVDk4BdY9GYkVY7QK3Z-Bcj@rb.dd
-DTSTART;VALUE=DATE:20191208
-DTEND;VALUE=DATE:20191209
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:Bbko5d5EPUeRAAY6AtqgZ6-DSA-GfmZOMtfWTK7dUhrx8hJVE-QnC@rb.dd
-DTSTART;VALUE=DATE:20191215
-DTEND;VALUE=DATE:20191216
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:JnQXFXHF7JbGyjyABHYtjT-DTA-BMAr9aPZcBc6ge2VCNEVzV-Omf@rb.dd
-DTSTART;VALUE=DATE:20191222
-DTEND;VALUE=DATE:20191223
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:VpZD6omq2We5NRt5nwJWb6-DmA-CXkNSFRfFnjpSiX8d9dmr4-BYG@rb.dd
-DTSTART;VALUE=DATE:20200220
-DTEND;VALUE=DATE:20200221
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:JiPLarKZktnmaj2wpYrKCm-DcA-RSQ264ubXJ9VVV4ebSWrnA-Fuy@rb.dd
-DTSTART;VALUE=DATE:20200224
-DTEND;VALUE=DATE:20200225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:Jax7gNMRDQVsVTBX8YIqit-DRA-FaxVPso66R3pELfRUyVIAu-7jA@rb.dd
-DTSTART;VALUE=DATE:20200225
-DTEND;VALUE=DATE:20200226
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:IbVPt55d34hm8RZiZDVHKg-DcA-DP4MAPu6c8jsjpDdVPBnLq-I5Y@rb.dd
-DTSTART;VALUE=DATE:20200226
-DTEND;VALUE=DATE:20200227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:LSzEPU3m2jApqUV5IDILYL-CpA-IxYLASVmaQT7WuGKaikEUg-OWi@rb.dd
-DTSTART;VALUE=DATE:20200301
-DTEND;VALUE=DATE:20200302
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:LVu2sbXOdBQIqHAFKru6BK-DFA-BgqsBbG33GMhLSGmW7hwVV-K8V@rb.dd
-DTSTART;VALUE=DATE:20200302
-DTEND;VALUE=DATE:20200303
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:IEImZAZOsoMbSdH5p9GWR7-CmA-GOu2HVdjQVDQFktahmhiV9-N7V@rb.dd
-DTSTART;VALUE=DATE:20200303
-DTEND;VALUE=DATE:20200304
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Ändstraich)
-UID:JZJUfPAqQ5yWkAVIyHFTDP-DEA-IRkdV6gZOukP6dgC3kPoqV-RGH@rb.dd
-DTSTART;VALUE=DATE:20200304
-DTEND;VALUE=DATE:20200305
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:EK5DnD62VHTnghcT9ACoNj-DeA-JsYZRsOXRVTaBNm7VV8HIL-GVW@rb.dd
-DTSTART;VALUE=DATE:20200409
-DTEND;VALUE=DATE:20200410
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:LChjhj6ZjfrwRQuYVS7Pob-DXA-DKVtYAygkhW6bsBc7UiAJX-JsW@rb.dd
-DTSTART;VALUE=DATE:20200410
-DTEND;VALUE=DATE:20200411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:IAOrOEbprO6ZVssknQwVTF-CqA-BumD2JdCjDqKViTV7nH5DE-PPW@rb.dd
-DTSTART;VALUE=DATE:20200412
-DTEND;VALUE=DATE:20200413
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:BFrr6EbWEoKVBZUfggPRmm-DyA-Osxa8QWpfcAjOiHXSoK6EA-HUU@rb.dd
-DTSTART;VALUE=DATE:20200413
-DTEND;VALUE=DATE:20200414
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sechseläuten
-UID:DqunDEiqWItads8JwaxDMQ-DjA-EptI4HcpM6KBWWcnRRziVA-KMq@rb.dd
-DTSTART;VALUE=DATE:20200420
-DTEND;VALUE=DATE:20200421
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:R6LoLmqAtaKkRg9GKejQxn-CzA-JECqUMVI32VAXWUIWNEzwx-Rag@rb.dd
-DTSTART;VALUE=DATE:20200521
-DTEND;VALUE=DATE:20200522
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:KQ9Ds98bdm8LmXCMtEGTsS-C3A-BV8m2M3xJb9wrdWcVzp77n-CV2@rb.dd
-DTSTART;VALUE=DATE:20200531
-DTEND;VALUE=DATE:20200601
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:CzUH3pkDy3NM8UwRZVyRZ6-ECA-GyUHcgjFAIaVVc9h9PzXZU-DE6@rb.dd
-DTSTART;VALUE=DATE:20200601
-DTEND;VALUE=DATE:20200602
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:TsrgJYdpaFEoSVqmYycFQR-JtA-IAcPL3h775ZSTsMwNrOZiU-BDL@rb.dd
-DTSTART;VALUE=DATE:20200611
-DTEND;VALUE=DATE:20200612
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:HFDd6BQEBeX4nsWNr5OyPu-DVA-Dyju2BURORVrIziZJDHyag-PJS@rb.dd
-DTSTART;VALUE=DATE:20200831
-DTEND;VALUE=DATE:20200901
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:BWEenoRLMFrJ2imKskTcGk-DIA-BkXCefH7TqEwcd4VGVempA-Brx@rb.dd
-DTSTART;VALUE=DATE:20200912
-DTEND;VALUE=DATE:20200913
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:NmCeKjB2cDqsFnUn9gxhLK-DIA-I7kNhqEFO6CBosZH2wdUVI-GcQ@rb.dd
-DTSTART;VALUE=DATE:20200913
-DTEND;VALUE=DATE:20200914
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:OosBWkZtdnVoIUpwWRw2GA-D6A-HVtBcXsBLYPkxjJAXRxyEr-SVY@rb.dd
-DTSTART;VALUE=DATE:20200914
-DTEND;VALUE=DATE:20200915
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:BKfQwtDgt5gfXES9y6eFUB-EFA-FyXeEwLEMtP3BLfR8seNMk-JSk@rb.dd
-DTSTART;VALUE=DATE:20200921
-DTEND;VALUE=DATE:20200922
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:Pa5OYrGN7VKUFZaZDHckBi-DRA-EpYfH6cjxLQcu3OF57eiMp-EYE@rb.dd
-DTSTART;VALUE=DATE:20201129
-DTEND;VALUE=DATE:20201130
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:IojnT68t8sFWW5C6r8Fgpm-DTA-FccU2CLoheGy7LVhwgatcy-Do4@rb.dd
-DTSTART;VALUE=DATE:20201206
-DTEND;VALUE=DATE:20201207
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:BoSUZJQSGUH9WpjaT4o7gJ-DSA-BPuWpHB9E5QHMe7FOoUgdQ-Pfc@rb.dd
-DTSTART;VALUE=DATE:20201213
-DTEND;VALUE=DATE:20201214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:Ln6OjZrkjmSLPVSSND6YJV-DTA-CBUNbwHiHcTbcK66IyZMWz-OO7@rb.dd
-DTSTART;VALUE=DATE:20201220
-DTEND;VALUE=DATE:20201221
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:GgAFmFkMiFh5NUD9WbbRmL-DmA-KGyVt4r2ha5DnLmtgpVEq3-DkN@rb.dd
-DTSTART;VALUE=DATE:20210211
-DTEND;VALUE=DATE:20210212
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:GW54yRwXVHSounypk6xg2x-DcA-KNkKFPBZIAtMf3fV2fXijc-DYr@rb.dd
-DTSTART;VALUE=DATE:20210215
-DTEND;VALUE=DATE:20210216
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:G5LLMXBqyLxGFHUdcS8YWh-DRA-JXjuW4cpjqTXuDotskMfNr-BZx@rb.dd
-DTSTART;VALUE=DATE:20210216
-DTEND;VALUE=DATE:20210217
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:LGkjdeVnwXdItVDNiJqV4F-DcA-IigPVnRnbJOpViidMBtufA-BNW@rb.dd
-DTSTART;VALUE=DATE:20210217
-DTEND;VALUE=DATE:20210218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:EA7Rws69V6279YBHPoiVGJ-CpA-KMLOejhQyH6OgammC39kKR-PaF@rb.dd
-DTSTART;VALUE=DATE:20210221
-DTEND;VALUE=DATE:20210222
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:BjuA8wnJCzs24FyqKfpZtc-DFA-DpKuSPwA2CVu7ctNDnPL8q-IGq@rb.dd
-DTSTART;VALUE=DATE:20210222
-DTEND;VALUE=DATE:20210223
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:bmxjwCGXZLShMLONeMmAaA-CmA-CYzoRdrTjrdTOpap3JoJcI-Dao@rb.dd
-DTSTART;VALUE=DATE:20210223
-DTEND;VALUE=DATE:20210224
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Ändstraich)
-UID:GpMkYwcdXNppJ7ixXum6R7-DEA-DrBVuqjmILtW9e4zPb9LEk-Q8u@rb.dd
-DTSTART;VALUE=DATE:20210224
-DTEND;VALUE=DATE:20210225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:RRtdYfRY7kJ8IEqGtMVuf9-DXA-PnLET4AqTjN46nLhd6CwWA-Jim@rb.dd
-DTSTART;VALUE=DATE:20210402
-DTEND;VALUE=DATE:20210403
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:Mx6XPk2mqErHh7YKOuOWGX-CqA-o8grRodM3NykwSk3aTQScA-PEA@rb.dd
-DTSTART;VALUE=DATE:20210404
-DTEND;VALUE=DATE:20210405
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:QiWwokeRnThAqrOZaViIcA-DyA-EdGegExV3g3j9VA7yIHRay-RgC@rb.dd
-DTSTART;VALUE=DATE:20210405
-DTEND;VALUE=DATE:20210406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:4YGWdJB2uWNpVzVNybTGdA-DeA-HbVyOXxutrbmqgd9W3spXG-QDt@rb.dd
-DTSTART;VALUE=DATE:20210408
-DTEND;VALUE=DATE:20210409
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sechseläuten
-UID:TrnFQDgzntyKxQgmV8r7uG-DjA-GUWewRdn6YhHjMc24H4AoJ-R2P@rb.dd
-DTSTART;VALUE=DATE:20210419
-DTEND;VALUE=DATE:20210420
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:VOoKSUT27Bm2wuDOfjHecD-CzA-GdiQFU4yQnJjUqUwWE3BHp-OxH@rb.dd
-DTSTART;VALUE=DATE:20210513
-DTEND;VALUE=DATE:20210514
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:Hp55PByQEkeqfzU5kiPqDB-C3A-C9uzxIrHhLASAQfUcEEp6L-DLg@rb.dd
-DTSTART;VALUE=DATE:20210523
-DTEND;VALUE=DATE:20210524
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:UBC2QBXj77IimdhUGrqogE-ECA-HXATspETVBwGOFRyqIwqVm-FD2@rb.dd
-DTSTART;VALUE=DATE:20210524
-DTEND;VALUE=DATE:20210525
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:C5k5VYHkyPuBomaXHmO54y-JtA-HSS5H34h5jLizneieNazMI-PYT@rb.dd
-DTSTART;VALUE=DATE:20210603
-DTEND;VALUE=DATE:20210604
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:KmYERYpXNEytAixAojmneK-DVA-K9QsNLB5w6d6w28VmeuHOo-DTT@rb.dd
-DTSTART;VALUE=DATE:20210830
-DTEND;VALUE=DATE:20210831
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:DBuxUocrIzNR4OQI9qmVIr-DIA-btCiy6jjnt5Vt2a4VzV9KA-BzA@rb.dd
-DTSTART;VALUE=DATE:20210911
-DTEND;VALUE=DATE:20210912
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:MP2oATM2LZKzW6CW3IeJ5M-DIA-hdXhBCZrwb7PrfJjNOqkGA-Ssd@rb.dd
-DTSTART;VALUE=DATE:20210912
-DTEND;VALUE=DATE:20210913
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:JfjjJ7uERMfD7EfuUnFUE9-D6A-CdYJrTkfpC8reNfMP9xOLd-LgM@rb.dd
-DTSTART;VALUE=DATE:20210913
-DTEND;VALUE=DATE:20210914
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:FF876EaUUxfrpCAqtqwV9J-EFA-IaFN64ebMR6ctMg6iYFDHV-N8b@rb.dd
-DTSTART;VALUE=DATE:20210920
-DTEND;VALUE=DATE:20210921
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:TYORmhVLWdFMHEYFZAHuTk-DRA-HsrbZKKq2Qam2Koa7MLrGf-MKi@rb.dd
-DTSTART;VALUE=DATE:20211128
-DTEND;VALUE=DATE:20211129
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:Mca2g6kY6oxXLd8FiVV7ft-DTA-DDOsJ7BVrbDNsDcAh2UNBE-PBB@rb.dd
-DTSTART;VALUE=DATE:20211205
-DTEND;VALUE=DATE:20211206
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:SUBxLtRMkmyaDYw3cuVRuA-DSA-EiURrIKOSAcR3DWKGfVRaL-J4P@rb.dd
-DTSTART;VALUE=DATE:20211212
-DTEND;VALUE=DATE:20211213
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:E43koghqem978EZDHPOoV4-DTA-K9MgMLyRmnptxPc9WTV78A-OnY@rb.dd
-DTSTART;VALUE=DATE:20211219
-DTEND;VALUE=DATE:20211220
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:RW5qkOhMUBUwePtDFi8BjB-DmA-F3ikiIuN7Z2bZcWW6V3o8W-EZO@rb.dd
-DTSTART;VALUE=DATE:20220224
-DTEND;VALUE=DATE:20220225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:UOrXEb3b3td4MzBgdnsjX5-DcA-JYqyD5VwtrzdSWKTE6CXIj-QhV@rb.dd
-DTSTART;VALUE=DATE:20220228
-DTEND;VALUE=DATE:20220301
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:KzcZoZID2KVDhu7obJTYTj-DRA-FnYNItZaTNmcxc6SruDtTO-QDC@rb.dd
-DTSTART;VALUE=DATE:20220301
-DTEND;VALUE=DATE:20220302
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:GLcgV9pVAVMjV4IoINQN3R-DcA-KS6EVPFp3VtLYKTUu9uBjT-Jga@rb.dd
-DTSTART;VALUE=DATE:20220302
-DTEND;VALUE=DATE:20220303
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:MiZeEVqzxgV77TCK6SGFZM-CpA-HCDF2UtDzG74Xz6ApUhsK6-MAD@rb.dd
-DTSTART;VALUE=DATE:20220306
-DTEND;VALUE=DATE:20220307
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:CNawM6RsgPrR3Ahhs43pRw-DFA-K65cLuUNJbcydgsN3nLs9n-Li8@rb.dd
-DTSTART;VALUE=DATE:20220307
-DTEND;VALUE=DATE:20220308
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:JFI4SpsqBWPFHbP4LHDzVW-CmA-Q34eR7hTOE25iFfNOHAy8A-KTg@rb.dd
-DTSTART;VALUE=DATE:20220308
-DTEND;VALUE=DATE:20220309
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Ändstraich)
-UID:PEMVB5CdV4w6kOXwWfa5N8-DEA-Hs9pLnuxPCqFzxjFRYw9Z3-Prm@rb.dd
-DTSTART;VALUE=DATE:20220309
-DTEND;VALUE=DATE:20220310
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:SIypLiFLMkGIdtYhP5KpnZ-DeA-DhCFWMQIotCppqOOhpTDC6-CPm@rb.dd
-DTSTART;VALUE=DATE:20220407
-DTEND;VALUE=DATE:20220408
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:Czdiq3gxIkj7DEtrCp4Fzp-DXA-Byg6WS8su2RT96uSdRiZa9-Oyz@rb.dd
-DTSTART;VALUE=DATE:20220415
-DTEND;VALUE=DATE:20220416
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:VuhIVjBWVj2kAXePxVhtrX-CqA-2ttZ4roU936gWuJ3rksN9A-GaL@rb.dd
-DTSTART;VALUE=DATE:20220417
-DTEND;VALUE=DATE:20220418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:Qeu2KNgVVAnboE8NZYT5Yi-DyA-HwbCjyLVAkyyb3kAadSMLp-Rt7@rb.dd
-DTSTART;VALUE=DATE:20220418
-DTEND;VALUE=DATE:20220419
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sechseläuten
-UID:CKrYPQoMugnRfi4UYejIn6-DjA-BJLXxcZAfRJeCqk9JCeCHz-g2A@rb.dd
-DTSTART;VALUE=DATE:20220425
-DTEND;VALUE=DATE:20220426
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:BDMqfahBQnTbwHh3Ccpkxo-CzA-IDi88MHWZjLeEIcVErFddE-MIy@rb.dd
-DTSTART;VALUE=DATE:20220526
-DTEND;VALUE=DATE:20220527
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:TgoqwCtopF8xwmxLIZi4WG-C3A-J2m4VKKO4D4cXwq7emexNr-OOp@rb.dd
-DTSTART;VALUE=DATE:20220605
-DTEND;VALUE=DATE:20220606
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:U3HkFLW9BbnfjPtaUZhsg8-ECA-FKjcKj7XcKxjookMFeqVkZ-ELw@rb.dd
-DTSTART;VALUE=DATE:20220606
-DTEND;VALUE=DATE:20220607
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:EIpQjrIXPy4xQCgcx7zsbm-JtA-C9IgpdUPLnqGBIQUZaU6VI-LVT@rb.dd
-DTSTART;VALUE=DATE:20220616
-DTEND;VALUE=DATE:20220617
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:B7c6u3soIsU9IJ5V8TiVxd-DVA-FIoxJWAJZHrN83d9wuyZnu-Czw@rb.dd
-DTSTART;VALUE=DATE:20220829
-DTEND;VALUE=DATE:20220830
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:UedWobdx5SkwL5zjfVVy3i-DIA-CzfqJetXfHbb2VnHzYpz5z-MIm@rb.dd
-DTSTART;VALUE=DATE:20220910
-DTEND;VALUE=DATE:20220911
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:MTO3QOIpRKECKV3RLrfAcV-DIA-GcaT8WTtpQpqRhxKMdDt9O-Smc@rb.dd
-DTSTART;VALUE=DATE:20220911
-DTEND;VALUE=DATE:20220912
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:MNx2jfVQUDd29a3fqdjUI5-D6A-KpNmD5ePPoybp5jwxWYiVL-HKW@rb.dd
-DTSTART;VALUE=DATE:20220912
-DTEND;VALUE=DATE:20220913
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:K8iOCgGzRtqyQBeVrigrdm-EFA-BamPOCt2BBfgIoaIOVgurV-H8S@rb.dd
-DTSTART;VALUE=DATE:20220919
-DTEND;VALUE=DATE:20220920
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:TYjef8upsfTKzMJxwFb5GS-DRA-J8rmgLXERucnEJ6U7QChHh-DZt@rb.dd
-DTSTART;VALUE=DATE:20221127
-DTEND;VALUE=DATE:20221128
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:Lby3J4PVJyVSbk2dJXLZcB-DTA-BFZ2yuk49Vu9xVLZkZu76N-Fwj@rb.dd
-DTSTART;VALUE=DATE:20221204
-DTEND;VALUE=DATE:20221205
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:FuQCIZnp5UiyHwOufYoAAQ-DSA-IdMqRi8DqKeaNjDWqQAc73-JwG@rb.dd
-DTSTART;VALUE=DATE:20221211
-DTEND;VALUE=DATE:20221212
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:yVmeT8aVZGQGp7oQb2ygzA-DTA-BcF8ZowN48YqP647gQjchE-HaS@rb.dd
-DTSTART;VALUE=DATE:20221218
-DTEND;VALUE=DATE:20221219
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:LMRZe7XeBDMoz6JBqVAS9q-DmA-CSqjaQLUBuoD7QXuWhEHxZ-RLz@rb.dd
-DTSTART;VALUE=DATE:20230216
-DTEND;VALUE=DATE:20230217
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:MFnarUuyyKyfDZjGGAGKKG-DcA-Ee5wGorMsIXTIm5w96opa9-N7b@rb.dd
-DTSTART;VALUE=DATE:20230220
-DTEND;VALUE=DATE:20230221
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:NnK3i5h8KPx56bUqHOynHA-DRA-xHgb7BNUAYxaHFtSsFGw9A-Dwt@rb.dd
-DTSTART;VALUE=DATE:20230221
-DTEND;VALUE=DATE:20230222
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:QHbmItGVyaczqecC9Ii3mN-DcA-FukPs5pAQD8popCbyHKSX4-DB7@rb.dd
-DTSTART;VALUE=DATE:20230222
-DTEND;VALUE=DATE:20230223
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:UM84FYuiKFu3BnDqdjaEVN-CpA-DB5XZ5xoVu6nTIJm8ViM2C-GVE@rb.dd
-DTSTART;VALUE=DATE:20230226
-DTEND;VALUE=DATE:20230227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:EXVH5YoXjSfqaWMNophHxd-DFA-GndMNVgVSnkfaz3ppH7U7O-G8V@rb.dd
-DTSTART;VALUE=DATE:20230227
-DTEND;VALUE=DATE:20230228
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:IwxIAm3D5UrJ8q6DHIkFqy-CmA-B8Vh8JoAtGVprfRiEbR9LN-QVT@rb.dd
-DTSTART;VALUE=DATE:20230228
-DTEND;VALUE=DATE:20230301
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Ändstraich)
-UID:OKNVmiPVQay9MGBQL7xK9f-DEA-K5ExMuu3E5VKP5POuFSqxE-Cja@rb.dd
-DTSTART;VALUE=DATE:20230301
-DTEND;VALUE=DATE:20230302
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:Rp3SR5siSjyQXWBxdOKx5Y-DeA-BgxMuGHyziXbgUch4yGPV2-KOH@rb.dd
-DTSTART;VALUE=DATE:20230406
-DTEND;VALUE=DATE:20230407
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:EgWDuVpmUnmHrbFOGRQVqO-DXA-J9XtZ2SQgjs8mGJkzARUCz-Bbx@rb.dd
-DTSTART;VALUE=DATE:20230407
-DTEND;VALUE=DATE:20230408
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:DFMJGsGR3y764eMX5kN9ZB-CqA-IoNTR5HqrKoWUsNioV9UAL-DGE@rb.dd
-DTSTART;VALUE=DATE:20230409
-DTEND;VALUE=DATE:20230410
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:Ft9tgu8K9TGXqQ78UBp9If-DyA-BJmuZgetJVINVC4MBXQhOG-EQt@rb.dd
-DTSTART;VALUE=DATE:20230410
-DTEND;VALUE=DATE:20230411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sechseläuten
-UID:CbP2uCbSCiwmeX9Y8YdkgS-DjA-DixULkHFCVir3BtRiwYrY3-F3u@rb.dd
-DTSTART;VALUE=DATE:20230417
-DTEND;VALUE=DATE:20230418
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:JHMrXridMS3phVa6SrKKRV-CzA-GVXNWD6qCsi87LbHQ39qB8-Bdz@rb.dd
-DTSTART;VALUE=DATE:20230518
-DTEND;VALUE=DATE:20230519
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:Kw2ZorVkQQNZmct26gkCKR-C3A-JRgGdNzRp6oCVc3UVGLR2O-IUK@rb.dd
-DTSTART;VALUE=DATE:20230528
-DTEND;VALUE=DATE:20230529
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:NgXP2N2mPcXCiqbphqyuoT-ECA-KfxsDa4LBP5Dr2b3ykQPSm-NSM@rb.dd
-DTSTART;VALUE=DATE:20230529
-DTEND;VALUE=DATE:20230530
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:DQRc7dt4bcFkhmODuQsZBT-JtA-Cin6DBQAxNw9VaFD8j93ox-DWp@rb.dd
-DTSTART;VALUE=DATE:20230608
-DTEND;VALUE=DATE:20230609
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:UqgbyZQ9UAeHg5WfdPO3Qc-DVA-rPcUGRzwxXderOZxFjXfwA-KbP@rb.dd
-DTSTART;VALUE=DATE:20230828
-DTEND;VALUE=DATE:20230829
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:PSHRB83hoETePmDPZRcp7A-DIA-DhiuHgccBZmnSnaXsPALuS-KVQ@rb.dd
-DTSTART;VALUE=DATE:20230909
-DTEND;VALUE=DATE:20230910
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:BUMYAYyBHU77ksHBzubP9I-DIA-Bstw8yzMR7Yh5QokX872gx-G2e@rb.dd
-DTSTART;VALUE=DATE:20230910
-DTEND;VALUE=DATE:20230911
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:KaaF2hqSEsBUq3ruPcTAd5-D6A-CUy8z2q99pBxrn5sQzTIfd-MXV@rb.dd
-DTSTART;VALUE=DATE:20230911
-DTEND;VALUE=DATE:20230912
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:N2RyJW2QboQFWssK5V2qHA-EFA-FosZQQ4VBMMVm5Pmq4Nbgr-MkD@rb.dd
-DTSTART;VALUE=DATE:20230918
-DTEND;VALUE=DATE:20230919
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:LkkkB9cx6GqXObzhnEibHH-DRA-DX4kEe8bHMwitRFdid2kJH-ILs@rb.dd
-DTSTART;VALUE=DATE:20231203
-DTEND;VALUE=DATE:20231204
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:LDyYDKZp4DTGzFA4Cy3RaX-DTA-CodiQjuP9aASUBKfCosRRS-9MA@rb.dd
-DTSTART;VALUE=DATE:20231210
-DTEND;VALUE=DATE:20231211
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:HLaoLbSxLLyKWt2nfSozMc-DSA-w3IHDOKWJLKDFFT8ahAHOA-J5n@rb.dd
-DTSTART;VALUE=DATE:20231217
-DTEND;VALUE=DATE:20231218
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:OppVaF5h6Em6yBG89bwIx4-DTA-FzNFnIOn6OV7FssuqjFUVx-NaC@rb.dd
-DTSTART;VALUE=DATE:20231224
-DTEND;VALUE=DATE:20231225
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:OCLwGhyAPybC7SDT8nGFya-DmA-FgKAa3GoYYY7aGpxfRnS5e-Ob6@rb.dd
-DTSTART;VALUE=DATE:20240208
-DTEND;VALUE=DATE:20240209
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:JabYKNAkNE7VFWsG8VGZfV-DcA-FFfJ2rwcMVpBERpsJUKBWM-IGd@rb.dd
-DTSTART;VALUE=DATE:20240212
-DTEND;VALUE=DATE:20240213
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:MtDa6K6KW9FEi9N9CNY5kr-DRA-KGVe7rVCjeWD6JfkwyHziJ-Qu7@rb.dd
-DTSTART;VALUE=DATE:20240213
-DTEND;VALUE=DATE:20240214
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:PVCFBieWgN5FdYx3VJa7G2-DcA-HMLVVt4VMF49mZ46J7I4fP-RIP@rb.dd
-DTSTART;VALUE=DATE:20240214
-DTEND;VALUE=DATE:20240215
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:KILsqrJ5O3fLWijWQJRf9e-CpA-CrfFyewDEMZxmzokWOtAuY-DY9@rb.dd
-DTSTART;VALUE=DATE:20240218
-DTEND;VALUE=DATE:20240219
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:Rp83oehxSbUo28ztgdZhmR-DFA-HaFqjrbgkHyyuHQSwbkfM3-OQZ@rb.dd
-DTSTART;VALUE=DATE:20240219
-DTEND;VALUE=DATE:20240220
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:Oz4Sf6WtU3zeUFTMrZGMB7-CmA-IbLEUC4h9d8LwaxzPTqWJY-Rbq@rb.dd
-DTSTART;VALUE=DATE:20240220
-DTEND;VALUE=DATE:20240221
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:NMCewQUGWM5OkFBF5s5j4x-DXA-Ex9e9gzZ9IhQ3w7RYLRfqf-PL5@rb.dd
-DTSTART;VALUE=DATE:20240329
-DTEND;VALUE=DATE:20240330
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:VrGtOR7LueNWEe9R63gjjg-CqA-BAbTW6PyEpARgDduC7AEGV-LGK@rb.dd
-DTSTART;VALUE=DATE:20240331
-DTEND;VALUE=DATE:20240401
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:HScRtxJtJtOSVZ7PbwUBXB-DyA-Gc2OscjcjSWsYUeuVuyutV-RRX@rb.dd
-DTSTART;VALUE=DATE:20240401
-DTEND;VALUE=DATE:20240402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:GFoxzZR3V6hDazjPZMTBGn-DeA-CCfQBVoJ7qbXkgZmUoZEE2-Qng@rb.dd
-DTSTART;VALUE=DATE:20240404
-DTEND;VALUE=DATE:20240405
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sechseläuten
-UID:OdTyigYnazUSXt4YRrCPD6-DjA-IBC6Pd3bSkMTBV83Vumn4i-End@rb.dd
-DTSTART;VALUE=DATE:20240415
-DTEND;VALUE=DATE:20240416
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:CjCF2t36s7PcurO8MpYmjX-CzA-IVYzhwZWemQKken2JAE7NW-P4G@rb.dd
-DTSTART;VALUE=DATE:20240509
-DTEND;VALUE=DATE:20240510
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:PUcAuo6xRTkrLGSUkDTCcu-C3A-GsLjSUVtx5krUsY9JtVgiy-SOO@rb.dd
-DTSTART;VALUE=DATE:20240519
-DTEND;VALUE=DATE:20240520
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:BoehYGkMTdijxOpqQVukne-ECA-K6pJfcGT2phfPTMcDyZVTu-NoB@rb.dd
-DTSTART;VALUE=DATE:20240520
-DTEND;VALUE=DATE:20240521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:Swh6IEhehwBi3OkgeDZhxf-JtA-IuEFzRdcJa46uQwhkbG4AW-QAV@rb.dd
-DTSTART;VALUE=DATE:20240530
-DTEND;VALUE=DATE:20240531
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:NJDseoOZktOGbBwJiFoDzi-DVA-IcrNBBCBDp8jWGwCoDhyMA-PcP@rb.dd
-DTSTART;VALUE=DATE:20240826
-DTEND;VALUE=DATE:20240827
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:UqfS2N4FCmrCuizVyH5z9S-DIA-DfHqqLBzA8E3jodhsF9Wby-IZ8@rb.dd
-DTSTART;VALUE=DATE:20240907
-DTEND;VALUE=DATE:20240908
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:SHBVQw2uayDOJprWDFuHQr-DIA-HDH5zamqOIKqFSTdKs9TbV-MVA@rb.dd
-DTSTART;VALUE=DATE:20240908
-DTEND;VALUE=DATE:20240909
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:LdMzNmgFsacVeIqjjAYBOp-D6A-IJDGuXuEzIs8yt8VMRGOuk-Hbk@rb.dd
-DTSTART;VALUE=DATE:20240909
-DTEND;VALUE=DATE:20240910
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:I7dVVsNnA6Ao9M4eJaCcVi-EFA-KfB84mbMXGiih8W4bBIM9A-PCQ@rb.dd
-DTSTART;VALUE=DATE:20240916
-DTEND;VALUE=DATE:20240917
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:KiQTEHkLIRNGD9TNDCgPVq-DRA-CjLgN9WXqwWULmSmfqA8G7-K4Y@rb.dd
-DTSTART;VALUE=DATE:20241201
-DTEND;VALUE=DATE:20241202
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:UBdTEx6byCRdkNERd2ZDP2-DTA-HjoWN4EmrUnJnFdXwbp7VO-Idx@rb.dd
-DTSTART;VALUE=DATE:20241208
-DTEND;VALUE=DATE:20241209
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:Kx7K3uRPVWpJkrLeyVbLZg-DSA-GKBLRiSOwhytM2JUyNasCR-LGy@rb.dd
-DTSTART;VALUE=DATE:20241215
-DTEND;VALUE=DATE:20241216
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:C3aubGxKWBWKRZbEPIpqTG-DTA-K9iYN2aUAc9ZYBceU3eC3Q-E7H@rb.dd
-DTSTART;VALUE=DATE:20241222
-DTEND;VALUE=DATE:20241223
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Weiberfastnacht)
-UID:LmqH4eismFOcuonJrfh4Qw-DmA-Fmm7VawAUBphWJhFnjOUpS-B96@rb.dd
-DTSTART;VALUE=DATE:20250227
-DTEND;VALUE=DATE:20250228
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Rosenmontag)
-UID:eUgLBV24Ugwnj7WZQ2EGjA-DcA-K5wcQENN2zfgP7Vq6ynIox-KBE@rb.dd
-DTSTART;VALUE=DATE:20250303
-DTEND;VALUE=DATE:20250304
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Fastnacht)
-UID:Mk5GMkIrCoetDffHX6OpkS-DRA-KNthjE7pZfiM2QakrtgPkE-CZ5@rb.dd
-DTSTART;VALUE=DATE:20250304
-DTEND;VALUE=DATE:20250305
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Aschermittwoch)
-UID:TwkjqAs43ENOtugxLOxpUK-DcA-IKeqAym6xNQOmiHaHCIwNB-RBG@rb.dd
-DTSTART;VALUE=DATE:20250305
-DTEND;VALUE=DATE:20250306
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Funkensonntag - Bauernfastnacht)
-UID:Uyg73s64BP3G5tVVDwfKmk-CpA-IDBpBff7FB7hncMtAguN7u-IXb@rb.dd
-DTSTART;VALUE=DATE:20250309
-DTEND;VALUE=DATE:20250310
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Morgestraich)
-UID:VBQgfhFaBHKqzcCuAR5T8Y-DFA-JV97sRWkALLWGFgRdsIZpb-Fpy@rb.dd
-DTSTART;VALUE=DATE:20250310
-DTEND;VALUE=DATE:20250311
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(Basler Fasnacht)
-UID:ROefIKIWEQDbHiPCu3Mwiz-CmA-CWEpXD82DpH3IucnJUEH2A-Leo@rb.dd
-DTSTART;VALUE=DATE:20250311
-DTEND;VALUE=DATE:20250312
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Näfelser Fahrt
-UID:EVIHeyhb2Lsg8EWrAKeLCG-DeA-Bj4w7osKJBUCo2k79KL2RJ-OEx@rb.dd
-DTSTART;VALUE=DATE:20250410
-DTEND;VALUE=DATE:20250411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Karfreitag
-UID:ID3QdOdJVKQmFPmXQ6cAXg-DXA-HKtn3L3GRhjzSr8WshtW3L-BXV@rb.dd
-DTSTART;VALUE=DATE:20250418
-DTEND;VALUE=DATE:20250419
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostern
-UID:QQr6K9NWez4metOLNnBwkz-CqA-HPTyZIhLnSr9TV4RyVUtjH-P5y@rb.dd
-DTSTART;VALUE=DATE:20250420
-DTEND;VALUE=DATE:20250421
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ostermontag
-UID:NuiKR7Pj6u3NU3MU2pVLD9-DyA-Kb2jTJGBWRKmzeXSu7w5XP-Ku8@rb.dd
-DTSTART;VALUE=DATE:20250421
-DTEND;VALUE=DATE:20250422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Sechseläuten
-UID:CWnaqBMKOzpkXH8qdGWSN7-DjA-Nu4w6teNdfIm99HeBgA5WA-FxA@rb.dd
-DTSTART;VALUE=DATE:20250428
-DTEND;VALUE=DATE:20250429
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Auffahrt
-UID:Oz7UHQBPSXrVj6o4ZUj42K-CzA-JhaSnENLbeVQcXLiKbRzuZ-KVU@rb.dd
-DTSTART;VALUE=DATE:20250529
-DTEND;VALUE=DATE:20250530
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingsten
-UID:U3eqqSDbtM98uVO4PbPV2I-C3A-JpD3ztsQBbrKYBDpwnD24q-MRi@rb.dd
-DTSTART;VALUE=DATE:20250608
-DTEND;VALUE=DATE:20250609
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pfingstmontag
-UID:Kau9itoF2AF3Ooo8yqYZVx-ECA-xJExMU4GFgmd9ACELc8tYA-4xA@rb.dd
-DTSTART;VALUE=DATE:20250609
-DTEND;VALUE=DATE:20250610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Fronleichnam
-UID:LItOChzMfWIGmboJInwQVP-JtA-IXQtNrXIwCdhk86D2Hj8zi-B5F@rb.dd
-DTSTART;VALUE=DATE:20250619
-DTEND;VALUE=DATE:20250620
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Genfer Bettag
-UID:Q8KYtfGyIGURrPfExXQkPj-DVA-KkzMVIHxgNGThFwWDjmGIy-OET@rb.dd
-DTSTART;VALUE=DATE:20250901
-DTEND;VALUE=DATE:20250902
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:EiQSZwos9sARLPKQUppV3Z-DIA-KeydPzLuF6ZYahkV2QOJBN-IV5@rb.dd
-DTSTART;VALUE=DATE:20250913
-DTEND;VALUE=DATE:20250914
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:NmdmkEFQ7PSaJdCeKVZG9Q-DIA-JEQPkGut3TgAguwDYEHcFo-SEs@rb.dd
-DTSTART;VALUE=DATE:20250914
-DTEND;VALUE=DATE:20250915
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Knabenschiessen
-UID:Qw5s2HPGZTzNBb3jyFm73x-D6A-FUVR6ngz4EHGuFqVTGcuGA-SWf@rb.dd
-DTSTART;VALUE=DATE:20250915
-DTEND;VALUE=DATE:20250916
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Bettagsmontag
-UID:MR2ATWMEcDigpI8HHUXjnG-EFA-JMRVuyI6h9WAQPeHoBZt5d-Hs9@rb.dd
-DTSTART;VALUE=DATE:20250922
-DTEND;VALUE=DATE:20250923
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(1. Advent)
-UID:ObbMtUEw2477HBCJrOxuiS-DRA-DEMdpmKSauYP2SBOxeOqph-CZA@rb.dd
-DTSTART;VALUE=DATE:20251130
-DTEND;VALUE=DATE:20251201
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(2. Advent)
-UID:E3WOSjMAWtJVwfSMPXxaR9-DTA-FJ2BXiQ3zOrB6uDDdcsgfV-CP5@rb.dd
-DTSTART;VALUE=DATE:20251207
-DTEND;VALUE=DATE:20251208
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(3. Advent)
-UID:SDapuDFJ7TG9GVEnBm8A49-DSA-KEBSj4OVyYbstmuTBVq3qA-QVM@rb.dd
-DTSTART;VALUE=DATE:20251214
-DTEND;VALUE=DATE:20251215
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:(4. Advent)
-UID:RL2MhCWHIgxgzr4PVci4B8-DTA-Gi67wIVN6LLZwNJRRhboN2-PyU@rb.dd
-DTSTART;VALUE=DATE:20251221
-DTEND;VALUE=DATE:20251222
+SUMMARY:Christmas holidays
+DTSTART;VALUE=DATE:20191223
+DTEND;VALUE=DATE:20200104
+UID:ferien2019-453624-fcal.ch
STATUS:CONFIRMED
END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/assets/taiwan.ics b/app/src/main/assets/taiwan.ics
new file mode 100644
index 000000000..c2598bfdc
--- /dev/null
+++ b/app/src/main/assets/taiwan.ics
@@ -0,0 +1,668 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200329
+DTEND;VALUE=DATE:20200330
+DTSTAMP:20190328T061054Z
+UID:20200329_60o30or4cgo30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:青年節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190329
+DTEND;VALUE=DATE:20190330
+DTSTAMP:20190328T061054Z
+UID:20190329_60o30or4cgo30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:青年節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180329
+DTEND;VALUE=DATE:20180330
+DTSTAMP:20190328T061054Z
+UID:20180329_60o30or4cgo30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:青年節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20201025
+DTEND;VALUE=DATE:20201026
+DTSTAMP:20190328T061054Z
+UID:20201025_60o30dppc8o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:重陽節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191007
+DTEND;VALUE=DATE:20191008
+DTSTAMP:20190328T061054Z
+UID:20191007_60o30dppc8o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:重陽節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181017
+DTEND;VALUE=DATE:20181018
+DTSTAMP:20190328T061054Z
+UID:20181017_60o30dppc8o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:重陽節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200204
+DTEND;VALUE=DATE:20200205
+DTSTAMP:20190328T061054Z
+UID:20200204_60o30dppcoo30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農民節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190204
+DTEND;VALUE=DATE:20190205
+DTSTAMP:20190328T061054Z
+UID:20190204_60o30dppcoo30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農民節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180204
+DTEND;VALUE=DATE:20180205
+DTSTAMP:20190328T061054Z
+UID:20180204_60o30dppcoo30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農民節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200124
+DTEND;VALUE=DATE:20200125
+DTSTAMP:20190328T061054Z
+UID:20200124_60o30dpo70o30e1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆除夕
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190204
+DTEND;VALUE=DATE:20190205
+DTSTAMP:20190328T061054Z
+UID:20190204_60o30dpo70o30e1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆除夕
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180215
+DTEND;VALUE=DATE:20180216
+DTSTAMP:20190328T061054Z
+UID:20180215_60o30dpo70o30e1g60o32chmc4@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆除夕
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200128
+DTEND;VALUE=DATE:20200129
+DTSTAMP:20190328T061054Z
+UID:20200128_60o30dpocco30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初四
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190208
+DTEND;VALUE=DATE:20190209
+DTSTAMP:20190328T061054Z
+UID:20190208_60o30dpocco30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初四
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180219
+DTEND;VALUE=DATE:20180220
+DTSTAMP:20190328T061054Z
+UID:20180219_60o30dpocco30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初四
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200129
+DTEND;VALUE=DATE:20200130
+DTSTAMP:20190328T061054Z
+UID:20200129_60o30ob368o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初五
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190209
+DTEND;VALUE=DATE:20190210
+DTSTAMP:20190328T061054Z
+UID:20190209_60o30ob368o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初五
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180220
+DTEND;VALUE=DATE:20180221
+DTSTAMP:20190328T061054Z
+UID:20180220_60o30ob368o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初五
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200126
+DTEND;VALUE=DATE:20200127
+DTSTAMP:20190328T061054Z
+UID:20200126_60o30dpoc4o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初二
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190206
+DTEND;VALUE=DATE:20190207
+DTSTAMP:20190328T061054Z
+UID:20190206_60o30dpoc4o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初二
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180217
+DTEND;VALUE=DATE:20180218
+DTSTAMP:20190328T061054Z
+UID:20180217_60o30dpoc4o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初二
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200127
+DTEND;VALUE=DATE:20200128
+DTSTAMP:20190328T061054Z
+UID:20200127_60o30dpoc8o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初三
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190207
+DTEND;VALUE=DATE:20190208
+DTSTAMP:20190328T061054Z
+UID:20190207_60o30dpoc8o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初三
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180218
+DTEND;VALUE=DATE:20180219
+DTSTAMP:20190328T061054Z
+UID:20180218_60o30dpoc8o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:農曆初三
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200903
+DTEND;VALUE=DATE:20200904
+DTSTAMP:20190328T061054Z
+UID:20200903_60o30dpp64o32c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:軍人節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190903
+DTEND;VALUE=DATE:20190904
+DTSTAMP:20190328T061054Z
+UID:20190903_60o30dpp64o32c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:軍人節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180903
+DTEND;VALUE=DATE:20180904
+DTSTAMP:20190328T061054Z
+UID:20180903_60o30dpp64o32c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:軍人節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+DTSTAMP:20190328T061054Z
+UID:20201225_60o30dppcco30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:行憲紀念日
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+DTSTAMP:20190328T061054Z
+UID:20191225_60o30dppcco30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:行憲紀念日
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+DTSTAMP:20190328T061054Z
+UID:20181225_60o30dppcco30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:行憲紀念日
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200625
+DTEND;VALUE=DATE:20200626
+DTSTAMP:20190328T061054Z
+UID:20200625_60o30dpp60o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:端午節彈性放假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190607
+DTEND;VALUE=DATE:20190608
+DTSTAMP:20190328T061054Z
+UID:20190607_60o30dpp60o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:端午節彈性放假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180618
+DTEND;VALUE=DATE:20180619
+DTSTAMP:20190328T061054Z
+UID:20180618_60o30dpp60o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:端午節彈性放假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200404
+DTEND;VALUE=DATE:20200405
+DTSTAMP:20190328T061054Z
+UID:20200404_60o30dpocko30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:清明節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190405
+DTEND;VALUE=DATE:20190406
+DTSTAMP:20190328T061054Z
+UID:20190405_60o30dpocko30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:清明節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180405
+DTEND;VALUE=DATE:20180406
+DTSTAMP:20190328T061054Z
+UID:20180405_60o30dpocko30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:清明節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200125
+DTEND;VALUE=DATE:20200126
+DTSTAMP:20190328T061054Z
+UID:20200125_60o30dpo74o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:春節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190205
+DTEND;VALUE=DATE:20190206
+DTSTAMP:20190328T061054Z
+UID:20190205_60o30dpo74o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:春節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180216
+DTEND;VALUE=DATE:20180217
+DTSTAMP:20190328T061054Z
+UID:20180216_60o30dpo74o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:春節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200928
+DTEND;VALUE=DATE:20200929
+DTSTAMP:20190328T061054Z
+UID:20200928_60o30dppc4o30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:教師節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190928
+DTEND;VALUE=DATE:20190929
+DTSTAMP:20190328T061054Z
+UID:20190928_60o30dppc4o30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:教師節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180928
+DTEND;VALUE=DATE:20180929
+DTSTAMP:20190328T061054Z
+UID:20180928_60o30dppc4o30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:教師節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200308
+DTEND;VALUE=DATE:20200309
+DTSTAMP:20190328T061054Z
+UID:20200308_60o30dpp6oo30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:婦女節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190308
+DTEND;VALUE=DATE:20190309
+DTSTAMP:20190328T061054Z
+UID:20190308_60o30dpp6oo30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:婦女節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180308
+DTEND;VALUE=DATE:20180309
+DTSTAMP:20190328T061054Z
+UID:20180308_60o30dpp6oo30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:婦女節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20201009
+DTEND;VALUE=DATE:20201010
+DTSTAMP:20190328T061054Z
+UID:20201009_60o30dpp6co30e1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:國慶日/雙十節彈性放假 補假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20201010
+DTEND;VALUE=DATE:20201011
+DTSTAMP:20190328T061054Z
+UID:20201010_60o30dpp6co30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:國慶日/雙十節彈性放假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191010
+DTEND;VALUE=DATE:20191011
+DTSTAMP:20190328T061054Z
+UID:20191010_60o30dpp6co30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:國慶日/雙十節彈性放假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181010
+DTEND;VALUE=DATE:20181011
+DTSTAMP:20190328T061054Z
+UID:20181010_60o30dpp6co30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:國慶日/雙十節彈性放假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20201025
+DTEND;VALUE=DATE:20201026
+DTSTAMP:20190328T061054Z
+UID:20201025_60o30dpp74o30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:台灣光復節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20191025
+DTEND;VALUE=DATE:20191026
+DTSTAMP:20190328T061054Z
+UID:20191025_60o30dpp74o30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:台灣光復節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20181025
+DTEND;VALUE=DATE:20181026
+DTSTAMP:20190328T061054Z
+UID:20181025_60o30dpp74o30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:台灣光復節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+DTSTAMP:20190328T061054Z
+UID:20200501_60o30dpocoo30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:勞動節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+DTSTAMP:20190328T061054Z
+UID:20190501_60o30dpocoo30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:勞動節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+DTSTAMP:20190328T061054Z
+UID:20180501_60o30dpocoo30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:勞動節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200403
+DTEND;VALUE=DATE:20200404
+DTSTAMP:20190328T061054Z
+UID:20200403_60o30dpp70o34e1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:兒童節 補假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200404
+DTEND;VALUE=DATE:20200405
+DTSTAMP:20190328T061054Z
+UID:20200404_60o30dpp70o34c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:兒童節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190404
+DTEND;VALUE=DATE:20190405
+DTSTAMP:20190328T061054Z
+UID:20190404_60o30dpp70o34c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:兒童節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180404
+DTEND;VALUE=DATE:20180405
+DTSTAMP:20190328T061054Z
+UID:20180404_60o30dpp70o34c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:兒童節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200208
+DTEND;VALUE=DATE:20200209
+DTSTAMP:20190328T061054Z
+UID:20200208_60o30dpp6go30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:元宵節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190219
+DTEND;VALUE=DATE:20190220
+DTSTAMP:20190328T061054Z
+UID:20190219_60o30dpp6go30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:元宵節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180302
+DTEND;VALUE=DATE:20180303
+DTSTAMP:20190328T061054Z
+UID:20180302_60o30dpp6go30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:元宵節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+DTSTAMP:20190328T061054Z
+UID:20200101_60o30dpo6oo30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中華民國開國紀念日/元旦
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+DTSTAMP:20190328T061054Z
+UID:20190101_60o30dpo6oo30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中華民國開國紀念日/元旦
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+DTSTAMP:20190328T061054Z
+UID:20180101_60o30dpo6oo30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中華民國開國紀念日/元旦
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20201001
+DTEND;VALUE=DATE:20201002
+DTSTAMP:20190328T061054Z
+UID:20201001_60o30dpp68o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中秋節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190913
+DTEND;VALUE=DATE:20190914
+DTSTAMP:20190328T061054Z
+UID:20190913_60o30dpp68o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中秋節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180924
+DTEND;VALUE=DATE:20180925
+DTSTAMP:20190328T061054Z
+UID:20180924_60o30dpp68o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中秋節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200902
+DTEND;VALUE=DATE:20200903
+DTSTAMP:20190328T061054Z
+UID:20200902_60o30dr160o30c1g60o32chmcg@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中元節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190815
+DTEND;VALUE=DATE:20190816
+DTSTAMP:20190328T061054Z
+UID:20190815_60o30dr160o30c1g60o32chmcc@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中元節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180825
+DTEND;VALUE=DATE:20180826
+DTSTAMP:20190328T061054Z
+UID:20180825_60o30dr160o30c1g60o32chmc8@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:中元節
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200228
+DTEND;VALUE=DATE:20200229
+DTSTAMP:20190328T061054Z
+UID:20200228_60o30dpocgo30c1g60o30dr56g@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:228連假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190228
+DTEND;VALUE=DATE:20190301
+DTSTAMP:20190328T061054Z
+UID:20190228_60o30dpocgo30c1g60o30dr56c@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:228連假
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20180228
+DTEND;VALUE=DATE:20180301
+DTSTAMP:20190328T061054Z
+UID:20180228_60o30dpocgo30c1g60o30dr568@google.com
+CREATED:20190221T131351Z
+STATUS:CONFIRMED
+SUMMARY:228連假
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/ukraine.ics b/app/src/main/assets/ukraine.ics
new file mode 100644
index 000000000..91bb3601f
--- /dev/null
+++ b/app/src/main/assets/ukraine.ics
@@ -0,0 +1,316 @@
+BEGIN:VCALENDAR
+BEGIN:VEVENT
+UID:enrico-ukr-20180101@kayaposoft.com
+DTSTART;VALUE=DATE:20180101
+DTEND;VALUE=DATE:20180102
+SUMMARY:Новий Рік
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180108@kayaposoft.com
+DTSTART;VALUE=DATE:20180108
+DTEND;VALUE=DATE:20180109
+SUMMARY:Різдво
+DESCRIPTION:Holiday in lieu of 7 Jan 2018
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180308@kayaposoft.com
+DTSTART;VALUE=DATE:20180308
+DTEND;VALUE=DATE:20180309
+SUMMARY:Міжнародний жіночий день
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180309@kayaposoft.com
+DTSTART;VALUE=DATE:20180309
+DTEND;VALUE=DATE:20180310
+SUMMARY:Свято
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180408@kayaposoft.com
+DTSTART;VALUE=DATE:20180408
+DTEND;VALUE=DATE:20180409
+SUMMARY:Пасха
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180409@kayaposoft.com
+DTSTART;VALUE=DATE:20180409
+DTEND;VALUE=DATE:20180410
+SUMMARY:Пасха
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180430@kayaposoft.com
+DTSTART;VALUE=DATE:20180430
+DTEND;VALUE=DATE:20180501
+SUMMARY:Свято
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180501@kayaposoft.com
+DTSTART;VALUE=DATE:20180501
+DTEND;VALUE=DATE:20180502
+SUMMARY:День праці (День міжнародної солідарності трудящих)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180509@kayaposoft.com
+DTSTART;VALUE=DATE:20180509
+DTEND;VALUE=DATE:20180510
+SUMMARY:День перемоги над нацизмом у Другій світовій війні
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180527@kayaposoft.com
+DTSTART;VALUE=DATE:20180527
+DTEND;VALUE=DATE:20180528
+SUMMARY:Трійця
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180528@kayaposoft.com
+DTSTART;VALUE=DATE:20180528
+DTEND;VALUE=DATE:20180529
+SUMMARY:Трійця
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180628@kayaposoft.com
+DTSTART;VALUE=DATE:20180628
+DTEND;VALUE=DATE:20180629
+SUMMARY:День Конституції
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180629@kayaposoft.com
+DTSTART;VALUE=DATE:20180629
+DTEND;VALUE=DATE:20180630
+SUMMARY:Свято
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20180824@kayaposoft.com
+DTSTART;VALUE=DATE:20180824
+DTEND;VALUE=DATE:20180825
+SUMMARY:День Незалежності
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20181015@kayaposoft.com
+DTSTART;VALUE=DATE:20181015
+DTEND;VALUE=DATE:20181016
+SUMMARY:День захисника України
+DESCRIPTION:Holiday in lieu of 14 Oct 2018
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20181224@kayaposoft.com
+DTSTART;VALUE=DATE:20181224
+DTEND;VALUE=DATE:20181225
+SUMMARY:Свято
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20181225@kayaposoft.com
+DTSTART;VALUE=DATE:20181225
+DTEND;VALUE=DATE:20181226
+SUMMARY:Різдво
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20181231@kayaposoft.com
+DTSTART;VALUE=DATE:20181231
+DTEND;VALUE=DATE:20190101
+SUMMARY:Свято
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190101@kayaposoft.com
+DTSTART;VALUE=DATE:20190101
+DTEND;VALUE=DATE:20190102
+SUMMARY:Новий Рік
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190107@kayaposoft.com
+DTSTART;VALUE=DATE:20190107
+DTEND;VALUE=DATE:20190108
+SUMMARY:Різдво
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190308@kayaposoft.com
+DTSTART;VALUE=DATE:20190308
+DTEND;VALUE=DATE:20190309
+SUMMARY:Міжнародний жіночий день
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190428@kayaposoft.com
+DTSTART;VALUE=DATE:20190428
+DTEND;VALUE=DATE:20190429
+SUMMARY:Пасха
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190429@kayaposoft.com
+DTSTART;VALUE=DATE:20190429
+DTEND;VALUE=DATE:20190430
+SUMMARY:Пасха
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190501@kayaposoft.com
+DTSTART;VALUE=DATE:20190501
+DTEND;VALUE=DATE:20190502
+SUMMARY:День праці (День міжнародної солідарності трудящих)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190509@kayaposoft.com
+DTSTART;VALUE=DATE:20190509
+DTEND;VALUE=DATE:20190510
+SUMMARY:День перемоги над нацизмом у Другій світовій війні
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190616@kayaposoft.com
+DTSTART;VALUE=DATE:20190616
+DTEND;VALUE=DATE:20190617
+SUMMARY:Трійця
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190617@kayaposoft.com
+DTSTART;VALUE=DATE:20190617
+DTEND;VALUE=DATE:20190618
+SUMMARY:Трійця
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190628@kayaposoft.com
+DTSTART;VALUE=DATE:20190628
+DTEND;VALUE=DATE:20190629
+SUMMARY:День Конституції
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20190826@kayaposoft.com
+DTSTART;VALUE=DATE:20190826
+DTEND;VALUE=DATE:20190827
+SUMMARY:День Незалежності
+DESCRIPTION:Holiday in lieu of 24 Aug 2019
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20191014@kayaposoft.com
+DTSTART;VALUE=DATE:20191014
+DTEND;VALUE=DATE:20191015
+SUMMARY:День захисника України
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20191225@kayaposoft.com
+DTSTART;VALUE=DATE:20191225
+DTEND;VALUE=DATE:20191226
+SUMMARY:Різдво
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200101@kayaposoft.com
+DTSTART;VALUE=DATE:20200101
+DTEND;VALUE=DATE:20200102
+SUMMARY:Новий Рік
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200107@kayaposoft.com
+DTSTART;VALUE=DATE:20200107
+DTEND;VALUE=DATE:20200108
+SUMMARY:Різдво
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200309@kayaposoft.com
+DTSTART;VALUE=DATE:20200309
+DTEND;VALUE=DATE:20200310
+SUMMARY:Міжнародний жіночий день
+DESCRIPTION:Holiday in lieu of 8 Mar 2020
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200419@kayaposoft.com
+DTSTART;VALUE=DATE:20200419
+DTEND;VALUE=DATE:20200420
+SUMMARY:Пасха
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200420@kayaposoft.com
+DTSTART;VALUE=DATE:20200420
+DTEND;VALUE=DATE:20200421
+SUMMARY:Пасха
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200501@kayaposoft.com
+DTSTART;VALUE=DATE:20200501
+DTEND;VALUE=DATE:20200502
+SUMMARY:День праці (День міжнародної солідарності трудящих)
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200511@kayaposoft.com
+DTSTART;VALUE=DATE:20200511
+DTEND;VALUE=DATE:20200512
+SUMMARY:День перемоги над нацизмом у Другій світовій війні
+DESCRIPTION:Holiday in lieu of 9 May 2020
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200607@kayaposoft.com
+DTSTART;VALUE=DATE:20200607
+DTEND;VALUE=DATE:20200608
+SUMMARY:Трійця
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200608@kayaposoft.com
+DTSTART;VALUE=DATE:20200608
+DTEND;VALUE=DATE:20200609
+SUMMARY:Трійця
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200629@kayaposoft.com
+DTSTART;VALUE=DATE:20200629
+DTEND;VALUE=DATE:20200630
+SUMMARY:День Конституції
+DESCRIPTION:Holiday in lieu of 28 Jun 2020
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20200824@kayaposoft.com
+DTSTART;VALUE=DATE:20200824
+DTEND;VALUE=DATE:20200825
+SUMMARY:День Незалежності
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20201014@kayaposoft.com
+DTSTART;VALUE=DATE:20201014
+DTEND;VALUE=DATE:20201015
+SUMMARY:День захисника України
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+UID:enrico-ukr-20201225@kayaposoft.com
+DTSTART;VALUE=DATE:20201225
+DTEND;VALUE=DATE:20201226
+SUMMARY:Різдво
+STATUS:CONFIRMED
+END:VEVENT
+END:VCALENDAR
diff --git a/app/src/main/assets/unitedkingdom.ics b/app/src/main/assets/unitedkingdom.ics
index c66c6a5e2..e0cda6eab 100755
--- a/app/src/main/assets/unitedkingdom.ics
+++ b/app/src/main/assets/unitedkingdom.ics
@@ -2,58 +2,33 @@ BEGIN:VCALENDAR
BEGIN:VEVENT
SUMMARY:New Year's Day
UID:4694f46a-aef0-49a0-a8eb-44ab6531e0d9
-DTSTART;VALUE=DATE:20170102
-DTEND;VALUE=DATE:20170103
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:St. David's Day
-UID:a08053d8-530b-4025-8edf-81c88d05fe84
-DTSTART;VALUE=DATE:20100301
-DTEND;VALUE=DATE:20100302
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:St. George's Day
-UID:b58227e2-7f9d-4571-afa5-0ae93192d10e
-DTSTART;VALUE=DATE:20100423
-DTEND;VALUE=DATE:20100424
+DTSTART;VALUE=DATE:20170101
+DTEND;VALUE=DATE:20170102
STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
SUMMARY:Early May Bank Holiday
UID:21626542-636f-43d7-8fa9-bad05bb82dca
-DTSTART;VALUE=DATE:20100503
-DTEND;VALUE=DATE:20100504
+DTSTART;VALUE=DATE:20210503
+DTEND;VALUE=DATE:20210504
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=1MO
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Early May Bank Holiday
+UID:21626542-636f-43d7-8fa9-bad05bbsds
+DTSTART;VALUE=DATE:20200508
+DTEND;VALUE=DATE:20200509
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
SUMMARY:Summer Bank Holiday
UID:5dac6a63-e519-4ad1-a687-2fd5fccb4656
-DTSTART;VALUE=DATE:20100802
-DTEND;VALUE=DATE:20100803
+DTSTART;VALUE=DATE:20130826
+DTEND;VALUE=DATE:20130827
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYDAY=-1MO
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Summer Bank Holiday
-UID:3d37b115-f0fa-4456-98c9-3b18cfffb47d
-DTSTART;VALUE=DATE:20170828
-DTEND;VALUE=DATE:20170829
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:BST ends
-UID:8eb163f3-6d42-494e-9e7c-747619cf965f
-DTSTART:20101031T000000Z
-DTEND:20101031T020000Z
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
SUMMARY:Christmas Day
@@ -71,4 +46,53 @@ DTEND;VALUE=DATE:20101227
STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
+BEGIN:VEVENT
+DTEND;VALUE=DATE:20190423
+DTSTART;VALUE=DATE:20190422
+SUMMARY:Easter Monday
+UID:ca6af7456b0088abad9a69f9f620f5ac-59@gov.uk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20190419
+DTEND;VALUE=DATE:20190420
+SUMMARY:Good Friday
+UID:ca6af7456b0088abad9a69f9f620f5ac-58@gov.uk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200410
+DTEND;VALUE=DATE:20200411
+SUMMARY:Good Friday
+UID:ca6af7456b0088abad9a69f9f620f5ac-2020-04-10-GoodFriday@gov.uk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20200413
+DTEND;VALUE=DATE:20200414
+SUMMARY:Easter Monday
+UID:ca6af7456b0088abad9a69f9f620f5ac-2020-04-13-EasterMonday@gov.uk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210402
+DTEND;VALUE=DATE:20210403
+SUMMARY:Good Friday
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20210405
+DTEND;VALUE=DATE:20210406
+SUMMARY:Easter Monday
+UID:ca6af7456b0088abad9a69f9f620f5ac-2021-04-05-EasterMonday@gov.uk
+STATUS:CONFIRMED
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Spring Bank Holiday
+UID:5dac6a63-e519-4ad1-a687-2fd5fccb4
+DTSTART;VALUE=DATE:20130527
+DTEND;VALUE=DATE:20130528
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=-1MO
+STATUS:CONFIRMED
+END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/assets/unitedstates.ics b/app/src/main/assets/unitedstates.ics
index 00aaec1a3..2cfb4f060 100755
--- a/app/src/main/assets/unitedstates.ics
+++ b/app/src/main/assets/unitedstates.ics
@@ -1,403 +1,83 @@
BEGIN:VCALENDAR
+VERSION:2.0
BEGIN:VEVENT
-SUMMARY:Ascension Day
-UID:7c8351e3-e3fd-4c0b-a60c-f558aa31df55
-DTSTART;VALUE=DATE:20170525
-DTEND;VALUE=DATE:20170526
+DTSTART;VALUE=DATE:20170101
+DTEND;VALUE=DATE:20170102
+UID:5a4b1ab2e4a3b@calendarlabs.com
STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pentecost
-UID:452c678d-0b42-4a89-a347-a0b8e12c4fd6
-DTSTART;VALUE=DATE:20170604
-DTEND;VALUE=DATE:20170605
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trinity Sunday
-UID:83608056-f721-4564-88ed-3cc5d12b01fb
-DTSTART;VALUE=DATE:20170611
-DTEND;VALUE=DATE:20170612
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ash Wednesday
-UID:6eff84ea-6763-46d3-9c64-b847376f3e34
-DTSTART;VALUE=DATE:20180214
-DTEND;VALUE=DATE:20180215
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Good Friday
-UID:19fb2499-97ae-457f-8d41-6ec76292c77f
-DTSTART;VALUE=DATE:20180330
-DTEND;VALUE=DATE:20180331
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Easter Sunday
-UID:d869e442-0b4d-40c4-b34c-6780afce8c46
-DTSTART;VALUE=DATE:20180401
-DTEND;VALUE=DATE:20180402
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ascension Day
-UID:db037aff-17bf-430b-b094-52e99879ba4e
-DTSTART;VALUE=DATE:20180510
-DTEND;VALUE=DATE:20180511
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pentecost
-UID:01fec43b-b371-4f56-97cb-ef454132a74a
-DTSTART;VALUE=DATE:20180520
-DTEND;VALUE=DATE:20180521
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trinity Sunday
-UID:cc92b242-3a24-471c-b1be-b8918fb8b6de
-DTSTART;VALUE=DATE:20180527
-DTEND;VALUE=DATE:20180528
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ash Wednesday
-UID:e5d798cf-474f-42c3-83b8-0d1186c2c3ad
-DTSTART;VALUE=DATE:20190306
-DTEND;VALUE=DATE:20190307
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palm Sunday
-UID:334646f6-5fef-43a9-83bc-d2dd88c9ebe6
-DTSTART;VALUE=DATE:20190414
-DTEND;VALUE=DATE:20190415
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Good Friday
-UID:0a125549-300d-4bd9-af20-3fc28935946e
-DTSTART;VALUE=DATE:20190419
-DTEND;VALUE=DATE:20190420
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Easter Sunday
-UID:135738b2-b8d5-4e65-bf7c-736e22c6b8b1
-DTSTART;VALUE=DATE:20190421
-DTEND;VALUE=DATE:20190422
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ascension Day
-UID:dddeea06-c165-474c-96aa-6c1b705c6b0a
-DTSTART;VALUE=DATE:20190530
-DTEND;VALUE=DATE:20190531
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pentecost
-UID:697b8004-261c-4c0b-a15e-06c1af95ab9b
-DTSTART;VALUE=DATE:20190609
-DTEND;VALUE=DATE:20190610
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trinity Sunday
-UID:006195b6-8021-4814-9635-f9f5bffa7a36
-DTSTART;VALUE=DATE:20190616
-DTEND;VALUE=DATE:20190617
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ash Wednesday
-UID:9d17488f-12b8-474b-8790-d22e6786735e
-DTSTART;VALUE=DATE:20200226
-DTEND;VALUE=DATE:20200227
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Palm Sunday
-UID:7f9c6e82-610a-4485-b5fd-dde7f0dba4b8
-DTSTART;VALUE=DATE:20200405
-DTEND;VALUE=DATE:20200406
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Good Friday
-UID:50de218e-013d-4739-b8d4-f30f74a57168
-DTSTART;VALUE=DATE:20200410
-DTEND;VALUE=DATE:20200411
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Easter Sunday
-UID:7606494f-0019-46e6-9d47-ab9498569e5a
-DTSTART;VALUE=DATE:20200412
-DTEND;VALUE=DATE:20200413
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Ascension Day
-UID:09a3f71c-83df-4649-b7d1-4792ae2fb523
-DTSTART;VALUE=DATE:20200521
-DTEND;VALUE=DATE:20200522
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Pentecost
-UID:ac723797-16b2-4fd9-9d5e-2f55cc90dc22
-DTSTART;VALUE=DATE:20200531
-DTEND;VALUE=DATE:20200601
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Trinity Sunday
-UID:b60896ca-b1eb-4ec1-a9cd-67c7403c6335
-DTSTART;VALUE=DATE:20200607
-DTEND;VALUE=DATE:20200608
-STATUS:CONFIRMED
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:New Year's Day
-UID:b1f194fc-1dd1-11b2-a973-d219c68b95c4
-DTSTART;VALUE=DATE:20000101
-DTEND;VALUE=DATE:20000102
-STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Groundhog's Day
-UID:0c771532-1dd2-11b2-8dd9-8638db3aef63
-DTSTART;VALUE=DATE:20000202
-DTEND;VALUE=DATE:20000203
+DTSTART;VALUE=DATE:20170116
+DTEND;VALUE=DATE:20170117
+UID:5a4b1ab2e4b97@calendarlabs.com
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:M L King Day
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYDAY=3MO
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Abraham Lincoln's Birthday
-UID:4d5b843e-1dd2-11b2-a6c9-8ea942124d1b
-DTSTART;VALUE=DATE:20000212
-DTEND;VALUE=DATE:20000213
+DTSTART;VALUE=DATE:20170220
+DTEND;VALUE=DATE:20170221
+UID:5a4b1ab2e4c77@calendarlabs.com
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
+SUMMARY:Presidents' Day
+RRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=3MO
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Valentine's Day
-UID:790b86e2-1dd2-11b2-9cb9-c905fa35c6f9
-DTSTART;VALUE=DATE:20000214
-DTEND;VALUE=DATE:20000215
+DTSTART;VALUE=DATE:20170529
+DTEND;VALUE=DATE:20170530
+UID:5a4b1ab2e4e43@calendarlabs.com
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:President's Day
-UID:9f3df592-1dd1-11b2-8b51-a8c49a575f97
-DTSTART;VALUE=DATE:20000221
-DTEND;VALUE=DATE:20000222
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:George Washington's Birthday (actual)
-UID:aff9f3c8-1dd1-11b2-8593-c63469762eb1
-DTSTART;VALUE=DATE:20000222
-DTEND;VALUE=DATE:20000223
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:St. Patrick's Day
-UID:bd6cfe4c-1dd1-11b2-b3dc-ebaab9302c26
-DTSTART;VALUE=DATE:20000317
-DTEND;VALUE=DATE:20000318
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:April Fool's Day
-UID:c981b858-1dd1-11b2-b69b-94d05f8bda66
-DTSTART;VALUE=DATE:20000401
-DTEND;VALUE=DATE:20000402
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Tax Day
-UID:e46086c2-1dd1-11b2-8a16-a2c99f1c525a
-DTSTART;VALUE=DATE:20000415
-DTEND;VALUE=DATE:20000416
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Earth Day
-UID:f1561106-1dd1-11b2-955f-ba14e5210f45
-DTSTART;VALUE=DATE:20000422
-DTEND;VALUE=DATE:20000423
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Arbor Day
-UID:e20f1b5c-8ac4-4bea-acf4-b4f2bfe95329
-DTSTART;VALUE=DATE:20000428
-DTEND;VALUE=DATE:20000429
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:National Day of Prayer
-UID:a44b8b6e-7ae4-4120-b7ad-14e91691be2a
-DTSTART;VALUE=DATE:20000504
-DTEND;VALUE=DATE:20000505
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Mother's Day
-UID:e0d4c156-1dd1-11b2-b6a4-dac750e0163e
-DTSTART;VALUE=DATE:20000514
-DTEND;VALUE=DATE:20000515
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Armed Forces Day
-UID:c2783248-1dd1-11b2-affa-ee50c39d8083
-DTSTART;VALUE=DATE:20000520
-DTEND;VALUE=DATE:20000521
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Memorial Day
-UID:d9706fa6-1dd1-11b2-a349-e97241bd4740
-DTSTART;VALUE=DATE:20000529
-DTEND;VALUE=DATE:20000530
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
+RRULE:FREQ=YEARLY;BYMONTH=5;BYDAY=-1MO
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Flag Day
-UID:e8022726-1dd1-11b2-aae6-9f73f16c0f01
-DTSTART;VALUE=DATE:20000614
-DTEND;VALUE=DATE:20000615
+DTSTART;VALUE=DATE:20170704
+DTEND;VALUE=DATE:20170705
+UID:5a4b1ab2e4f3d@calendarlabs.com
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Father's Day
-UID:cb90487e-1dd1-11b2-9e06-d1a12cc963dc
-DTSTART;VALUE=DATE:20000618
-DTEND;VALUE=DATE:20000619
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Independence Day
-UID:f669a974-1dd1-11b2-9fac-f44d91c657af
-DTSTART;VALUE=DATE:20000704
-DTEND;VALUE=DATE:20000705
-STATUS:CONFIRMED
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Parents' Day
-UID:46e6845c-1dd2-11b2-bd3d-d8755a1a171f
-DTSTART;VALUE=DATE:20000723
-DTEND;VALUE=DATE:20000724
+DTSTART;VALUE=DATE:20170904
+DTEND;VALUE=DATE:20170905
+UID:5a4b1ab2e4fd4@calendarlabs.com
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Labor Day
-UID:0726ea42-1dd2-11b2-9fe6-dda3d063fb50
-DTSTART;VALUE=DATE:20000904
-DTEND;VALUE=DATE:20000905
+RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=1MO
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20171009
+DTEND;VALUE=DATE:20171010
+UID:5a4b1ab2e502a@calendarlabs.com
STATUS:CONFIRMED
+SUMMARY:Columbus Day
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=2MO
+END:VEVENT
+BEGIN:VEVENT
+DTSTART;VALUE=DATE:20171111
+DTEND;VALUE=DATE:20171112
+UID:5a4b1ab2e515a@calendarlabs.com
+STATUS:CONFIRMED
+SUMMARY:Veterans Day
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Constitution Day\, Citizenship Day
-UID:d3c521e1-ffab-414c-8cfa-99e76758d8c8
-DTSTART;VALUE=DATE:20000917
-DTEND;VALUE=DATE:20000918
+DTSTART;VALUE=DATE:20171123
+DTEND;VALUE=DATE:20171124
+UID:5a4b1ab2e51ca@calendarlabs.com
STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:United Nations Day
-UID:8a05b5d3-6b97-4fe1-b26e-b258a073c5e0
-DTSTART;VALUE=DATE:20001024
-DTEND;VALUE=DATE:20001025
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Halloween
-UID:7d4402f4-1dd2-11b2-9790-b6193cfa4349
-DTSTART;VALUE=DATE:20001031
-DTEND;VALUE=DATE:20001101
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Veteran's Day
-UID:2c7ba10c-1dd2-11b2-b0b7-96f89f288199
-DTSTART;VALUE=DATE:20001111
-DTEND;VALUE=DATE:20001112
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
SUMMARY:Thanksgiving Day
-UID:4299a358-1dd2-11b2-a228-d062222b6f88
-DTSTART;VALUE=DATE:20001123
-DTEND;VALUE=DATE:20001124
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=4TH
END:VEVENT
BEGIN:VEVENT
-SUMMARY:Christmas Day
-UID:54f392fa-1dd2-11b2-8ecb-9ae0a7fcebd1
-DTSTART;VALUE=DATE:20001225
-DTEND;VALUE=DATE:20001226
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:New Year's Eve
-UID:a2cbca9c-1dd1-11b2-83e2-abab36f0506d
-DTSTART;VALUE=DATE:20001231
-DTEND;VALUE=DATE:20010101
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Inauguration Day
-UID:088031d1-abea-47ed-978d-39c6319e8bf2
-DTSTART;VALUE=DATE:20010120
-DTEND;VALUE=DATE:20010121
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=4
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Daylight Saving Time begins
-UID:e462881a-20b8-4b70-ab3c-cf38016d3398
-DTSTART;VALUE=DATE:20070311
-DTEND;VALUE=DATE:20070312
-STATUS:CONFIRMED
-RRULE:FREQ=YEARLY;INTERVAL=1
-END:VEVENT
-BEGIN:VEVENT
-SUMMARY:Daylight Saving Time ends
-UID:13f83ed2-1dd2-11b2-a0de-ee8645423959
-DTSTART;VALUE=DATE:20071104
-DTEND;VALUE=DATE:20071105
+DTSTART;VALUE=DATE:20171225
+DTEND;VALUE=DATE:20171226
+UID:5a4b1ab2e5265@calendarlabs.com
STATUS:CONFIRMED
+SUMMARY:Christmas
RRULE:FREQ=YEARLY;INTERVAL=1
END:VEVENT
END:VCALENDAR
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/App.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/App.kt
deleted file mode 100644
index 382012eaa..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/App.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.simplemobiletools.calendar
-
-import android.support.multidex.MultiDexApplication
-import com.facebook.stetho.Stetho
-import com.simplemobiletools.commons.extensions.checkUseEnglish
-import com.squareup.leakcanary.LeakCanary
-
-class App : MultiDexApplication() {
- override fun onCreate() {
- super.onCreate()
- if (BuildConfig.DEBUG) {
- if (LeakCanary.isInAnalyzerProcess(this)) {
- return
- }
- LeakCanary.install(this)
- Stetho.initializeWithDefaults(this)
- }
-
- checkUseEnglish()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/DayActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/activities/DayActivity.kt
deleted file mode 100644
index 420069f61..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/DayActivity.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-package com.simplemobiletools.calendar.activities
-
-import android.content.Intent
-import android.os.Bundle
-import android.support.v4.view.ViewPager
-import android.util.SparseIntArray
-import android.view.Menu
-import android.view.MenuItem
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.adapters.MyDayPagerAdapter
-import com.simplemobiletools.calendar.dialogs.FilterEventTypesDialog
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.extensions.getNewEventTimestampFromCode
-import com.simplemobiletools.calendar.helpers.DAY_CODE
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.helpers.NEW_EVENT_START_TS
-import com.simplemobiletools.calendar.interfaces.NavigationListener
-import com.simplemobiletools.commons.extensions.isActivityDestroyed
-import com.simplemobiletools.commons.extensions.updateTextColors
-import kotlinx.android.synthetic.main.activity_day.*
-import org.joda.time.DateTime
-import java.util.*
-
-class DayActivity : SimpleActivity(), NavigationListener, ViewPager.OnPageChangeListener {
- private val PREFILLED_DAYS = 121
- private var mDayCode = ""
- private var mPagerDays: MutableList? = null
- private var mPagerPos = 0
- private var eventTypeColors = SparseIntArray()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_day)
-
- val intent = intent ?: return
- mDayCode = intent.getStringExtra(DAY_CODE)
- if (mDayCode.isEmpty())
- return
-
- fillViewPager(mDayCode)
-
- day_fab.setOnClickListener { addNewEvent() }
- updateTextColors(day_coordinator)
-
- dbHelper.getEventTypes {
- if (!isActivityDestroyed()) {
- eventTypeColors.clear()
- it.map { eventTypeColors.put(it.id, it.color) }
- invalidateOptionsMenu()
- }
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.menu_day, menu)
- menu.findItem(R.id.filter).isVisible = eventTypeColors.size() > 1 || config.displayEventTypes.isEmpty()
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.filter -> showFilterDialog()
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- private fun fillViewPager(targetDay: String) {
- getDays(targetDay)
- val daysAdapter = MyDayPagerAdapter(supportFragmentManager, mPagerDays!!, this)
- mPagerPos = mPagerDays!!.size / 2
- view_pager.apply {
- adapter = daysAdapter
- currentItem = mPagerPos
- addOnPageChangeListener(this@DayActivity)
- }
- }
-
- private fun showFilterDialog() {
- FilterEventTypesDialog(this) {
- recheckEvents()
- }
- }
-
- private fun addNewEvent() {
- Intent(applicationContext, EventActivity::class.java).apply {
- putExtra(NEW_EVENT_START_TS, getNewEventTimestampFromCode(mPagerDays?.get(view_pager.currentItem).toString()))
- startActivity(this)
- }
- }
-
- private fun getDays(code: String) {
- mPagerDays = ArrayList(PREFILLED_DAYS)
- val today = Formatter.getDateTimeFromCode(code)
- for (i in -PREFILLED_DAYS / 2..PREFILLED_DAYS / 2) {
- mPagerDays!!.add(Formatter.getDayCodeFromDateTime(today.plusDays(i)))
- }
- }
-
- fun recheckEvents() {
- (view_pager.adapter as MyDayPagerAdapter).checkDayEvents(mPagerPos)
- }
-
- override fun onPageScrollStateChanged(state: Int) {
-
- }
-
- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
-
- }
-
- override fun onPageSelected(position: Int) {
- mPagerPos = position
- (view_pager.adapter as MyDayPagerAdapter).destroyMultiselector(position)
- }
-
- override fun goLeft() {
- view_pager.currentItem = view_pager.currentItem - 1
- }
-
- override fun goRight() {
- view_pager.currentItem = view_pager.currentItem + 1
- }
-
- override fun goToDateTime(dateTime: DateTime) {
- fillViewPager(Formatter.getDayCodeFromDateTime(dateTime))
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/EventActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/activities/EventActivity.kt
deleted file mode 100644
index edefe1e9b..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/EventActivity.kt
+++ /dev/null
@@ -1,754 +0,0 @@
-package com.simplemobiletools.calendar.activities
-
-import android.annotation.SuppressLint
-import android.app.DatePickerDialog
-import android.app.TimePickerDialog
-import android.os.Bundle
-import android.text.method.LinkMovementMethod
-import android.view.Menu
-import android.view.MenuItem
-import android.view.WindowManager
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.dialogs.*
-import com.simplemobiletools.calendar.extensions.*
-import com.simplemobiletools.calendar.helpers.*
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.models.CalDAVCalendar
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.commons.dialogs.RadioGroupDialog
-import com.simplemobiletools.commons.extensions.*
-import com.simplemobiletools.commons.models.RadioItem
-import kotlinx.android.synthetic.main.activity_event.*
-import org.joda.time.DateTime
-import java.util.*
-
-class EventActivity : SimpleActivity() {
- private var mReminder1Minutes = 0
- private var mReminder2Minutes = 0
- private var mReminder3Minutes = 0
- private var mRepeatInterval = 0
- private var mRepeatLimit = 0
- private var mRepeatRule = 0
- private var mEventTypeId = DBHelper.REGULAR_EVENT_TYPE_ID
- private var mDialogTheme = 0
- private var mEventOccurrenceTS = 0
- private var mEventCalendarId = STORED_LOCALLY_ONLY
- private var wasActivityInitialized = false
-
- lateinit var mEventStartDateTime: DateTime
- lateinit var mEventEndDateTime: DateTime
- lateinit var mEvent: Event
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_event)
-
- supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_cross)
- val intent = intent ?: return
- mDialogTheme = getDialogTheme()
-
- val eventId = intent.getIntExtra(EVENT_ID, 0)
- val event = dbHelper.getEventWithId(eventId)
-
- if (eventId != 0 && event == null) {
- finish()
- return
- }
-
- if (event != null) {
- mEvent = event
- mEventOccurrenceTS = intent.getIntExtra(EVENT_OCCURRENCE_TS, 0)
- setupEditEvent()
- } else {
- mEvent = Event()
- mReminder1Minutes = config.defaultReminderMinutes
- mReminder2Minutes = -1
- mReminder3Minutes = -1
- val startTS = intent.getIntExtra(NEW_EVENT_START_TS, 0)
- if (startTS == 0) {
- return
- }
-
- setupNewEvent(Formatter.getDateTimeFromTS(startTS))
- }
-
- checkReminderTexts()
- updateRepetitionText()
- updateStartTexts()
- updateEndTexts()
- updateEventType()
- updateCalDAVCalendar()
- updateLocation()
-
- event_start_date.setOnClickListener { setupStartDate() }
- event_start_time.setOnClickListener { setupStartTime() }
- event_end_date.setOnClickListener { setupEndDate() }
- event_end_time.setOnClickListener { setupEndTime() }
-
- event_all_day.setOnCheckedChangeListener { compoundButton, isChecked -> toggleAllDay(isChecked) }
- event_repetition.setOnClickListener { showRepeatIntervalDialog() }
- event_repetition_rule_holder.setOnClickListener { showRepetitionRuleDialog() }
- event_repetition_limit_holder.setOnClickListener { showRepetitionTypePicker() }
-
- event_reminder_1.setOnClickListener { showReminder1Dialog() }
- event_reminder_2.setOnClickListener { showReminder2Dialog() }
- event_reminder_3.setOnClickListener { showReminder3Dialog() }
-
- event_type_holder.setOnClickListener { showEventTypeDialog() }
-
- if (mEvent.flags and FLAG_ALL_DAY != 0)
- event_all_day.toggle()
-
- updateTextColors(event_scrollview)
- updateIconColors()
- wasActivityInitialized = true
- }
-
- private fun setupEditEvent() {
- val realStart = if (mEventOccurrenceTS == 0) mEvent.startTS else mEventOccurrenceTS
- val duration = mEvent.endTS - mEvent.startTS
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
- supportActionBar?.title = resources.getString(R.string.edit_event)
- mEventStartDateTime = Formatter.getDateTimeFromTS(realStart)
- mEventEndDateTime = Formatter.getDateTimeFromTS(realStart + duration)
- event_title.setText(mEvent.title)
- event_location.setText(mEvent.location)
- event_description.setText(mEvent.description)
- event_description.movementMethod = LinkMovementMethod.getInstance()
-
- mReminder1Minutes = mEvent.reminder1Minutes
- mReminder2Minutes = mEvent.reminder2Minutes
- mReminder3Minutes = mEvent.reminder3Minutes
- mRepeatInterval = mEvent.repeatInterval
- mRepeatLimit = mEvent.repeatLimit
- mRepeatRule = mEvent.repeatRule
- mEventTypeId = mEvent.eventType
- mEventCalendarId = mEvent.getCalDAVCalendarId()
- checkRepeatTexts(mRepeatInterval)
- }
-
- private fun setupNewEvent(dateTime: DateTime) {
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
- supportActionBar?.title = resources.getString(R.string.new_event)
- mEventStartDateTime = dateTime
-
- val addHours = if (intent.getBooleanExtra(NEW_EVENT_SET_HOUR_DURATION, false)) 1 else 0
- mEventEndDateTime = mEventStartDateTime.plusHours(addHours)
-
- val isLastCaldavCalendarOK = config.caldavSync && config.getSyncedCalendarIdsAsList().contains(config.lastUsedCaldavCalendar.toString())
- mEventCalendarId = if (isLastCaldavCalendarOK) config.lastUsedCaldavCalendar else STORED_LOCALLY_ONLY
- }
-
- private fun showReminder1Dialog() {
- showEventReminderDialog(mReminder1Minutes) {
- mReminder1Minutes = it
- checkReminderTexts()
- }
- }
-
- private fun showReminder2Dialog() {
- showEventReminderDialog(mReminder2Minutes) {
- mReminder2Minutes = it
- checkReminderTexts()
- }
- }
-
- private fun showReminder3Dialog() {
- showEventReminderDialog(mReminder3Minutes) {
- mReminder3Minutes = it
- checkReminderTexts()
- }
- }
-
- private fun showRepeatIntervalDialog() {
- showEventRepeatIntervalDialog(mRepeatInterval) {
- setRepeatInterval(it)
- }
- }
-
- private fun setRepeatInterval(interval: Int) {
- mRepeatInterval = interval
- updateRepetitionText()
- checkRepeatTexts(interval)
-
- if (mRepeatInterval.isXWeeklyRepetition()) {
- setRepeatRule(Math.pow(2.0, (mEventStartDateTime.dayOfWeek - 1).toDouble()).toInt())
- } else if (mRepeatInterval.isXMonthlyRepetition()) {
- setRepeatRule(REPEAT_MONTH_SAME_DAY)
- }
- }
-
- private fun checkRepeatTexts(limit: Int) {
- event_repetition_limit_holder.beGoneIf(limit == 0)
- checkRepetitionLimitText()
-
- event_repetition_rule_holder.beVisibleIf(mRepeatInterval.isXWeeklyRepetition() || mRepeatInterval.isXMonthlyRepetition())
- checkRepetitionRuleText()
- }
-
- private fun showRepetitionTypePicker() {
- hideKeyboard()
- RepeatLimitTypePickerDialog(this, mRepeatLimit, mEventStartDateTime.seconds()) {
- setRepeatLimit(it)
- }
- }
-
- private fun setRepeatLimit(limit: Int) {
- mRepeatLimit = limit
- checkRepetitionLimitText()
- }
-
- private fun checkRepetitionLimitText() {
- event_repetition_limit.text = when {
- mRepeatLimit == 0 -> {
- event_repetition_limit_label.text = getString(R.string.repeat)
- resources.getString(R.string.forever)
- }
- mRepeatLimit > 0 -> {
- event_repetition_limit_label.text = getString(R.string.repeat_till)
- val repeatLimitDateTime = Formatter.getDateTimeFromTS(mRepeatLimit)
- Formatter.getFullDate(applicationContext, repeatLimitDateTime)
- }
- else -> {
- event_repetition_limit_label.text = getString(R.string.repeat)
- "${-mRepeatLimit} ${getString(R.string.times)}"
- }
- }
- }
-
- private fun showRepetitionRuleDialog() {
- hideKeyboard()
- if (mRepeatInterval.isXWeeklyRepetition()) {
- RepeatRuleWeeklyDialog(this, mRepeatRule) {
- setRepeatRule(it)
- }
- } else if (mRepeatInterval.isXMonthlyRepetition()) {
- val items = arrayListOf(RadioItem(REPEAT_MONTH_SAME_DAY, getString(R.string.repeat_on_the_same_day)))
-
- // split Every Last Sunday and Every Fourth Sunday of the month, if the month has 4 sundays
- if (isLastWeekDayOfMonth()) {
- val order = (mEventStartDateTime.dayOfMonth - 1) / 7 + 1
- if (order == 4) {
- items.add(RadioItem(REPEAT_MONTH_ORDER_WEEKDAY, getRepeatXthDayString(true, REPEAT_MONTH_ORDER_WEEKDAY)))
- items.add(RadioItem(REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayString(true, REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST)))
- } else if (order == 5) {
- items.add(RadioItem(REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayString(true, REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST)))
- }
- } else {
- items.add(RadioItem(REPEAT_MONTH_ORDER_WEEKDAY, getRepeatXthDayString(true, REPEAT_MONTH_ORDER_WEEKDAY)))
- }
-
- if (isLastDayOfTheMonth()) {
- items.add(RadioItem(REPEAT_MONTH_LAST_DAY, getString(R.string.repeat_on_the_last_day)))
- }
-
- RadioGroupDialog(this, items, mRepeatRule) {
- setRepeatRule(it as Int)
- }
- }
- }
-
- private fun isLastDayOfTheMonth() = mEventStartDateTime.dayOfMonth == mEventStartDateTime.dayOfMonth().withMaximumValue().dayOfMonth
-
- private fun isLastWeekDayOfMonth() = mEventStartDateTime.monthOfYear != mEventStartDateTime.plusDays(7).monthOfYear
-
- private fun getRepeatXthDayString(includeBase: Boolean, repeatRule: Int): String {
- val dayOfWeek = mEventStartDateTime.dayOfWeek
- val base = getBaseString(dayOfWeek)
- val order = getOrderString(repeatRule)
- val dayString = getDayString(dayOfWeek)
- return if (includeBase) {
- "$base $order $dayString"
- } else {
- val everyString = getString(if (isMaleGender(mEventStartDateTime.dayOfWeek)) R.string.every_m else R.string.every_f)
- "$everyString $order $dayString"
- }
- }
-
- private fun getBaseString(day: Int): String {
- return getString(if (isMaleGender(day)) {
- R.string.repeat_every_m
- } else {
- R.string.repeat_every_f
- })
- }
-
- private fun isMaleGender(day: Int) = day == 1 || day == 2 || day == 4 || day == 5
-
- private fun getOrderString(repeatRule: Int): String {
- val dayOfMonth = mEventStartDateTime.dayOfMonth
- var order = (dayOfMonth - 1) / 7 + 1
- if (order == 4 && isLastWeekDayOfMonth() && repeatRule == REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST) {
- order = -1
- }
-
- val isMale = isMaleGender(mEventStartDateTime.dayOfWeek)
- return getString(when (order) {
- 1 -> if (isMale) R.string.first_m else R.string.first_f
- 2 -> if (isMale) R.string.second_m else R.string.second_f
- 3 -> if (isMale) R.string.third_m else R.string.third_f
- 4 -> if (isMale) R.string.fourth_m else R.string.fourth_f
- else -> if (isMale) R.string.last_m else R.string.last_f
- })
- }
-
- private fun getDayString(day: Int): String {
- return getString(when (day) {
- 1 -> R.string.monday_alt
- 2 -> R.string.tuesday_alt
- 3 -> R.string.wednesday_alt
- 4 -> R.string.thursday_alt
- 5 -> R.string.friday_alt
- 6 -> R.string.saturday_alt
- else -> R.string.sunday_alt
- })
- }
-
- private fun setRepeatRule(rule: Int) {
- mRepeatRule = rule
- checkRepetitionRuleText()
- if (rule == 0) {
- setRepeatInterval(0)
- }
- }
-
- private fun checkRepetitionRuleText() {
- if (mRepeatInterval.isXWeeklyRepetition()) {
- event_repetition_rule.text = if (mRepeatRule == EVERY_DAY) getString(R.string.every_day) else getSelectedDaysString()
- } else if (mRepeatInterval.isXMonthlyRepetition()) {
- val repeatString = if (mRepeatRule == REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST || mRepeatRule == REPEAT_MONTH_ORDER_WEEKDAY)
- R.string.repeat else R.string.repeat_on
-
- event_repetition_rule_label.text = getString(repeatString)
- event_repetition_rule.text = getMonthlyRepetitionRuleText()
- }
- }
-
- private fun getSelectedDaysString(): String {
- var days = ""
- if (mRepeatRule and MONDAY != 0)
- days += "${getString(R.string.monday).substringTo(3)}, "
- if (mRepeatRule and TUESDAY != 0)
- days += "${getString(R.string.tuesday).substringTo(3)}, "
- if (mRepeatRule and WEDNESDAY != 0)
- days += "${getString(R.string.wednesday).substringTo(3)}, "
- if (mRepeatRule and THURSDAY != 0)
- days += "${getString(R.string.thursday).substringTo(3)}, "
- if (mRepeatRule and FRIDAY != 0)
- days += "${getString(R.string.friday).substringTo(3)}, "
- if (mRepeatRule and SATURDAY != 0)
- days += "${getString(R.string.saturday).substringTo(3)}, "
- if (mRepeatRule and SUNDAY != 0)
- days += "${getString(R.string.sunday).substringTo(3)}, "
-
- return days.trim().trimEnd(',')
- }
-
- private fun getMonthlyRepetitionRuleText() = when (mRepeatRule) {
- REPEAT_MONTH_SAME_DAY -> getString(R.string.the_same_day)
- REPEAT_MONTH_LAST_DAY -> getString(R.string.the_last_day)
- else -> getRepeatXthDayString(false, mRepeatRule)
- }
-
- private fun showEventTypeDialog() {
- hideKeyboard()
- SelectEventTypeDialog(this, mEventTypeId) {
- mEventTypeId = it
- updateEventType()
- }
- }
-
- private fun checkReminderTexts() {
- updateReminder1Text()
- updateReminder2Text()
- updateReminder3Text()
- }
-
- private fun updateReminder1Text() {
- event_reminder_1.text = getFormattedMinutes(mReminder1Minutes)
- if (mReminder1Minutes == REMINDER_OFF) {
- mReminder2Minutes = REMINDER_OFF
- mReminder3Minutes = REMINDER_OFF
- }
- }
-
- private fun updateReminder2Text() {
- event_reminder_2.apply {
- beGoneIf(mReminder1Minutes == REMINDER_OFF)
- if (mReminder2Minutes == REMINDER_OFF) {
- text = resources.getString(R.string.add_another_reminder)
- alpha = 0.4f
- mReminder3Minutes = REMINDER_OFF
- } else {
- text = getFormattedMinutes(mReminder2Minutes)
- alpha = 1f
- }
- }
- }
-
- private fun updateReminder3Text() {
- event_reminder_3.apply {
- beGoneIf(mReminder2Minutes == REMINDER_OFF || mReminder1Minutes == REMINDER_OFF)
- if (mReminder3Minutes == REMINDER_OFF) {
- text = resources.getString(R.string.add_another_reminder)
- alpha = 0.4f
- } else {
- text = getFormattedMinutes(mReminder3Minutes)
- alpha = 1f
- }
- }
- }
-
- private fun updateRepetitionText() {
- event_repetition.text = getRepetitionText(mRepeatInterval)
- }
-
- private fun updateEventType() {
- val eventType = dbHelper.getEventType(mEventTypeId)
- if (eventType != null) {
- event_type.text = eventType.title
- event_type_color.setBackgroundWithStroke(eventType.color, config.backgroundColor)
- }
- }
-
- private fun updateCalDAVCalendar() {
- if (config.caldavSync) {
- event_caldav_calendar_image.beVisible()
- event_caldav_calendar_holder.beVisible()
- event_caldav_calendar_divider.beVisible()
-
- val calendars = CalDAVHandler(applicationContext).getCalDAVCalendars().filter {
- it.canWrite() && config.getSyncedCalendarIdsAsList().contains(it.id.toString())
- }
- updateCurrentCalendarInfo(if (mEventCalendarId == STORED_LOCALLY_ONLY) null else getCalendarWithId(calendars, getCalendarId()))
-
- event_caldav_calendar_holder.setOnClickListener {
- hideKeyboard()
- SelectEventCalendarDialog(this, calendars, mEventCalendarId) {
- if (mEventCalendarId != STORED_LOCALLY_ONLY && it == STORED_LOCALLY_ONLY) {
- mEventTypeId = DBHelper.REGULAR_EVENT_TYPE_ID
- updateEventType()
- }
- mEventCalendarId = it
- config.lastUsedCaldavCalendar = it
- updateCurrentCalendarInfo(getCalendarWithId(calendars, it))
- }
- }
- } else {
- updateCurrentCalendarInfo(null)
- }
- }
-
- private fun getCalendarId() = if (mEvent.source == SOURCE_SIMPLE_CALENDAR) config.lastUsedCaldavCalendar else mEvent.getCalDAVCalendarId()
-
- private fun getCalendarWithId(calendars: List, calendarId: Int): CalDAVCalendar? =
- calendars.firstOrNull { it.id == calendarId }
-
- private fun updateCurrentCalendarInfo(currentCalendar: CalDAVCalendar?) {
- event_type_image.beVisibleIf(currentCalendar == null)
- event_type_holder.beVisibleIf(currentCalendar == null)
- event_caldav_calendar_divider.beVisibleIf(currentCalendar == null)
- event_caldav_calendar_email.beGoneIf(currentCalendar == null)
-
- if (currentCalendar == null) {
- event_caldav_calendar_name.apply {
- text = getString(R.string.store_locally_only)
- setPadding(paddingLeft, paddingTop, paddingRight, resources.getDimension(R.dimen.medium_margin).toInt())
- }
- } else {
- event_caldav_calendar_email.text = currentCalendar.accountName
- event_caldav_calendar_name.apply {
- text = currentCalendar.displayName
- setPadding(paddingLeft, paddingTop, paddingRight, resources.getDimension(R.dimen.tiny_margin).toInt())
- }
- }
- }
-
- private fun updateLocation() {
- event_location.setText(mEvent.location)
- }
-
- private fun toggleAllDay(isChecked: Boolean) {
- hideKeyboard()
- event_start_time.beGoneIf(isChecked)
- event_end_time.beGoneIf(isChecked)
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.menu_event, menu)
- if (wasActivityInitialized) {
- menu.findItem(R.id.delete).isVisible = mEvent.id != 0
- menu.findItem(R.id.share).isVisible = mEvent.id != 0
- }
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.save -> saveEvent()
- R.id.delete -> deleteEvent()
- R.id.share -> shareEvent()
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- private fun shareEvent() {
- shareEvents(arrayListOf(mEvent.id))
- }
-
- private fun deleteEvent() {
- DeleteEventDialog(this, arrayListOf(mEvent.id)) {
- if (it) {
- dbHelper.deleteEvents(arrayOf(mEvent.id.toString()), true)
- } else {
- dbHelper.addEventRepeatException(mEvent.id, mEventOccurrenceTS)
- }
- finish()
- }
- }
-
- private fun saveEvent() {
- val newTitle = event_title.value
- if (newTitle.isEmpty()) {
- toast(R.string.title_empty)
- event_title.requestFocus()
- return
- }
-
- val newStartTS = mEventStartDateTime.withSecondOfMinute(0).withMillisOfSecond(0).seconds()
- val newEndTS = mEventEndDateTime.withSecondOfMinute(0).withMillisOfSecond(0).seconds()
-
- if (newStartTS > newEndTS) {
- toast(R.string.end_before_start)
- return
- }
-
- val wasRepeatable = mEvent.repeatInterval > 0
- val oldSource = mEvent.source
- val newImportId = if (mEvent.id != 0) mEvent.importId else UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis().toString()
-
- val newEventType = if (!config.caldavSync || config.lastUsedCaldavCalendar == 0 || mEventCalendarId == STORED_LOCALLY_ONLY) {
- mEventTypeId
- } else {
- dbHelper.getEventTypeWithCalDAVCalendarId(config.lastUsedCaldavCalendar)?.id ?: DBHelper.REGULAR_EVENT_TYPE_ID
- }
-
- val newSource = if (!config.caldavSync || config.lastUsedCaldavCalendar == 0 || mEventCalendarId == STORED_LOCALLY_ONLY) {
- SOURCE_SIMPLE_CALENDAR
- } else {
- "$CALDAV-${config.lastUsedCaldavCalendar}"
- }
-
- val reminders = sortedSetOf(mReminder1Minutes, mReminder2Minutes, mReminder3Minutes).filter { it != REMINDER_OFF }
- mEvent.apply {
- startTS = newStartTS
- endTS = newEndTS
- title = newTitle
- description = event_description.value
- reminder1Minutes = reminders.elementAtOrElse(0) { REMINDER_OFF }
- reminder2Minutes = reminders.elementAtOrElse(1) { REMINDER_OFF }
- reminder3Minutes = reminders.elementAtOrElse(2) { REMINDER_OFF }
- repeatInterval = mRepeatInterval
- importId = newImportId
- flags = if (event_all_day.isChecked) (mEvent.flags or FLAG_ALL_DAY) else (mEvent.flags.removeFlag(FLAG_ALL_DAY))
- repeatLimit = if (repeatInterval == 0) 0 else mRepeatLimit
- repeatRule = mRepeatRule
- eventType = newEventType
- offset = getCurrentOffset()
- isDstIncluded = TimeZone.getDefault().inDaylightTime(Date())
- lastUpdated = System.currentTimeMillis()
- source = newSource
- location = event_location.value
- }
-
- // recreate the event if it was moved in a different CalDAV calendar
- if (mEvent.id != 0 && oldSource != newSource) {
- dbHelper.deleteEvents(arrayOf(mEvent.id.toString()), true)
- mEvent.id = 0
- }
-
- storeEvent(wasRepeatable)
- }
-
- private fun storeEvent(wasRepeatable: Boolean) {
- if (mEvent.id == 0) {
- dbHelper.insert(mEvent, true) {
- if (DateTime.now().isAfter(mEventStartDateTime.millis)) {
- if (mEvent.getReminders().isNotEmpty()) {
- notifyEvent(mEvent)
- }
- } else {
- toast(R.string.event_added)
- }
-
- finish()
- }
- } else {
- if (mRepeatInterval > 0 && wasRepeatable) {
- EditRepeatingEventDialog(this) {
- if (it) {
- dbHelper.update(mEvent, true) {
- eventUpdated()
- }
- } else {
- dbHelper.addEventRepeatException(mEvent.id, mEventOccurrenceTS)
- mEvent.parentId = mEvent.id
- mEvent.id = 0
- dbHelper.insert(mEvent, true) {
- toast(R.string.event_updated)
- finish()
- }
- }
- }
- } else {
- dbHelper.update(mEvent, true) {
- eventUpdated()
- }
- }
- }
- }
-
- private fun eventUpdated() {
- toast(R.string.event_updated)
- finish()
- }
-
- private fun updateStartTexts() {
- updateStartDateText()
- updateStartTimeText()
- }
-
- private fun updateStartDateText() {
- event_start_date.text = Formatter.getDate(applicationContext, mEventStartDateTime)
- checkStartEndValidity()
- }
-
- private fun updateStartTimeText() {
- event_start_time.text = Formatter.getTime(this, mEventStartDateTime)
- checkStartEndValidity()
- }
-
- private fun updateEndTexts() {
- updateEndDateText()
- updateEndTimeText()
- }
-
- private fun updateEndDateText() {
- event_end_date.text = Formatter.getDate(applicationContext, mEventEndDateTime)
- checkStartEndValidity()
- }
-
- private fun updateEndTimeText() {
- event_end_time.text = Formatter.getTime(this, mEventEndDateTime)
- checkStartEndValidity()
- }
-
- private fun checkStartEndValidity() {
- val textColor = if (mEventStartDateTime.isAfter(mEventEndDateTime)) resources.getColor(R.color.red_text) else config.textColor
- event_end_date.setTextColor(textColor)
- event_end_time.setTextColor(textColor)
- }
-
- @SuppressLint("NewApi")
- private fun setupStartDate() {
- hideKeyboard()
- config.backgroundColor.getContrastColor()
- val datepicker = DatePickerDialog(this, mDialogTheme, startDateSetListener, mEventStartDateTime.year, mEventStartDateTime.monthOfYear - 1,
- mEventStartDateTime.dayOfMonth)
-
- if (isLollipopPlus()) {
- datepicker.datePicker.firstDayOfWeek = if (config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY
- }
-
- datepicker.show()
- }
-
- private fun setupStartTime() {
- hideKeyboard()
- TimePickerDialog(this, mDialogTheme, startTimeSetListener, mEventStartDateTime.hourOfDay, mEventStartDateTime.minuteOfHour, config.use24hourFormat).show()
- }
-
- @SuppressLint("NewApi")
- private fun setupEndDate() {
- hideKeyboard()
- val datepicker = DatePickerDialog(this, mDialogTheme, endDateSetListener, mEventEndDateTime.year, mEventEndDateTime.monthOfYear - 1,
- mEventEndDateTime.dayOfMonth)
-
- if (isLollipopPlus()) {
- datepicker.datePicker.firstDayOfWeek = if (config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY
- }
-
- datepicker.show()
- }
-
- private fun setupEndTime() {
- hideKeyboard()
- TimePickerDialog(this, mDialogTheme, endTimeSetListener, mEventEndDateTime.hourOfDay, mEventEndDateTime.minuteOfHour, config.use24hourFormat).show()
- }
-
- private val startDateSetListener = DatePickerDialog.OnDateSetListener { view, year, monthOfYear, dayOfMonth ->
- dateSet(year, monthOfYear, dayOfMonth, true)
- }
-
- private val startTimeSetListener = TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute ->
- timeSet(hourOfDay, minute, true)
- }
-
- private val endDateSetListener = DatePickerDialog.OnDateSetListener { view, year, monthOfYear, dayOfMonth -> dateSet(year, monthOfYear, dayOfMonth, false) }
-
- private val endTimeSetListener = TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute -> timeSet(hourOfDay, minute, false) }
-
- private fun dateSet(year: Int, month: Int, day: Int, isStart: Boolean) {
- if (isStart) {
- val diff = mEventEndDateTime.seconds() - mEventStartDateTime.seconds()
-
- mEventStartDateTime = mEventStartDateTime.withDate(year, month + 1, day)
- updateStartDateText()
- checkRepeatRule()
-
- mEventEndDateTime = mEventStartDateTime.plusSeconds(diff)
- updateEndTexts()
- } else {
- mEventEndDateTime = mEventEndDateTime.withDate(year, month + 1, day)
- updateEndDateText()
- }
- }
-
- private fun timeSet(hours: Int, minutes: Int, isStart: Boolean) {
- if (isStart) {
- val diff = mEventEndDateTime.seconds() - mEventStartDateTime.seconds()
-
- mEventStartDateTime = mEventStartDateTime.withHourOfDay(hours).withMinuteOfHour(minutes)
- updateStartTimeText()
-
- mEventEndDateTime = mEventStartDateTime.plusSeconds(diff)
- updateEndTexts()
- } else {
- mEventEndDateTime = mEventEndDateTime.withHourOfDay(hours).withMinuteOfHour(minutes)
- updateEndTimeText()
- }
- }
-
- private fun checkRepeatRule() {
- if (mRepeatInterval.isXWeeklyRepetition()) {
- val day = mRepeatRule
- if (day == MONDAY || day == TUESDAY || day == WEDNESDAY || day == THURSDAY || day == FRIDAY || day == SATURDAY || day == SUNDAY) {
- setRepeatRule(Math.pow(2.0, (mEventStartDateTime.dayOfWeek - 1).toDouble()).toInt())
- }
- } else if (mRepeatInterval.isXMonthlyRepetition()) {
- if (mRepeatRule == REPEAT_MONTH_LAST_DAY && !isLastDayOfTheMonth())
- mRepeatRule = REPEAT_MONTH_SAME_DAY
- checkRepetitionRuleText()
- }
- }
-
- private fun updateIconColors() {
- val textColor = config.textColor
- event_time_image.applyColorFilter(textColor)
- event_repetition_image.applyColorFilter(textColor)
- event_reminder_image.applyColorFilter(textColor)
- event_type_image.applyColorFilter(textColor)
- event_caldav_calendar_image.applyColorFilter(textColor)
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/activities/MainActivity.kt
deleted file mode 100644
index a79bfc65d..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/MainActivity.kt
+++ /dev/null
@@ -1,832 +0,0 @@
-package com.simplemobiletools.calendar.activities
-
-import android.content.ContentResolver
-import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.database.ContentObserver
-import android.database.Cursor
-import android.net.Uri
-import android.os.Bundle
-import android.os.Handler
-import android.provider.CalendarContract
-import android.provider.ContactsContract
-import android.support.v4.view.ViewPager
-import android.util.SparseIntArray
-import android.view.Menu
-import android.view.MenuItem
-import android.widget.TextView
-import android.widget.Toast
-import com.simplemobiletools.calendar.BuildConfig
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.adapters.MyMonthPagerAdapter
-import com.simplemobiletools.calendar.adapters.MyWeekPagerAdapter
-import com.simplemobiletools.calendar.adapters.MyYearPagerAdapter
-import com.simplemobiletools.calendar.dialogs.ExportEventsDialog
-import com.simplemobiletools.calendar.dialogs.FilterEventTypesDialog
-import com.simplemobiletools.calendar.dialogs.ImportEventsDialog
-import com.simplemobiletools.calendar.extensions.*
-import com.simplemobiletools.calendar.fragments.EventListFragment
-import com.simplemobiletools.calendar.fragments.WeekFragment
-import com.simplemobiletools.calendar.helpers.*
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.interfaces.NavigationListener
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.calendar.views.MyScrollView
-import com.simplemobiletools.commons.dialogs.FilePickerDialog
-import com.simplemobiletools.commons.dialogs.RadioGroupDialog
-import com.simplemobiletools.commons.extensions.*
-import com.simplemobiletools.commons.helpers.*
-import com.simplemobiletools.commons.models.RadioItem
-import com.simplemobiletools.commons.models.Release
-import kotlinx.android.synthetic.main.activity_main.*
-import org.joda.time.DateTime
-import java.io.FileOutputStream
-import java.text.SimpleDateFormat
-import java.util.*
-import kotlin.collections.ArrayList
-
-class MainActivity : SimpleActivity(), NavigationListener {
- private val CALDAV_SYNC_DELAY = 2000L
- private val PREFILLED_MONTHS = 97
- private val PREFILLED_YEARS = 31
- private val PREFILLED_WEEKS = 61
-
- private var mIsMonthSelected = false
- private var mStoredUseEnglish = false
- private var mStoredTextColor = 0
- private var mStoredBackgroundColor = 0
- private var mStoredPrimaryColor = 0
- private var mStoredDayCode = ""
- private var mStoredIsSundayFirst = false
- private var mStoredUse24HourFormat = false
- private var mShouldFilterBeVisible = false
- private var mCalDAVSyncHandler = Handler()
-
- private var mDefaultWeeklyPage = 0
- private var mDefaultMonthlyPage = 0
- private var mDefaultYearlyPage = 0
-
- companion object {
- var mWeekScrollY = 0
- var eventTypeColors = SparseIntArray()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- appLaunched()
- calendar_fab.setOnClickListener { launchNewEventIntent() }
- checkWhatsNewDialog()
-
- if (resources.getBoolean(R.bool.portrait_only))
- requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-
- if (intent?.action == Intent.ACTION_VIEW && intent.data != null) {
- val uri = intent.data
- if (uri.authority == "com.android.calendar") {
- // clicking date on a third party widget: content://com.android.calendar/time/1507309245683
- if (intent?.extras?.getBoolean("DETAIL_VIEW", false) == true) {
- val timestamp = uri.pathSegments.last()
- if (timestamp.areDigitsOnly()) {
- openDayAt(timestamp.toLong())
- return
- }
- }
- } else {
- tryImportEventsFromFile(uri)
- }
- }
-
- storeStateVariables()
- updateViewPager()
-
- if (!hasPermission(PERMISSION_WRITE_CALENDAR) || !hasPermission(PERMISSION_READ_CALENDAR)) {
- config.caldavSync = false
- }
-
- recheckCalDAVCalendars {}
-
- if (config.googleSync) {
- val ids = dbHelper.getGoogleSyncEvents().map { it.id.toString() }.toTypedArray()
- dbHelper.deleteEvents(ids, false)
- config.googleSync = false
- }
-
- checkOpenIntents()
- }
-
- override fun onResume() {
- super.onResume()
- if (mStoredUseEnglish != config.useEnglish) {
- restartActivity()
- return
- }
-
- if (mStoredTextColor != config.textColor || mStoredBackgroundColor != config.backgroundColor || mStoredPrimaryColor != config.primaryColor
- || mStoredDayCode != Formatter.getTodayCode()) {
- updateViewPager()
- }
-
- dbHelper.getEventTypes {
- eventTypeColors.clear()
- it.map { eventTypeColors.put(it.id, it.color) }
- mShouldFilterBeVisible = eventTypeColors.size() > 1 || config.displayEventTypes.isEmpty()
- invalidateOptionsMenu()
- }
-
- storeStateVariables()
- if (config.storedView == WEEKLY_VIEW) {
- if (mStoredIsSundayFirst != config.isSundayFirst || mStoredUse24HourFormat != config.use24hourFormat) {
- fillWeeklyViewPager()
- }
- }
-
- updateWidgets()
- updateTextColors(calendar_coordinator)
- }
-
- override fun onPause() {
- super.onPause()
- storeStateVariables()
- }
-
- override fun onStop() {
- super.onStop()
- mCalDAVSyncHandler.removeCallbacksAndMessages(null)
- contentResolver.unregisterContentObserver(calDAVSyncObserver)
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.menu_main, menu)
- menu.apply {
- findItem(R.id.filter).isVisible = mShouldFilterBeVisible
- findItem(R.id.go_to_today).isVisible = shouldGoToTodayBeVisible()
- findItem(R.id.refresh_caldav_calendars).isVisible = config.caldavSync
- }
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.change_view -> showViewDialog()
- R.id.go_to_today -> goToToday()
- R.id.filter -> showFilterDialog()
- R.id.refresh_caldav_calendars -> refreshCalDAVCalendars()
- R.id.add_holidays -> addHolidays()
- R.id.add_birthdays -> tryAddBirthdays()
- R.id.add_anniversaries -> tryAddAnniversaries()
- R.id.import_events -> tryImportEvents()
- R.id.export_events -> tryExportEvents()
- R.id.settings -> launchSettings()
- R.id.about -> launchAbout()
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- override fun onBackPressed() {
- if (mIsMonthSelected && config.storedView == YEARLY_VIEW) {
- updateView(YEARLY_VIEW)
- } else {
- super.onBackPressed()
- }
- }
-
- private fun storeStateVariables() {
- config.apply {
- mStoredUseEnglish = useEnglish
- mStoredIsSundayFirst = isSundayFirst
- mStoredTextColor = textColor
- mStoredPrimaryColor = primaryColor
- mStoredBackgroundColor = backgroundColor
- mStoredUse24HourFormat = use24hourFormat
- }
- mStoredDayCode = Formatter.getTodayCode()
- }
-
- private fun checkOpenIntents() {
- val dayCodeToOpen = intent.getStringExtra(DAY_CODE) ?: ""
- if (dayCodeToOpen.isNotEmpty()) {
- openDayCode(dayCodeToOpen)
- }
-
- val eventIdToOpen = intent.getIntExtra(EVENT_ID, 0)
- val eventOccurrenceToOpen = intent.getIntExtra(EVENT_OCCURRENCE_TS, 0)
- if (eventIdToOpen != 0 && eventOccurrenceToOpen != 0) {
- Intent(this, EventActivity::class.java).apply {
- putExtra(EVENT_ID, eventIdToOpen)
- putExtra(EVENT_OCCURRENCE_TS, eventOccurrenceToOpen)
- startActivity(this)
- }
- }
- }
-
- private fun showViewDialog() {
- val res = resources
- val items = arrayListOf(
- RadioItem(WEEKLY_VIEW, res.getString(R.string.weekly_view)),
- RadioItem(MONTHLY_VIEW, res.getString(R.string.monthly_view)),
- RadioItem(YEARLY_VIEW, res.getString(R.string.yearly_view)),
- RadioItem(EVENTS_LIST_VIEW, res.getString(R.string.simple_event_list)))
-
- RadioGroupDialog(this, items, config.storedView) {
- updateView(it as Int)
- invalidateOptionsMenu()
- }
- }
-
- private fun goToToday() {
- if (config.storedView == WEEKLY_VIEW) {
- week_view_view_pager.currentItem = mDefaultWeeklyPage
- } else if (config.storedView == MONTHLY_VIEW) {
- main_view_pager.currentItem = mDefaultMonthlyPage
- } else if (config.storedView == YEARLY_VIEW) {
- if (mIsMonthSelected) {
- openMonthlyToday()
- } else {
- main_view_pager.currentItem = mDefaultYearlyPage
- }
- }
- }
-
- private fun shouldGoToTodayBeVisible() = when {
- config.storedView == WEEKLY_VIEW -> week_view_view_pager.currentItem != mDefaultWeeklyPage
- config.storedView == MONTHLY_VIEW -> main_view_pager.currentItem != mDefaultMonthlyPage
- config.storedView == YEARLY_VIEW -> main_view_pager.currentItem != mDefaultYearlyPage
- else -> false
- }
-
- private fun showFilterDialog() {
- FilterEventTypesDialog(this) {
- refreshViewPager()
- }
- }
-
- private fun refreshCalDAVCalendars() {
- toast(R.string.refreshing)
- val uri = CalendarContract.Calendars.CONTENT_URI
- contentResolver.registerContentObserver(uri, false, calDAVSyncObserver)
- Bundle().apply {
- putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
- putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)
- ContentResolver.requestSync(null, uri.authority, this)
- }
- scheduleCalDAVSync(true)
- }
-
- private val calDAVSyncObserver = object : ContentObserver(Handler()) {
- override fun onChange(selfChange: Boolean) {
- super.onChange(selfChange)
- if (!selfChange) {
- mCalDAVSyncHandler.removeCallbacksAndMessages(null)
- mCalDAVSyncHandler.postDelayed({
- recheckCalDAVCalendars {
- refreshViewPager()
- toast(R.string.refreshing_complete)
- }
- }, CALDAV_SYNC_DELAY)
- }
- }
- }
-
- private fun addHolidays() {
- val items = getHolidayRadioItems()
- RadioGroupDialog(this, items) {
- toast(R.string.importing)
- Thread {
- val holidays = getString(R.string.holidays)
- var eventTypeId = dbHelper.getEventTypeIdWithTitle(holidays)
- if (eventTypeId == -1) {
- val eventType = EventType(0, holidays, resources.getColor(R.color.default_holidays_color))
- eventTypeId = dbHelper.insertEventType(eventType)
- }
- val result = IcsImporter(this).importEvents(it as String, eventTypeId)
- handleParseResult(result)
- if (result != IcsImporter.ImportResult.IMPORT_FAIL) {
- runOnUiThread {
- updateViewPager()
- }
- }
- }.start()
- }
- }
-
- private fun tryAddBirthdays() {
- handlePermission(PERMISSION_READ_CONTACTS) {
- if (it) {
- Thread {
- addContactEvents(true) {
- if (it > 0) {
- toast(R.string.birthdays_added)
- updateViewPager()
- } else {
- toast(R.string.no_birthdays)
- }
- }
- }.start()
- } else {
- toast(R.string.no_contacts_permission)
- }
- }
- }
-
- private fun tryAddAnniversaries() {
- handlePermission(PERMISSION_READ_CONTACTS) {
- if (it) {
- Thread {
- addContactEvents(false) {
- if (it > 0) {
- toast(R.string.anniversaries_added)
- updateViewPager()
- } else {
- toast(R.string.no_anniversaries)
- }
- }
- }.start()
- } else {
- toast(R.string.no_contacts_permission)
- }
- }
- }
-
- private fun handleParseResult(result: IcsImporter.ImportResult) {
- toast(when (result) {
- IcsImporter.ImportResult.IMPORT_OK -> R.string.holidays_imported_successfully
- IcsImporter.ImportResult.IMPORT_PARTIAL -> R.string.importing_some_holidays_failed
- else -> R.string.importing_holidays_failed
- }, Toast.LENGTH_LONG)
- }
-
- private fun addContactEvents(birthdays: Boolean, callback: (Int) -> Unit) {
- var eventsAdded = 0
- val uri = ContactsContract.Data.CONTENT_URI
- val projection = arrayOf(ContactsContract.Contacts.DISPLAY_NAME,
- ContactsContract.CommonDataKinds.Event.CONTACT_ID,
- ContactsContract.CommonDataKinds.Event.CONTACT_LAST_UPDATED_TIMESTAMP,
- ContactsContract.CommonDataKinds.Event.START_DATE)
-
- val selection = "${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.CommonDataKinds.Event.TYPE} = ?"
- val type = if (birthdays) ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY else ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY
- val selectionArgs = arrayOf(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, type.toString())
- var cursor: Cursor? = null
- try {
- cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
- if (cursor?.moveToFirst() == true) {
- val dateFormats = getDateFormats()
- val existingEvents = if (birthdays) dbHelper.getBirthdays() else dbHelper.getAnniversaries()
- val importIDs = existingEvents.map { it.importId }
- val eventTypeId = if (birthdays) getBirthdaysEventTypeId() else getAnniversariesEventTypeId()
-
- do {
- val contactId = cursor.getIntValue(ContactsContract.CommonDataKinds.Event.CONTACT_ID).toString()
- val name = cursor.getStringValue(ContactsContract.Contacts.DISPLAY_NAME)
- val startDate = cursor.getStringValue(ContactsContract.CommonDataKinds.Event.START_DATE)
-
- for (format in dateFormats) {
- try {
- val formatter = SimpleDateFormat(format, Locale.getDefault())
- val date = formatter.parse(startDate)
- if (date.year < 70)
- date.year = 70
-
- val timestamp = (date.time / 1000).toInt()
- val source = if (birthdays) SOURCE_CONTACT_BIRTHDAY else SOURCE_CONTACT_ANNIVERSARY
- val lastUpdated = cursor.getLongValue(ContactsContract.CommonDataKinds.Event.CONTACT_LAST_UPDATED_TIMESTAMP)
- val event = Event(0, timestamp, timestamp, name, importId = contactId, flags = FLAG_ALL_DAY, repeatInterval = YEAR,
- eventType = eventTypeId, source = source, lastUpdated = lastUpdated)
-
- if (!importIDs.contains(contactId)) {
- dbHelper.insert(event, false) {
- eventsAdded++
- }
- }
- break
- } catch (e: Exception) {
- }
- }
- } while (cursor.moveToNext())
- }
- } catch (e: Exception) {
- showErrorToast(e)
- } finally {
- cursor?.close()
- }
-
- runOnUiThread {
- callback(eventsAdded)
- }
- }
-
- private fun getBirthdaysEventTypeId(): Int {
- val birthdays = getString(R.string.birthdays)
- var eventTypeId = dbHelper.getEventTypeIdWithTitle(birthdays)
- if (eventTypeId == -1) {
- val eventType = EventType(0, birthdays, resources.getColor(R.color.default_birthdays_color))
- eventTypeId = dbHelper.insertEventType(eventType)
- }
- return eventTypeId
- }
-
- private fun getAnniversariesEventTypeId(): Int {
- val anniversaries = getString(R.string.anniversaries)
- var eventTypeId = dbHelper.getEventTypeIdWithTitle(anniversaries)
- if (eventTypeId == -1) {
- val eventType = EventType(0, anniversaries, resources.getColor(R.color.default_anniversaries_color))
- eventTypeId = dbHelper.insertEventType(eventType)
- }
- return eventTypeId
- }
-
- private fun updateView(view: Int) {
- calendar_fab.beGoneIf(view == YEARLY_VIEW)
- mIsMonthSelected = view == MONTHLY_VIEW
- config.storedView = view
- updateViewPager()
- }
-
- private fun updateViewPager() {
- resetTitle()
- when {
- config.storedView == YEARLY_VIEW -> fillYearlyViewPager()
- config.storedView == EVENTS_LIST_VIEW -> fillEventsList()
- config.storedView == WEEKLY_VIEW -> fillWeeklyViewPager()
- else -> openMonthlyToday()
- }
-
- mWeekScrollY = 0
- }
-
- private fun openMonthlyToday() {
- val targetDay = DateTime().toString(Formatter.DAYCODE_PATTERN)
- fillMonthlyViewPager(targetDay)
- }
-
- private fun refreshViewPager() {
- when {
- config.storedView == YEARLY_VIEW && !mIsMonthSelected -> (main_view_pager.adapter as? MyYearPagerAdapter)?.refreshEvents(main_view_pager.currentItem)
- config.storedView == EVENTS_LIST_VIEW -> fillEventsList()
- config.storedView == WEEKLY_VIEW -> (week_view_view_pager.adapter as? MyWeekPagerAdapter)?.refreshEvents(week_view_view_pager.currentItem)
- else -> (main_view_pager.adapter as? MyMonthPagerAdapter)?.refreshEvents(main_view_pager.currentItem)
- }
- }
-
- private fun tryImportEvents() {
- handlePermission(PERMISSION_READ_STORAGE) {
- if (it) {
- importEvents()
- }
- }
- }
-
- private fun importEvents() {
- FilePickerDialog(this) {
- importEventsDialog(it)
- }
- }
-
- private fun tryImportEventsFromFile(uri: Uri) {
- when {
- uri.scheme == "file" -> importEventsDialog(uri.path)
- uri.scheme == "content" -> {
- val tempFile = getTempFile()
- if (tempFile == null) {
- toast(R.string.unknown_error_occurred)
- return
- }
-
- val inputStream = contentResolver.openInputStream(uri)
- val out = FileOutputStream(tempFile)
- inputStream.copyTo(out)
- importEventsDialog(tempFile.absolutePath)
- }
- else -> toast(R.string.invalid_file_format)
- }
- }
-
- private fun importEventsDialog(path: String) {
- ImportEventsDialog(this, path) {
- if (it) {
- runOnUiThread {
- updateViewPager()
- }
- }
- }
- }
-
- private fun tryExportEvents() {
- handlePermission(PERMISSION_WRITE_STORAGE) {
- if (it) {
- exportEvents()
- }
- }
- }
-
- private fun exportEvents() {
- FilePickerDialog(this, pickFile = false, showFAB = true) {
- val path = it
- ExportEventsDialog(this, path) { exportPastEvents, file, eventTypes ->
- Thread {
- val events = dbHelper.getEventsToExport(exportPastEvents).filter { eventTypes.contains(it.eventType.toString()) }
- if (events.isEmpty()) {
- toast(R.string.no_events_for_exporting)
- } else {
- toast(R.string.exporting)
- IcsExporter().exportEvents(this, file, events as ArrayList) {
- toast(when (it) {
- IcsExporter.ExportResult.EXPORT_OK -> R.string.events_exported_successfully
- IcsExporter.ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_events_failed
- else -> R.string.exporting_events_failed
- })
- }
- }
- }.start()
- }
- }
- }
-
- private fun launchSettings() {
- startActivity(Intent(applicationContext, SettingsActivity::class.java))
- }
-
- private fun launchAbout() {
- startAboutActivity(R.string.app_name, LICENSE_KOTLIN or LICENSE_JODA or LICENSE_STETHO or LICENSE_MULTISELECT or LICENSE_GSON or
- LICENSE_LEAK_CANARY, BuildConfig.VERSION_NAME)
- }
-
- private fun resetTitle() {
- supportActionBar?.title = getString(R.string.app_launcher_name)
- supportActionBar?.subtitle = ""
- }
-
- private fun fillMonthlyViewPager(targetDay: String) {
- main_weekly_scrollview.beGone()
- calendar_fab.beVisible()
- val codes = getMonths(targetDay)
- val monthlyAdapter = MyMonthPagerAdapter(supportFragmentManager, codes, this)
- mDefaultMonthlyPage = codes.size / 2
-
- main_view_pager.apply {
- adapter = monthlyAdapter
- beVisible()
- addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
- override fun onPageScrollStateChanged(state: Int) {
- }
-
- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
- }
-
- override fun onPageSelected(position: Int) {
- invalidateOptionsMenu()
- if (config.storedView == YEARLY_VIEW) {
- val dateTime = Formatter.getDateTimeFromCode(codes[position])
- supportActionBar?.title = "${getString(R.string.app_launcher_name)} - ${Formatter.getYear(dateTime)}"
- }
- }
- })
- currentItem = mDefaultMonthlyPage
- }
- calendar_event_list_holder.beGone()
- }
-
- private fun getMonths(code: String): List {
- val months = ArrayList(PREFILLED_MONTHS)
- val today = Formatter.getDateTimeFromCode(code)
- for (i in -PREFILLED_MONTHS / 2..PREFILLED_MONTHS / 2) {
- months.add(Formatter.getDayCodeFromDateTime(today.plusMonths(i)))
- }
-
- return months
- }
-
- private fun fillWeeklyViewPager() {
- var thisweek = DateTime().withDayOfWeek(1).withTimeAtStartOfDay().minusDays(if (config.isSundayFirst) 1 else 0)
- if (DateTime().minusDays(7).seconds() > thisweek.seconds()) {
- thisweek = thisweek.plusDays(7)
- }
- val weekTSs = getWeekTimestamps(thisweek.seconds())
- val weeklyAdapter = MyWeekPagerAdapter(supportFragmentManager, weekTSs, object : WeekFragment.WeekScrollListener {
- override fun scrollTo(y: Int) {
- week_view_hours_scrollview.scrollY = y
- mWeekScrollY = y
- }
- })
- main_view_pager.beGone()
- calendar_event_list_holder.beGone()
- main_weekly_scrollview.beVisible()
-
- week_view_hours_holder.removeAllViews()
- val hourDateTime = DateTime().withDate(2000, 1, 1).withTime(0, 0, 0, 0)
- for (i in 1..23) {
- val formattedHours = Formatter.getHours(applicationContext, hourDateTime.withHourOfDay(i))
- (layoutInflater.inflate(R.layout.weekly_view_hour_textview, null, false) as TextView).apply {
- text = formattedHours
- setTextColor(mStoredTextColor)
- week_view_hours_holder.addView(this)
- }
- }
-
- mDefaultWeeklyPage = weekTSs.size / 2
- week_view_view_pager.apply {
- adapter = weeklyAdapter
- addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
- override fun onPageScrollStateChanged(state: Int) {
- }
-
- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
- }
-
- override fun onPageSelected(position: Int) {
- invalidateOptionsMenu()
- setupWeeklyActionbarTitle(weekTSs[position])
- }
- })
- currentItem = mDefaultWeeklyPage
- }
-
- week_view_hours_scrollview.setOnScrollviewListener(object : MyScrollView.ScrollViewListener {
- override fun onScrollChanged(scrollView: MyScrollView, x: Int, y: Int, oldx: Int, oldy: Int) {
- mWeekScrollY = y
- weeklyAdapter.updateScrollY(week_view_view_pager.currentItem, y)
- }
- })
- week_view_hours_scrollview.setOnTouchListener { view, motionEvent -> true }
- }
-
- fun updateHoursTopMargin(margin: Int) {
- week_view_hours_divider.layoutParams.height = margin
- week_view_hours_scrollview.requestLayout()
- }
-
- private fun getWeekTimestamps(targetWeekTS: Int): List {
- val weekTSs = ArrayList(PREFILLED_WEEKS)
- for (i in -PREFILLED_WEEKS / 2..PREFILLED_WEEKS / 2) {
- weekTSs.add(Formatter.getDateTimeFromTS(targetWeekTS).plusWeeks(i).seconds())
- }
- return weekTSs
- }
-
- private fun setupWeeklyActionbarTitle(timestamp: Int) {
- val startDateTime = Formatter.getDateTimeFromTS(timestamp)
- val endDateTime = Formatter.getDateTimeFromTS(timestamp + WEEK_SECONDS)
- val startMonthName = Formatter.getMonthName(applicationContext, startDateTime.monthOfYear)
- if (startDateTime.monthOfYear == endDateTime.monthOfYear) {
- var newTitle = startMonthName
- if (startDateTime.year != DateTime().year)
- newTitle += " - ${startDateTime.year}"
- supportActionBar?.title = newTitle
- } else {
- val endMonthName = Formatter.getMonthName(applicationContext, endDateTime.monthOfYear)
- supportActionBar?.title = "$startMonthName - $endMonthName"
- }
- supportActionBar?.subtitle = "${getString(R.string.week)} ${startDateTime.plusDays(3).weekOfWeekyear}"
- }
-
- private fun fillYearlyViewPager() {
- main_weekly_scrollview.beGone()
- calendar_fab.beGone()
- val targetYear = DateTime().toString(Formatter.YEAR_PATTERN).toInt()
- val years = getYears(targetYear)
- val yearlyAdapter = MyYearPagerAdapter(supportFragmentManager, years, this)
-
- mDefaultYearlyPage = years.size / 2
- main_view_pager.apply {
- adapter = yearlyAdapter
- addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
- override fun onPageScrollStateChanged(state: Int) {
- }
-
- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
- }
-
- override fun onPageSelected(position: Int) {
- invalidateOptionsMenu()
- if (position < years.size) {
- supportActionBar?.title = "${getString(R.string.app_launcher_name)} - ${years[position]}"
- }
- }
- })
- currentItem = mDefaultYearlyPage
- beVisible()
- }
- supportActionBar?.title = "${getString(R.string.app_launcher_name)} - ${years[years.size / 2]}"
- calendar_event_list_holder.beGone()
- }
-
- private fun getYears(targetYear: Int): List {
- val years = ArrayList(PREFILLED_YEARS)
- years += targetYear - PREFILLED_YEARS / 2..targetYear + PREFILLED_YEARS / 2
- return years
- }
-
- private fun fillEventsList() {
- main_view_pager.adapter = null
- main_view_pager.beGone()
- main_weekly_scrollview.beGone()
- calendar_event_list_holder.beVisible()
- supportFragmentManager.beginTransaction().replace(R.id.calendar_event_list_holder, EventListFragment(), "").commit()
- }
-
- override fun goLeft() {
- main_view_pager.currentItem = main_view_pager.currentItem - 1
- }
-
- override fun goRight() {
- main_view_pager.currentItem = main_view_pager.currentItem + 1
- }
-
- override fun goToDateTime(dateTime: DateTime) {
- fillMonthlyViewPager(Formatter.getDayCodeFromDateTime(dateTime))
- mIsMonthSelected = true
- }
-
- private fun openDayAt(timestamp: Long) {
- val dayCode = Formatter.getDayCodeFromTS((timestamp / 1000).toInt())
- openDayCode(dayCode)
- }
-
- private fun openDayCode(dayCode: String) {
- Intent(this, DayActivity::class.java).apply {
- putExtra(DAY_CODE, dayCode)
- startActivity(this)
- }
- }
-
- private fun getHolidayRadioItems(): ArrayList {
- val items = ArrayList()
-
- LinkedHashMap().apply {
- put("Algeria", "algeria.ics")
- put("Argentina", "argentina.ics")
- put("België", "belgium.ics")
- put("Bolivia", "bolivia.ics")
- put("Brasil", "brazil.ics")
- put("Canada", "canada.ics")
- put("Česká republika", "czech.ics")
- put("Deutschland", "germany.ics")
- put("Eesti", "estonia.ics")
- put("España", "spain.ics")
- put("Éire", "ireland.ics")
- put("France", "france.ics")
- put("Hanguk", "southkorea.ics")
- put("Hellas", "greece.ics")
- put("India", "india.ics")
- put("Ísland", "iceland.ics")
- put("Italia", "italy.ics")
- put("Magyarország", "hungary.ics")
- put("Nederland", "netherlands.ics")
- put("日本", "japan.ics")
- put("Norge", "norway.ics")
- put("Österreich", "austria.ics")
- put("Pākistān", "pakistan.ics")
- put("Polska", "poland.ics")
- put("Portugal", "portugal.ics")
- put("Россия", "russia.ics")
- put("Schweiz", "switzerland.ics")
- put("Slovenija", "slovenia.ics")
- put("Slovensko", "slovakia.ics")
- put("Suomi", "finland.ics")
- put("Sverige", "sweden.ics")
- put("United Kingdom", "unitedkingdom.ics")
- put("United States", "unitedstates.ics")
-
- var i = 0
- for ((country, file) in this) {
- items.add(RadioItem(i++, country, file))
- }
- }
-
- return items
- }
-
- private fun checkWhatsNewDialog() {
- arrayListOf().apply {
- add(Release(39, R.string.release_39))
- add(Release(40, R.string.release_40))
- add(Release(42, R.string.release_42))
- add(Release(44, R.string.release_44))
- add(Release(46, R.string.release_46))
- add(Release(48, R.string.release_48))
- add(Release(49, R.string.release_49))
- add(Release(51, R.string.release_51))
- add(Release(52, R.string.release_52))
- add(Release(54, R.string.release_54))
- add(Release(57, R.string.release_57))
- add(Release(59, R.string.release_59))
- add(Release(60, R.string.release_60))
- add(Release(62, R.string.release_62))
- add(Release(67, R.string.release_67))
- add(Release(69, R.string.release_69))
- add(Release(71, R.string.release_71))
- add(Release(73, R.string.release_73))
- add(Release(76, R.string.release_76))
- add(Release(77, R.string.release_77))
- add(Release(80, R.string.release_80))
- add(Release(84, R.string.release_84))
- add(Release(86, R.string.release_86))
- add(Release(88, R.string.release_88))
- add(Release(98, R.string.release_98))
- checkWhatsNew(this, BuildConfig.VERSION_CODE)
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SettingsActivity.kt
deleted file mode 100644
index fa64a3bff..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SettingsActivity.kt
+++ /dev/null
@@ -1,367 +0,0 @@
-package com.simplemobiletools.calendar.activities
-
-import android.content.Intent
-import android.content.res.Resources
-import android.media.RingtoneManager
-import android.net.Uri
-import android.os.Bundle
-import android.os.Parcelable
-import android.text.TextUtils
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.dialogs.CustomEventReminderDialog
-import com.simplemobiletools.calendar.dialogs.SelectCalendarsDialog
-import com.simplemobiletools.calendar.dialogs.SnoozePickerDialog
-import com.simplemobiletools.calendar.extensions.*
-import com.simplemobiletools.calendar.helpers.CalDAVHandler
-import com.simplemobiletools.calendar.helpers.FONT_SIZE_LARGE
-import com.simplemobiletools.calendar.helpers.FONT_SIZE_MEDIUM
-import com.simplemobiletools.calendar.helpers.FONT_SIZE_SMALL
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.dialogs.RadioGroupDialog
-import com.simplemobiletools.commons.extensions.*
-import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALENDAR
-import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALENDAR
-import com.simplemobiletools.commons.models.RadioItem
-import kotlinx.android.synthetic.main.activity_settings.*
-import java.util.*
-
-class SettingsActivity : SimpleActivity() {
- private val GET_RINGTONE_URI = 1
-
- lateinit var res: Resources
- private var mStoredPrimaryColor = 0
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_settings)
- res = resources
- mStoredPrimaryColor = config.primaryColor
- setupCaldavSync()
- }
-
- override fun onResume() {
- super.onResume()
-
- setupCustomizeColors()
- setupUseEnglish()
- setupManageEventTypes()
- setupHourFormat()
- setupSundayFirst()
- setupReplaceDescription()
- setupWeekNumbers()
- setupWeeklyStart()
- setupWeeklyEnd()
- setupVibrate()
- setupReminderSound()
- setupSnoozeDelay()
- setupEventReminder()
- setupDisplayPastEvents()
- setupFontSize()
- updateTextColors(settings_holder)
- checkPrimaryColor()
- }
-
- override fun onPause() {
- super.onPause()
- mStoredPrimaryColor = config.primaryColor
- }
-
- private fun checkPrimaryColor() {
- if (config.primaryColor != mStoredPrimaryColor) {
- dbHelper.getEventTypes {
- if (it.filter { it.caldavCalendarId == 0 }.size == 1) {
- val eventType = it.first { it.caldavCalendarId == 0 }
- eventType.color = config.primaryColor
- dbHelper.updateEventType(eventType)
- }
- }
- }
- }
-
- private fun setupCustomizeColors() {
- settings_customize_colors_holder.setOnClickListener {
- startCustomizationActivity()
- }
- }
-
- private fun setupUseEnglish() {
- settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
- settings_use_english.isChecked = config.useEnglish
- settings_use_english_holder.setOnClickListener {
- settings_use_english.toggle()
- config.useEnglish = settings_use_english.isChecked
- useEnglishToggled()
- }
- }
-
- private fun setupManageEventTypes() {
- settings_manage_event_types_holder.setOnClickListener {
- startActivity(Intent(this, ManageEventTypesActivity::class.java))
- }
- }
-
- private fun setupHourFormat() {
- settings_hour_format.isChecked = config.use24hourFormat
- settings_hour_format_holder.setOnClickListener {
- settings_hour_format.toggle()
- config.use24hourFormat = settings_hour_format.isChecked
- }
- }
-
- private fun setupCaldavSync() {
- settings_caldav_sync.isChecked = config.caldavSync
- settings_caldav_sync_holder.setOnClickListener {
- if (config.caldavSync) {
- toggleCaldavSync(false)
- } else {
- handlePermission(PERMISSION_WRITE_CALENDAR) {
- if (it) {
- handlePermission(PERMISSION_READ_CALENDAR) {
- if (it) {
- toggleCaldavSync(true)
- }
- }
- }
- }
- }
- }
-
- settings_manage_synced_calendars_holder.beVisibleIf(config.caldavSync)
- settings_manage_synced_calendars_holder.setOnClickListener {
- showCalendarPicker()
- }
- }
-
- private fun toggleCaldavSync(enable: Boolean) {
- if (enable) {
- showCalendarPicker()
- } else {
- settings_caldav_sync.isChecked = false
- config.caldavSync = false
- settings_manage_synced_calendars_holder.beGone()
- config.getSyncedCalendarIdsAsList().forEach {
- CalDAVHandler(applicationContext).deleteCalDAVCalendarEvents(it.toLong())
- }
- dbHelper.deleteEventTypesWithCalendarId(config.caldavSyncedCalendarIDs)
- }
- }
-
- private fun showCalendarPicker() {
- val oldCalendarIds = config.getSyncedCalendarIdsAsList()
-
- SelectCalendarsDialog(this) {
- val newCalendarIds = config.getSyncedCalendarIdsAsList()
- settings_manage_synced_calendars_holder.beVisibleIf(newCalendarIds.isNotEmpty())
- settings_caldav_sync.isChecked = newCalendarIds.isNotEmpty()
- config.caldavSync = newCalendarIds.isNotEmpty()
- toast(R.string.syncing)
-
- Thread {
- if (newCalendarIds.isNotEmpty()) {
- val existingEventTypeNames = dbHelper.fetchEventTypes().map { it.getDisplayTitle().toLowerCase() } as ArrayList
- getSyncedCalDAVCalendars().forEach {
- val calendarTitle = it.getFullTitle()
- if (!existingEventTypeNames.contains(calendarTitle.toLowerCase())) {
- val eventType = EventType(0, it.displayName, it.color, it.id, it.displayName, it.accountName)
- existingEventTypeNames.add(calendarTitle.toLowerCase())
- dbHelper.insertEventType(eventType)
- }
- }
- CalDAVHandler(applicationContext).refreshCalendars(this) {}
- }
-
- val removedCalendarIds = oldCalendarIds.filter { !newCalendarIds.contains(it) }
- removedCalendarIds.forEach {
- CalDAVHandler(applicationContext).deleteCalDAVCalendarEvents(it.toLong())
- dbHelper.getEventTypeWithCalDAVCalendarId(it.toInt())?.apply {
- dbHelper.deleteEventTypes(arrayListOf(this), true) {}
- }
- }
- dbHelper.deleteEventTypesWithCalendarId(TextUtils.join(",", removedCalendarIds))
- toast(R.string.synchronization_completed)
- }.start()
- }
- }
-
- private fun setupSundayFirst() {
- settings_sunday_first.isChecked = config.isSundayFirst
- settings_sunday_first_holder.setOnClickListener {
- settings_sunday_first.toggle()
- config.isSundayFirst = settings_sunday_first.isChecked
- }
- }
-
- private fun setupReplaceDescription() {
- settings_replace_description.isChecked = config.replaceDescription
- settings_replace_description_holder.setOnClickListener {
- settings_replace_description.toggle()
- config.replaceDescription = settings_replace_description.isChecked
- }
- }
-
- private fun setupWeeklyStart() {
- settings_start_weekly_at.text = getHoursString(config.startWeeklyAt)
- settings_start_weekly_at_holder.setOnClickListener {
- val items = ArrayList()
- (0..24).mapTo(items) { RadioItem(it, getHoursString(it)) }
-
- RadioGroupDialog(this@SettingsActivity, items, config.startWeeklyAt) {
- if (it as Int >= config.endWeeklyAt) {
- toast(R.string.day_end_before_start)
- } else {
- config.startWeeklyAt = it
- settings_start_weekly_at.text = getHoursString(it)
- }
- }
- }
- }
-
- private fun setupWeeklyEnd() {
- settings_end_weekly_at.text = getHoursString(config.endWeeklyAt)
- settings_end_weekly_at_holder.setOnClickListener {
- val items = ArrayList()
- (0..24).mapTo(items) { RadioItem(it, getHoursString(it)) }
-
- RadioGroupDialog(this@SettingsActivity, items, config.endWeeklyAt) {
- if (it as Int <= config.startWeeklyAt) {
- toast(R.string.day_end_before_start)
- } else {
- config.endWeeklyAt = it
- settings_end_weekly_at.text = getHoursString(it)
- }
- }
- }
- }
-
- private fun setupWeekNumbers() {
- settings_week_numbers.isChecked = config.displayWeekNumbers
- settings_week_numbers_holder.setOnClickListener {
- settings_week_numbers.toggle()
- config.displayWeekNumbers = settings_week_numbers.isChecked
- }
- }
-
- private fun setupReminderSound() {
- val noRingtone = res.getString(R.string.no_ringtone_selected)
- if (config.reminderSound.isEmpty()) {
- settings_reminder_sound.text = noRingtone
- } else {
- settings_reminder_sound.text = RingtoneManager.getRingtone(this, Uri.parse(config.reminderSound))?.getTitle(this) ?: noRingtone
- }
- settings_reminder_sound_holder.setOnClickListener {
- Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
- putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
- putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, res.getString(R.string.reminder_sound))
- putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(config.reminderSound))
-
- if (resolveActivity(packageManager) != null)
- startActivityForResult(this, GET_RINGTONE_URI)
- else {
- toast(R.string.no_ringtone_picker)
- }
- }
- }
- }
-
- private fun setupVibrate() {
- settings_vibrate.isChecked = config.vibrateOnReminder
- settings_vibrate_holder.setOnClickListener {
- settings_vibrate.toggle()
- config.vibrateOnReminder = settings_vibrate.isChecked
- }
- }
-
- private fun setupSnoozeDelay() {
- updateSnoozeText()
- settings_snooze_delay_holder.setOnClickListener {
- SnoozePickerDialog(this, config.snoozeDelay) {
- config.snoozeDelay = it
- updateSnoozeText()
- }
- }
- }
-
- private fun updateSnoozeText() {
- settings_snooze_delay.text = res.getQuantityString(R.plurals.by_minutes, config.snoozeDelay, config.snoozeDelay)
- }
-
- private fun setupEventReminder() {
- var reminderMinutes = config.defaultReminderMinutes
- settings_default_reminder.text = getFormattedMinutes(reminderMinutes)
- settings_default_reminder_holder.setOnClickListener {
- showEventReminderDialog(reminderMinutes) {
- config.defaultReminderMinutes = it
- reminderMinutes = it
- settings_default_reminder.text = getFormattedMinutes(it)
- }
- }
- }
-
- private fun getHoursString(hours: Int): String {
- return if (hours < 10) {
- "0$hours:00"
- } else {
- "$hours:00"
- }
- }
-
- private fun setupDisplayPastEvents() {
- var displayPastEvents = config.displayPastEvents
- updatePastEventsText(displayPastEvents)
- settings_display_past_events_holder.setOnClickListener {
- CustomEventReminderDialog(this, displayPastEvents) {
- displayPastEvents = it
- config.displayPastEvents = it
- updatePastEventsText(it)
- }
- }
- }
-
- private fun updatePastEventsText(displayPastEvents: Int) {
- settings_display_past_events.text = getDisplayPastEventsText(displayPastEvents)
- }
-
- private fun getDisplayPastEventsText(displayPastEvents: Int): String {
- return if (displayPastEvents == 0)
- getString(R.string.never)
- else
- getFormattedMinutes(displayPastEvents, false)
- }
-
- private fun setupFontSize() {
- settings_font_size.text = getFontSizeText()
- settings_font_size_holder.setOnClickListener {
- val items = arrayListOf(
- RadioItem(FONT_SIZE_SMALL, res.getString(R.string.small)),
- RadioItem(FONT_SIZE_MEDIUM, res.getString(R.string.medium)),
- RadioItem(FONT_SIZE_LARGE, res.getString(R.string.large)))
-
- RadioGroupDialog(this@SettingsActivity, items, config.fontSize) {
- config.fontSize = it as Int
- settings_font_size.text = getFontSizeText()
- updateWidgets()
- updateListWidget()
- }
- }
- }
-
- private fun getFontSizeText() = getString(when (config.fontSize) {
- FONT_SIZE_SMALL -> R.string.small
- FONT_SIZE_MEDIUM -> R.string.medium
- else -> R.string.large
- })
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (resultCode == RESULT_OK) {
- if (requestCode == GET_RINGTONE_URI) {
- val uri = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
- if (uri == null) {
- config.reminderSound = ""
- } else {
- settings_reminder_sound.text = RingtoneManager.getRingtone(this, uri as Uri)?.getTitle(this)
- config.reminderSound = uri.toString()
- }
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SimpleActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SimpleActivity.kt
deleted file mode 100644
index c66b67731..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SimpleActivity.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.simplemobiletools.calendar.activities
-
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.dialogs.CustomEventReminderDialog
-import com.simplemobiletools.calendar.dialogs.CustomEventRepeatIntervalDialog
-import com.simplemobiletools.calendar.extensions.getFormattedMinutes
-import com.simplemobiletools.calendar.extensions.getRepetitionText
-import com.simplemobiletools.calendar.helpers.DAY
-import com.simplemobiletools.calendar.helpers.MONTH
-import com.simplemobiletools.calendar.helpers.WEEK
-import com.simplemobiletools.calendar.helpers.YEAR
-import com.simplemobiletools.commons.activities.BaseSimpleActivity
-import com.simplemobiletools.commons.dialogs.RadioGroupDialog
-import com.simplemobiletools.commons.extensions.hideKeyboard
-import com.simplemobiletools.commons.models.RadioItem
-import java.util.TreeSet
-import kotlin.collections.ArrayList
-
-open class SimpleActivity : BaseSimpleActivity() {
- protected fun showEventReminderDialog(curMinutes: Int, callback: (minutes: Int) -> Unit) {
- hideKeyboard()
- val minutes = TreeSet()
- minutes.apply {
- add(-1)
- add(0)
- add(10)
- add(30)
- add(curMinutes)
- }
-
- val items = ArrayList(minutes.size + 1)
- minutes.mapIndexedTo(items, { index, value ->
- RadioItem(index, getFormattedMinutes(value), value)
- })
-
- var selectedIndex = 0
- minutes.forEachIndexed { index, value ->
- if (value == curMinutes)
- selectedIndex = index
- }
-
- items.add(RadioItem(-2, getString(R.string.custom)))
-
- RadioGroupDialog(this, items, selectedIndex) {
- if (it == -2) {
- CustomEventReminderDialog(this) {
- callback(it)
- }
- } else {
- callback(it as Int)
- }
- }
- }
-
- protected fun showEventRepeatIntervalDialog(curSeconds: Int, callback: (minutes: Int) -> Unit) {
- hideKeyboard()
- val seconds = TreeSet()
- seconds.apply {
- add(0)
- add(DAY)
- add(WEEK)
- add(MONTH)
- add(YEAR)
- add(curSeconds)
- }
-
- val items = ArrayList(seconds.size + 1)
- seconds.mapIndexedTo(items, { index, value ->
- RadioItem(index, getRepetitionText(value), value)
- })
-
- var selectedIndex = 0
- seconds.forEachIndexed { index, value ->
- if (value == curSeconds)
- selectedIndex = index
- }
-
- items.add(RadioItem(-1, getString(R.string.custom)))
-
- RadioGroupDialog(this, items, selectedIndex) {
- if (it == -1) {
- CustomEventRepeatIntervalDialog(this) {
- callback(it)
- }
- } else {
- callback(it as Int)
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SplashActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SplashActivity.kt
deleted file mode 100644
index 684fda084..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/SplashActivity.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.simplemobiletools.calendar.activities
-
-import android.content.Intent
-import com.simplemobiletools.calendar.helpers.DAY_CODE
-import com.simplemobiletools.calendar.helpers.EVENT_ID
-import com.simplemobiletools.calendar.helpers.EVENT_OCCURRENCE_TS
-import com.simplemobiletools.commons.activities.BaseSplashActivity
-
-class SplashActivity : BaseSplashActivity() {
- override fun initActivity() {
- when {
- intent.extras?.containsKey(DAY_CODE) == true -> Intent(this, MainActivity::class.java).apply {
- putExtra(DAY_CODE, intent.getStringExtra(DAY_CODE))
- startActivity(this)
- }
- intent.extras?.containsKey(EVENT_ID) == true -> Intent(this, MainActivity::class.java).apply {
- putExtra(EVENT_ID, intent.getIntExtra(EVENT_ID, 0))
- putExtra(EVENT_OCCURRENCE_TS, intent.getIntExtra(EVENT_OCCURRENCE_TS, 0))
- startActivity(this)
- }
- else -> startActivity(Intent(this, MainActivity::class.java))
- }
- finish()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/DayEventsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/DayEventsAdapter.kt
deleted file mode 100644
index e8480eacd..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/DayEventsAdapter.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.simplemobiletools.calendar.adapters
-
-import android.view.Menu
-import android.view.View
-import android.view.ViewGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.dialogs.DeleteEventDialog
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.shareEvents
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.interfaces.DeleteEventsListener
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
-import com.simplemobiletools.commons.extensions.applyColorFilter
-import com.simplemobiletools.commons.extensions.beInvisible
-import com.simplemobiletools.commons.extensions.beInvisibleIf
-import com.simplemobiletools.commons.views.MyRecyclerView
-import kotlinx.android.synthetic.main.event_item_day_view.view.*
-
-class DayEventsAdapter(activity: SimpleActivity, val events: ArrayList, val listener: DeleteEventsListener?, recyclerView: MyRecyclerView,
- itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
-
- private var allDayString = resources.getString(R.string.all_day)
- private var replaceDescriptionWithLocation = activity.config.replaceDescription
-
- override fun getActionMenuId() = R.menu.cab_day
-
- override fun prepareActionMode(menu: Menu) {}
-
- override fun prepareItemSelection(view: View) {}
-
- override fun markItemSelection(select: Boolean, view: View?) {
- view?.event_item_frame?.isSelected = select
- }
-
- override fun actionItemPressed(id: Int) {
- when (id) {
- R.id.cab_share -> shareEvents()
- R.id.cab_delete -> askConfirmDelete()
- }
- }
-
- override fun getSelectableItemCount() = events.size
-
- override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int) = createViewHolder(R.layout.event_item_day_view, parent)
-
- override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
- val event = events[position]
- val view = holder.bindView(event) { itemView, layoutPosition ->
- setupView(itemView, event)
- }
- bindViewHolder(holder, position, view)
- }
-
- override fun getItemCount() = events.size
-
- private fun setupView(view: View, event: Event) {
- view.apply {
- event_section_title.text = event.title
- event_item_description.text = if (replaceDescriptionWithLocation) event.location else event.description
- event_item_start.text = if (event.getIsAllDay()) allDayString else Formatter.getTimeFromTS(context, event.startTS)
- event_item_end.beInvisibleIf(event.startTS == event.endTS)
- event_item_color.applyColorFilter(event.color)
-
- if (event.startTS != event.endTS) {
- val startCode = Formatter.getDayCodeFromTS(event.startTS)
- val endCode = Formatter.getDayCodeFromTS(event.endTS)
-
- event_item_end.apply {
- text = Formatter.getTimeFromTS(context, event.endTS)
- if (startCode != endCode) {
- if (event.getIsAllDay()) {
- text = Formatter.getDateFromCode(context, endCode, true)
- } else {
- append(" (${Formatter.getDateFromCode(context, endCode, true)})")
- }
- } else if (event.getIsAllDay()) {
- beInvisible()
- }
- }
- }
-
- event_item_start.setTextColor(textColor)
- event_item_end.setTextColor(textColor)
- event_section_title.setTextColor(textColor)
- event_item_description.setTextColor(textColor)
- }
- }
-
- private fun shareEvents() {
- val eventIds = ArrayList(selectedPositions.size)
- selectedPositions.forEach {
- eventIds.add(events[it].id)
- }
- activity.shareEvents(eventIds.distinct())
- }
-
- private fun askConfirmDelete() {
- val eventIds = ArrayList(selectedPositions.size)
- val timestamps = ArrayList(selectedPositions.size)
- selectedPositions.forEach {
- eventIds.add(events[it].id)
- timestamps.add(events[it].startTS)
- }
-
- DeleteEventDialog(activity, eventIds) {
- val eventsToDelete = ArrayList(selectedPositions.size)
- selectedPositions.sortedDescending().forEach {
- val event = events[it]
- eventsToDelete.add(event)
- }
- events.removeAll(eventsToDelete)
-
- if (it) {
- listener?.deleteItems(eventIds)
- } else {
- listener?.addEventRepeatException(eventIds, timestamps)
- }
- removeSelectedItems()
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/EventListAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/EventListAdapter.kt
deleted file mode 100644
index c54d1f4c4..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/EventListAdapter.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-package com.simplemobiletools.calendar.adapters
-
-import android.view.Menu
-import android.view.View
-import android.view.ViewGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.dialogs.DeleteEventDialog
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.shareEvents
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.interfaces.DeleteEventsListener
-import com.simplemobiletools.calendar.models.ListEvent
-import com.simplemobiletools.calendar.models.ListItem
-import com.simplemobiletools.calendar.models.ListSection
-import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
-import com.simplemobiletools.commons.extensions.applyColorFilter
-import com.simplemobiletools.commons.extensions.beInvisible
-import com.simplemobiletools.commons.extensions.beInvisibleIf
-import com.simplemobiletools.commons.views.MyRecyclerView
-import kotlinx.android.synthetic.main.event_list_item.view.*
-import java.util.*
-
-class EventListAdapter(activity: SimpleActivity, val listItems: ArrayList, val allowLongClick: Boolean, val listener: DeleteEventsListener?,
- recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
-
- private val ITEM_EVENT = 0
- private val ITEM_HEADER = 1
-
- private val topDivider = resources.getDrawable(R.drawable.divider_width)
- private val allDayString = resources.getString(R.string.all_day)
- private val replaceDescriptionWithLocation = activity.config.replaceDescription
- private val redTextColor = resources.getColor(R.color.red_text)
- private val now = (System.currentTimeMillis() / 1000).toInt()
- private val todayDate = Formatter.getDayTitle(activity, Formatter.getDayCodeFromTS(now))
-
- override fun getActionMenuId() = R.menu.cab_event_list
-
- override fun prepareActionMode(menu: Menu) {}
-
- override fun prepareItemSelection(view: View) {}
-
- override fun markItemSelection(select: Boolean, view: View?) {
- view?.event_item_frame?.isSelected = select
- }
-
- override fun actionItemPressed(id: Int) {
- when (id) {
- R.id.cab_share -> shareEvents()
- R.id.cab_delete -> askConfirmDelete()
- }
- }
-
- override fun getSelectableItemCount() = listItems.filter { it is ListEvent }.size
-
- override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MyRecyclerViewAdapter.ViewHolder {
- val layoutId = if (viewType == ITEM_EVENT) R.layout.event_list_item else R.layout.event_list_section
- return createViewHolder(layoutId, parent)
- }
-
- override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
- val listItem = listItems[position]
- val view = holder.bindView(listItem, allowLongClick) { itemView, layoutPosition ->
- if (listItem is ListSection) {
- setupListSection(itemView, listItem, position)
- } else if (listItem is ListEvent) {
- setupListEvent(itemView, listItem)
- }
- }
- bindViewHolder(holder, position, view)
- }
-
- override fun getItemCount() = listItems.size
-
- override fun getItemViewType(position: Int) = if (listItems[position] is ListEvent) ITEM_EVENT else ITEM_HEADER
-
- private fun setupListEvent(view: View, listEvent: ListEvent) {
- view.apply {
- event_section_title.text = listEvent.title
- event_item_description.text = if (replaceDescriptionWithLocation) listEvent.location else listEvent.description
- event_item_start.text = if (listEvent.isAllDay) allDayString else Formatter.getTimeFromTS(context, listEvent.startTS)
- event_item_end.beInvisibleIf(listEvent.startTS == listEvent.endTS)
- event_item_color.applyColorFilter(listEvent.color)
-
- if (listEvent.startTS != listEvent.endTS) {
- val startCode = Formatter.getDayCodeFromTS(listEvent.startTS)
- val endCode = Formatter.getDayCodeFromTS(listEvent.endTS)
-
- event_item_end.apply {
- text = Formatter.getTimeFromTS(context, listEvent.endTS)
- if (startCode != endCode) {
- if (listEvent.isAllDay) {
- text = Formatter.getDateFromCode(context, endCode, true)
- } else {
- append(" (${Formatter.getDateFromCode(context, endCode, true)})")
- }
- } else if (listEvent.isAllDay) {
- beInvisible()
- }
- }
- }
-
- var startTextColor = textColor
- var endTextColor = textColor
- if (listEvent.startTS <= now && listEvent.endTS <= now) {
- if (listEvent.isAllDay) {
- if (Formatter.getDayCodeFromTS(listEvent.startTS) == Formatter.getDayCodeFromTS(now))
- startTextColor = primaryColor
- } else {
- startTextColor = redTextColor
- }
- endTextColor = redTextColor
- } else if (listEvent.startTS <= now && listEvent.endTS >= now) {
- startTextColor = primaryColor
- }
-
- event_item_start.setTextColor(startTextColor)
- event_item_end.setTextColor(endTextColor)
- event_section_title.setTextColor(startTextColor)
- event_item_description.setTextColor(startTextColor)
- }
- }
-
- private fun setupListSection(view: View, listSection: ListSection, position: Int) {
- view.event_section_title.apply {
- text = listSection.title
- setCompoundDrawablesWithIntrinsicBounds(null, if (position == 0) null else topDivider, null, null)
- setTextColor(if (listSection.title == todayDate) primaryColor else textColor)
- }
- }
-
- private fun shareEvents() {
- val eventIds = ArrayList(selectedPositions.size)
- selectedPositions.forEach {
- eventIds.add((listItems[it] as ListEvent).id)
- }
- activity.shareEvents(eventIds.distinct())
- finishActMode()
- }
-
- private fun askConfirmDelete() {
- val eventIds = ArrayList(selectedPositions.size)
- val timestamps = ArrayList(selectedPositions.size)
-
- selectedPositions.forEach {
- eventIds.add((listItems[it] as ListEvent).id)
- timestamps.add((listItems[it] as ListEvent).startTS)
- }
-
- DeleteEventDialog(activity, eventIds) {
- val listItemsToDelete = ArrayList(selectedPositions.size)
- selectedPositions.sortedDescending().forEach {
- val listItem = listItems[it]
- listItemsToDelete.add(listItem)
- }
- listItems.removeAll(listItemsToDelete)
-
- if (it) {
- listener?.deleteItems(eventIds)
- } else {
- listener?.addEventRepeatException(eventIds, timestamps)
- }
- finishActMode()
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/EventListWidgetAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/EventListWidgetAdapter.kt
deleted file mode 100644
index a0203ff18..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/EventListWidgetAdapter.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-package com.simplemobiletools.calendar.adapters
-
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import android.widget.RemoteViews
-import android.widget.RemoteViewsService
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.R.id.event_item_holder
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.extensions.seconds
-import com.simplemobiletools.calendar.helpers.EVENT_ID
-import com.simplemobiletools.calendar.helpers.EVENT_OCCURRENCE_TS
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.models.ListEvent
-import com.simplemobiletools.calendar.models.ListItem
-import com.simplemobiletools.calendar.models.ListSection
-import com.simplemobiletools.commons.extensions.getColoredBitmap
-import com.simplemobiletools.commons.extensions.setText
-import com.simplemobiletools.commons.extensions.setTextSize
-import org.joda.time.DateTime
-import java.util.*
-
-class EventListWidgetAdapter(val context: Context) : RemoteViewsService.RemoteViewsFactory {
- private val ITEM_EVENT = 0
- private val ITEM_HEADER = 1
-
- private val allDayString = context.resources.getString(R.string.all_day)
- private var events = ArrayList()
- private val textColor = context.config.widgetTextColor
- private val replaceDescription = context.config.replaceDescription
- private var mediumFontSize = context.config.getFontSize()
- private var todayDate = ""
-
- override fun getViewAt(position: Int): RemoteViews? {
- val type = getItemViewType(position)
- val remoteView: RemoteViews
-
- if (type == ITEM_EVENT) {
- val item = events[position] as ListEvent
- remoteView = RemoteViews(context.packageName, R.layout.event_list_item_widget).apply {
- setText(R.id.event_section_title, item.title)
- setText(R.id.event_item_description, if (replaceDescription) item.location else item.description)
- setText(R.id.event_item_start, if (item.isAllDay) allDayString else Formatter.getTimeFromTS(context, item.startTS))
- setImageViewBitmap(R.id.event_item_color, context.resources.getColoredBitmap(R.drawable.monthly_event_dot, item.color))
-
- if (item.startTS == item.endTS) {
- setViewVisibility(R.id.event_item_end, View.INVISIBLE)
- } else {
- setViewVisibility(R.id.event_item_end, View.VISIBLE)
- var endString = Formatter.getTimeFromTS(context, item.endTS)
- val startCode = Formatter.getDayCodeFromTS(item.startTS)
- val endCode = Formatter.getDayCodeFromTS(item.endTS)
-
- if (startCode != endCode) {
- if (item.isAllDay) {
- endString = Formatter.getDateFromCode(context, endCode, true)
- } else {
- endString += " (${Formatter.getDateFromCode(context, endCode, true)})"
- }
- } else if (item.isAllDay) {
- setViewVisibility(R.id.event_item_end, View.INVISIBLE)
- }
- setText(R.id.event_item_end, endString)
- }
-
- setTextColor(R.id.event_section_title, textColor)
- setTextColor(R.id.event_item_description, textColor)
- setTextColor(R.id.event_item_start, textColor)
- setTextColor(R.id.event_item_end, textColor)
-
- setTextSize(R.id.event_section_title, mediumFontSize)
- setTextSize(R.id.event_item_description, mediumFontSize)
- setTextSize(R.id.event_item_start, mediumFontSize)
- setTextSize(R.id.event_item_end, mediumFontSize)
-
- Intent().apply {
- putExtra(EVENT_ID, item.id)
- putExtra(EVENT_OCCURRENCE_TS, item.startTS)
- setOnClickFillInIntent(event_item_holder, this)
- }
- }
- } else {
- val item = events[position] as ListSection
- remoteView = RemoteViews(context.packageName, R.layout.event_list_section_widget).apply {
- setTextColor(R.id.event_section_title, textColor)
- setTextSize(R.id.event_section_title, mediumFontSize)
- setText(R.id.event_section_title, item.title)
- }
- }
-
- return remoteView
- }
-
- private fun getItemViewType(position: Int) = if (events[position] is ListEvent) ITEM_EVENT else ITEM_HEADER
-
- override fun getLoadingView() = null
-
- override fun getViewTypeCount() = 2
-
- override fun onCreate() {
- val now = (System.currentTimeMillis() / 1000).toInt()
- val todayCode = Formatter.getDayCodeFromTS(now)
- todayDate = Formatter.getDayTitle(context, todayCode)
- }
-
- override fun getItemId(position: Int) = position.toLong()
-
- override fun onDataSetChanged() {
- mediumFontSize = context.config.getFontSize()
- val fromTS = DateTime().seconds() - context.config.displayPastEvents * 60
- val toTS = DateTime().plusYears(1).seconds()
- context.dbHelper.getEventsInBackground(fromTS, toTS) {
- val listItems = ArrayList(it.size)
- val replaceDescription = context.config.replaceDescription
- val sorted = it.sortedWith(compareBy({ it.startTS }, { it.endTS }, { it.title }, { if (replaceDescription) it.location else it.description }))
- val sublist = sorted.subList(0, Math.min(sorted.size, 100))
- var prevCode = ""
- sublist.forEach {
- val code = Formatter.getDayCodeFromTS(it.startTS)
- if (code != prevCode) {
- val day = Formatter.getDayTitle(context, code)
- if (day != todayDate)
- listItems.add(ListSection(day))
- prevCode = code
- }
- listItems.add(ListEvent(it.id, it.startTS, it.endTS, it.title, it.description, it.getIsAllDay(), it.color, it.location))
- }
-
- this@EventListWidgetAdapter.events = listItems
- }
- }
-
- override fun hasStableIds() = true
-
- override fun getCount() = events.size
-
- override fun onDestroy() {}
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/FilterEventTypeAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/FilterEventTypeAdapter.kt
deleted file mode 100644
index 9aea1cf23..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/FilterEventTypeAdapter.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.simplemobiletools.calendar.adapters
-
-import android.support.v7.widget.RecyclerView
-import android.util.SparseArray
-import android.view.View
-import android.view.ViewGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.extensions.setBackgroundWithStroke
-import com.simplemobiletools.commons.interfaces.MyAdapterListener
-import kotlinx.android.synthetic.main.filter_event_type_view.view.*
-import java.util.*
-
-class FilterEventTypeAdapter(val activity: SimpleActivity, val eventTypes: List, val displayEventTypes: Set) :
- RecyclerView.Adapter() {
- private val itemViews = SparseArray()
- private val selectedPositions = HashSet()
-
- init {
- eventTypes.forEachIndexed { index, eventType ->
- if (displayEventTypes.contains(eventType.id.toString())) {
- selectedPositions.add(index)
- }
- }
- }
-
- private fun toggleItemSelection(select: Boolean, pos: Int) {
- if (select) {
- if (itemViews[pos] != null) {
- selectedPositions.add(pos)
- }
- } else {
- selectedPositions.remove(pos)
- }
-
- itemViews[pos]?.filter_event_type_checkbox?.isChecked = select
- }
-
- private val adapterListener = object : MyAdapterListener {
- override fun toggleItemSelectionAdapter(select: Boolean, position: Int) {
- toggleItemSelection(select, position)
- }
-
- override fun getSelectedPositions() = selectedPositions
-
- override fun itemLongClicked(position: Int) {}
- }
-
- fun getSelectedItemsSet(): HashSet {
- val selectedItemsSet = HashSet(selectedPositions.size)
- selectedPositions.forEach { selectedItemsSet.add(eventTypes[it].id.toString()) }
- return selectedItemsSet
- }
-
- override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
- val view = activity.layoutInflater.inflate(R.layout.filter_event_type_view, parent, false)
- return ViewHolder(view, adapterListener, activity)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val eventType = eventTypes[position]
- itemViews.put(position, holder.bindView(eventType))
- toggleItemSelection(selectedPositions.contains(position), position)
- }
-
- override fun getItemCount() = eventTypes.size
-
- class ViewHolder(view: View, val adapterListener: MyAdapterListener, val activity: SimpleActivity) : RecyclerView.ViewHolder(view) {
- fun bindView(eventType: EventType): View {
- itemView.apply {
- filter_event_type_checkbox.setColors(activity.config.textColor, activity.config.primaryColor, activity.config.backgroundColor)
- filter_event_type_checkbox.text = eventType.getDisplayTitle()
- filter_event_type_color.setBackgroundWithStroke(eventType.color, activity.config.backgroundColor)
- filter_event_type_holder.setOnClickListener { viewClicked(!filter_event_type_checkbox.isChecked) }
- }
-
- return itemView
- }
-
- private fun viewClicked(select: Boolean) {
- adapterListener.toggleItemSelectionAdapter(select, adapterPosition)
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/ManageEventTypesAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/ManageEventTypesAdapter.kt
deleted file mode 100644
index d30aa32c2..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/ManageEventTypesAdapter.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-package com.simplemobiletools.calendar.adapters
-
-import android.view.Menu
-import android.view.View
-import android.view.ViewGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.helpers.DBHelper
-import com.simplemobiletools.calendar.interfaces.DeleteEventTypesListener
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
-import com.simplemobiletools.commons.dialogs.ConfirmationDialog
-import com.simplemobiletools.commons.dialogs.RadioGroupDialog
-import com.simplemobiletools.commons.extensions.setBackgroundWithStroke
-import com.simplemobiletools.commons.extensions.toast
-import com.simplemobiletools.commons.models.RadioItem
-import com.simplemobiletools.commons.views.MyRecyclerView
-import kotlinx.android.synthetic.main.item_event_type.view.*
-import java.util.*
-
-class ManageEventTypesAdapter(activity: SimpleActivity, val eventTypes: ArrayList, val listener: DeleteEventTypesListener?, recyclerView: MyRecyclerView,
- itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
-
- override fun getActionMenuId() = R.menu.cab_event_type
-
- override fun prepareActionMode(menu: Menu) {}
-
- override fun prepareItemSelection(view: View) {}
-
- override fun markItemSelection(select: Boolean, view: View?) {
- view?.event_item_frame?.isSelected = select
- }
-
- override fun actionItemPressed(id: Int) {
- when (id) {
- R.id.cab_delete -> askConfirmDelete()
- }
- }
-
- override fun getSelectableItemCount() = eventTypes.size
-
- override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int) = createViewHolder(R.layout.item_event_type, parent)
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val eventType = eventTypes[position]
- val view = holder.bindView(eventType) { itemView, layoutPosition ->
- setupView(itemView, eventType)
- }
- bindViewHolder(holder, position, view)
- }
-
- override fun getItemCount() = eventTypes.size
-
- private fun setupView(view: View, eventType: EventType) {
- view.apply {
- event_type_title.text = eventType.getDisplayTitle()
- event_type_color.setBackgroundWithStroke(eventType.color, activity.config.backgroundColor)
- event_type_title.setTextColor(textColor)
- }
- }
-
- private fun askConfirmDelete() {
- val eventTypes = ArrayList(selectedPositions.size)
- selectedPositions.forEach { eventTypes.add(this.eventTypes[it]) }
-
- if (activity.dbHelper.doEventTypesContainEvent(eventTypes)) {
- val MOVE_EVENTS = 0
- val DELETE_EVENTS = 1
- val res = activity.resources
- val items = ArrayList().apply {
- add(RadioItem(MOVE_EVENTS, res.getString(R.string.move_events_into_default)))
- add(RadioItem(DELETE_EVENTS, res.getString(R.string.remove_affected_events)))
- }
- RadioGroupDialog(activity, items) {
- deleteEventTypes(it == DELETE_EVENTS)
- }
- } else {
- ConfirmationDialog(activity) {
- deleteEventTypes(true)
- }
- }
- }
-
- private fun deleteEventTypes(deleteEvents: Boolean) {
- val eventTypesToDelete = ArrayList(selectedPositions.size)
-
- for (pos in selectedPositions) {
- if (eventTypes[pos].id == DBHelper.REGULAR_EVENT_TYPE_ID) {
- activity.toast(R.string.cannot_delete_default_type)
- selectedPositions.remove(pos)
- toggleItemSelection(false, pos)
- break
- }
- }
-
- selectedPositions.sortedDescending().forEach {
- val eventType = eventTypes[it]
- eventTypesToDelete.add(eventType)
- }
-
- eventTypes.removeAll(eventTypesToDelete)
- listener?.deleteEventTypes(eventTypesToDelete, deleteEvents)
- removeSelectedItems()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyWeekPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyWeekPagerAdapter.kt
deleted file mode 100644
index 475f55293..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyWeekPagerAdapter.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.simplemobiletools.calendar.adapters
-
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.support.v4.app.FragmentManager
-import android.support.v4.app.FragmentStatePagerAdapter
-import android.util.SparseArray
-import com.simplemobiletools.calendar.fragments.WeekFragment
-import com.simplemobiletools.calendar.helpers.WEEK_START_TIMESTAMP
-
-class MyWeekPagerAdapter(fm: FragmentManager, val mWeekTimestamps: List, val mListener: WeekFragment.WeekScrollListener) : FragmentStatePagerAdapter(fm) {
- private val mFragments = SparseArray()
-
- override fun getCount() = mWeekTimestamps.size
-
- override fun getItem(position: Int): Fragment {
- val bundle = Bundle()
- val weekTimestamp = mWeekTimestamps[position]
- bundle.putInt(WEEK_START_TIMESTAMP, weekTimestamp)
-
- val fragment = WeekFragment()
- fragment.arguments = bundle
- fragment.mListener = mListener
-
- mFragments.put(position, fragment)
- return fragment
- }
-
- fun updateScrollY(pos: Int, y: Int) {
- mFragments[pos - 1]?.updateScrollY(y)
- mFragments[pos + 1]?.updateScrollY(y)
- }
-
- fun refreshEvents(pos: Int) {
- for (i in -1..1) {
- mFragments[pos + i]?.updateEvents()
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/CustomEventReminderDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/CustomEventReminderDialog.kt
deleted file mode 100644
index 3ffc50d1e..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/CustomEventReminderDialog.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.app.Activity
-import android.support.v7.app.AlertDialog
-import android.view.ViewGroup
-import android.view.WindowManager
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.commons.extensions.hideKeyboard
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import com.simplemobiletools.commons.extensions.value
-import kotlinx.android.synthetic.main.dialog_custom_event_reminder.view.*
-
-class CustomEventReminderDialog(val activity: Activity, val selectedMinutes: Int = 0, val callback: (minutes: Int) -> Unit) {
- var dialog: AlertDialog
- var view = (activity.layoutInflater.inflate(R.layout.dialog_custom_event_reminder, null) as ViewGroup).apply {
- when {
- selectedMinutes == 0 -> dialog_radio_view.check(R.id.dialog_radio_minutes)
- selectedMinutes % 1440 == 0 -> {
- dialog_radio_view.check(R.id.dialog_radio_days)
- dialog_custom_reminder_value.setText((selectedMinutes / 1440).toString())
- }
- selectedMinutes % 60 == 0 -> {
- dialog_radio_view.check(R.id.dialog_radio_hours)
- dialog_custom_reminder_value.setText((selectedMinutes / 60).toString())
- }
- else -> {
- dialog_radio_view.check(R.id.dialog_radio_minutes)
- dialog_custom_reminder_value.setText(selectedMinutes.toString())
- }
- }
- }
-
- init {
- dialog = AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, { dialogInterface, i -> confirmReminder() })
- .setNegativeButton(R.string.cancel, null)
- .create().apply {
- window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- activity.setupDialogStuff(view, this)
- }
- }
-
- private fun confirmReminder() {
- val value = view.dialog_custom_reminder_value.value
- val multiplier = getMultiplier(view.dialog_radio_view.checkedRadioButtonId)
- val minutes = Integer.valueOf(if (value.isEmpty()) "0" else value)
- callback(minutes * multiplier)
- activity.hideKeyboard()
- dialog.dismiss()
- }
-
- private fun getMultiplier(id: Int) = when (id) {
- R.id.dialog_radio_hours -> 60
- R.id.dialog_radio_days -> 1440
- else -> 1
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/DeleteEventDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/DeleteEventDialog.kt
deleted file mode 100644
index 9654b4885..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/DeleteEventDialog.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.app.Activity
-import android.support.v7.app.AlertDialog
-import android.view.ViewGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.commons.extensions.beVisibleIf
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import kotlinx.android.synthetic.main.dialog_delete_event.view.*
-
-class DeleteEventDialog(val activity: Activity, eventIds: List, val callback: (allOccurrences: Boolean) -> Unit) {
- val dialog: AlertDialog?
-
- init {
- val events = activity.dbHelper.getEventsWithIds(eventIds)
- val hasRepeatableEvent = events.any { it.repeatInterval > 0 }
-
- val view = activity.layoutInflater.inflate(R.layout.dialog_delete_event, null).apply {
- delete_event_repeat_description.beVisibleIf(hasRepeatableEvent)
- delete_event_radio_view.beVisibleIf(hasRepeatableEvent)
-
- if (eventIds.size > 1) {
- delete_event_repeat_description.text = resources.getString(R.string.selection_contains_repetition)
- }
- }
-
- dialog = AlertDialog.Builder(activity)
- .setPositiveButton(R.string.yes, { dialog, which -> dialogConfirmed(view as ViewGroup, hasRepeatableEvent) })
- .setNegativeButton(R.string.no, null)
- .create().apply {
- activity.setupDialogStuff(view, this)
- }
- }
-
- private fun dialogConfirmed(view: ViewGroup, hasRepeatableEvent: Boolean) {
- val deleteAllOccurrences = !hasRepeatableEvent || view.delete_event_radio_view.checkedRadioButtonId == R.id.delete_event_all
- dialog?.dismiss()
- callback(deleteAllOccurrences)
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/ExportEventsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/ExportEventsDialog.kt
deleted file mode 100644
index dcd920919..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/ExportEventsDialog.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.support.v7.app.AlertDialog
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.adapters.FilterEventTypeAdapter
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.commons.extensions.*
-import kotlinx.android.synthetic.main.dialog_export_events.view.*
-import java.io.File
-
-class ExportEventsDialog(val activity: SimpleActivity, val path: String, val callback: (exportPastEvents: Boolean, file: File, eventTypes: HashSet) -> Unit) {
-
- init {
- val view = (activity.layoutInflater.inflate(R.layout.dialog_export_events, null) as ViewGroup).apply {
- export_events_folder.text = activity.humanizePath(path)
- export_events_filename.setText("events_${System.currentTimeMillis() / 1000}")
-
- activity.dbHelper.getEventTypes {
- val eventTypes = HashSet()
- it.mapTo(eventTypes, { it.id.toString() })
-
- activity.runOnUiThread {
- export_events_types_list.adapter = FilterEventTypeAdapter(activity, it, eventTypes)
- if (it.size > 1) {
- export_events_pick_types.beVisible()
-
- val margin = activity.resources.getDimension(R.dimen.normal_margin).toInt()
- (export_events_checkbox.layoutParams as LinearLayout.LayoutParams).leftMargin = margin
- }
- }
- }
- }
-
- AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, null)
- .setNegativeButton(R.string.cancel, null)
- .create().apply {
- activity.setupDialogStuff(view, this, R.string.export_events) {
- getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
- val filename = view.export_events_filename.value
- when {
- filename.isEmpty() -> activity.toast(R.string.empty_name)
- filename.isAValidFilename() -> {
- val file = File(path, "$filename.ics")
- if (file.exists()) {
- activity.toast(R.string.name_taken)
- return@setOnClickListener
- }
-
- val eventTypes = (view.export_events_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsSet()
- callback(view.export_events_checkbox.isChecked, file, eventTypes)
- dismiss()
- }
- else -> activity.toast(R.string.invalid_name)
- }
- }
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/FilterEventTypesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/FilterEventTypesDialog.kt
deleted file mode 100644
index 71c24a62d..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/FilterEventTypesDialog.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.support.v7.app.AlertDialog
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.adapters.FilterEventTypeAdapter
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import kotlinx.android.synthetic.main.dialog_filter_event_types.view.*
-
-class FilterEventTypesDialog(val activity: SimpleActivity, val callback: () -> Unit) {
- var dialog: AlertDialog
- val view = activity.layoutInflater.inflate(R.layout.dialog_filter_event_types, null)
-
- init {
- val eventTypes = activity.dbHelper.fetchEventTypes()
- val displayEventTypes = activity.config.displayEventTypes
- view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, eventTypes, displayEventTypes)
-
- dialog = AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, { dialogInterface, i -> confirmEventTypes() })
- .setNegativeButton(R.string.cancel, null)
- .create().apply {
- activity.setupDialogStuff(view, this, R.string.filter_events_by_type)
- }
- }
-
- private fun confirmEventTypes() {
- val selectedItems = (view.filter_event_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsSet()
- if (activity.config.displayEventTypes != selectedItems) {
- activity.config.displayEventTypes = selectedItems
- callback()
- }
- dialog.dismiss()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/ImportEventsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/ImportEventsDialog.kt
deleted file mode 100644
index a0411d2cf..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/ImportEventsDialog.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.support.v7.app.AlertDialog
-import android.view.ViewGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.helpers.DBHelper
-import com.simplemobiletools.calendar.helpers.IcsImporter
-import com.simplemobiletools.calendar.helpers.IcsImporter.ImportResult.*
-import com.simplemobiletools.commons.extensions.setBackgroundWithStroke
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import com.simplemobiletools.commons.extensions.toast
-import kotlinx.android.synthetic.main.dialog_import_events.view.*
-
-class ImportEventsDialog(val activity: SimpleActivity, val path: String, val callback: (refreshView: Boolean) -> Unit) {
- var currEventTypeId = DBHelper.REGULAR_EVENT_TYPE_ID
-
- init {
- val view = (activity.layoutInflater.inflate(R.layout.dialog_import_events, null) as ViewGroup).apply {
- updateEventType(this)
- import_event_type_holder.setOnClickListener {
- SelectEventTypeDialog(activity, currEventTypeId) {
- currEventTypeId = it
- updateEventType(this)
- }
- }
- }
-
- AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, null)
- .setNegativeButton(R.string.cancel, null)
- .create().apply {
- activity.setupDialogStuff(view, this, R.string.import_events) {
- getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
- activity.toast(R.string.importing)
- Thread {
- val result = IcsImporter(activity).importEvents(path, currEventTypeId)
- handleParseResult(result)
- dismiss()
- }.start()
- }
- }
- }
- }
-
- private fun updateEventType(view: ViewGroup) {
- val eventType = activity.dbHelper.getEventType(currEventTypeId)
- view.import_event_type_title.text = eventType!!.getDisplayTitle()
- view.import_event_type_color.setBackgroundWithStroke(eventType.color, activity.config.backgroundColor)
- }
-
- private fun handleParseResult(result: IcsImporter.ImportResult) {
- activity.toast(when (result) {
- IMPORT_OK -> R.string.events_imported_successfully
- IMPORT_PARTIAL -> R.string.importing_some_events_failed
- else -> R.string.importing_events_failed
- })
- callback(result != IMPORT_FAIL)
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventTypeColorDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventTypeColorDialog.kt
deleted file mode 100644
index b59387982..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventTypeColorDialog.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.app.Activity
-import android.graphics.Color
-import android.support.v7.app.AlertDialog
-import android.view.ViewGroup
-import android.widget.RadioButton
-import android.widget.RadioGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.helpers.CalDAVHandler
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.extensions.setBackgroundWithStroke
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import kotlinx.android.synthetic.main.dialog_select_radio_group.view.*
-import kotlinx.android.synthetic.main.radio_button_with_color.view.*
-
-class SelectEventTypeColorDialog(val activity: Activity, val eventType: EventType, val callback: (color: Int) -> Unit) {
- private val dialog: AlertDialog?
- private val radioGroup: RadioGroup
- private var wasInit = false
- private val colors = CalDAVHandler(activity).getAvailableCalDAVCalendarColors(eventType)
-
- init {
- val view = activity.layoutInflater.inflate(R.layout.dialog_select_radio_group, null) as ViewGroup
- radioGroup = view.dialog_radio_group
-
- colors.forEachIndexed { index, value ->
- addRadioButton(index, value)
- }
-
- wasInit = true
- dialog = AlertDialog.Builder(activity)
- .create().apply {
- activity.setupDialogStuff(view, this)
- }
- }
-
- private fun addRadioButton(colorKey: Int, color: Int) {
- val view = activity.layoutInflater.inflate(R.layout.radio_button_with_color, null)
- (view.dialog_radio_button as RadioButton).apply {
- text = String.format("#%06X", 0xFFFFFF and color)
- isChecked = color == eventType.color
- id = colorKey
- }
-
- if (color != Color.TRANSPARENT)
- view.dialog_radio_color.setBackgroundWithStroke(color, activity.config.backgroundColor)
-
- view.setOnClickListener { viewClicked(colorKey) }
- radioGroup.addView(view, RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
- }
-
- private fun viewClicked(colorKey: Int) {
- if (!wasInit)
- return
-
- callback(colors[colorKey])
- dialog?.dismiss()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventTypeDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventTypeDialog.kt
deleted file mode 100644
index 2831ee682..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventTypeDialog.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.app.Activity
-import android.graphics.Color
-import android.support.v7.app.AlertDialog
-import android.view.ViewGroup
-import android.widget.RadioButton
-import android.widget.RadioGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.extensions.hideKeyboard
-import com.simplemobiletools.commons.extensions.setBackgroundWithStroke
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import com.simplemobiletools.commons.extensions.updateTextColors
-import kotlinx.android.synthetic.main.dialog_select_radio_group.view.*
-import kotlinx.android.synthetic.main.radio_button_with_color.view.*
-import java.util.*
-
-class SelectEventTypeDialog(val activity: Activity, val currEventType: Int, val callback: (checkedId: Int) -> Unit) {
- private val NEW_TYPE_ID = -2
-
- private val dialog: AlertDialog?
- private val radioGroup: RadioGroup
- private var wasInit = false
- private var eventTypes = ArrayList()
-
- init {
- val view = activity.layoutInflater.inflate(R.layout.dialog_select_radio_group, null) as ViewGroup
- radioGroup = view.dialog_radio_group
-
- activity.dbHelper.getEventTypes {
- eventTypes = it
- activity.runOnUiThread {
- eventTypes.filter { it.caldavCalendarId == 0 }.forEach {
- addRadioButton(it.getDisplayTitle(), it.id, it.color)
- }
- addRadioButton(activity.getString(R.string.add_new_type), NEW_TYPE_ID, Color.TRANSPARENT)
- wasInit = true
- activity.updateTextColors(view.dialog_radio_holder)
- }
- }
-
- dialog = AlertDialog.Builder(activity)
- .create().apply {
- activity.setupDialogStuff(view, this)
- }
- }
-
- private fun addRadioButton(title: String, typeId: Int, color: Int) {
- val view = activity.layoutInflater.inflate(R.layout.radio_button_with_color, null)
- (view.dialog_radio_button as RadioButton).apply {
- text = title
- isChecked = typeId == currEventType
- id = typeId
- }
-
- if (color != Color.TRANSPARENT)
- view.dialog_radio_color.setBackgroundWithStroke(color, activity.config.backgroundColor)
-
- view.setOnClickListener { viewClicked(typeId) }
- radioGroup.addView(view, RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
- }
-
- private fun viewClicked(typeId: Int) {
- if (!wasInit)
- return
-
- if (typeId == NEW_TYPE_ID) {
- UpdateEventTypeDialog(activity) {
- callback(it)
- activity.hideKeyboard()
- dialog?.dismiss()
- }
- } else {
- callback(typeId)
- dialog?.dismiss()
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SnoozePickerDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SnoozePickerDialog.kt
deleted file mode 100644
index fc6c47552..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SnoozePickerDialog.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.support.v7.app.AlertDialog
-import android.view.ViewGroup
-import android.view.WindowManager
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import com.simplemobiletools.commons.extensions.value
-import kotlinx.android.synthetic.main.dialog_snooze_picker.view.*
-
-class SnoozePickerDialog(val activity: SimpleActivity, val minutes: Int, val callback: (newMinutes: Int) -> Unit) {
- init {
- val view = (activity.layoutInflater.inflate(R.layout.dialog_snooze_picker, null) as ViewGroup).apply {
- snooze_picker_label.text = snooze_picker_label.text.toString().capitalize()
- snooze_picker.setText(minutes.toString())
- }
-
- AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, null)
- .setNegativeButton(R.string.cancel, null)
- .create().apply {
- activity.setupDialogStuff(view, this) {
- window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
- val value = view.snooze_picker.value
- val minutes = Integer.valueOf(if (value.isEmpty() || value == "0") "1" else value)
- callback(minutes)
- dismiss()
- }
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/UpdateEventTypeDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/UpdateEventTypeDialog.kt
deleted file mode 100644
index c5bc43ba4..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/UpdateEventTypeDialog.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.simplemobiletools.calendar.dialogs
-
-import android.app.Activity
-import android.support.v7.app.AlertDialog
-import android.view.WindowManager
-import android.widget.ImageView
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.dialogs.ColorPickerDialog
-import com.simplemobiletools.commons.extensions.setBackgroundWithStroke
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import com.simplemobiletools.commons.extensions.toast
-import com.simplemobiletools.commons.extensions.value
-import kotlinx.android.synthetic.main.dialog_event_type.view.*
-
-class UpdateEventTypeDialog(val activity: Activity, var eventType: EventType? = null, val callback: (eventTypeId: Int) -> Unit) {
- var isNewEvent = eventType == null
-
- init {
- if (eventType == null)
- eventType = EventType(0, "", activity.config.primaryColor)
-
- val view = activity.layoutInflater.inflate(R.layout.dialog_event_type, null).apply {
- setupColor(type_color)
- type_title.setText(eventType!!.title)
- type_color.setOnClickListener {
- if (eventType?.caldavCalendarId == 0) {
- ColorPickerDialog(activity, eventType!!.color) {
- eventType!!.color = it
- setupColor(type_color)
- }
- } else {
- SelectEventTypeColorDialog(activity, eventType!!) {
- eventType!!.color = it
- setupColor(type_color)
- }
- }
- }
- }
-
- AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, null)
- .setNegativeButton(R.string.cancel, null)
- .create().apply {
- window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- activity.setupDialogStuff(view, this, if (isNewEvent) R.string.add_new_type else R.string.edit_type) {
- getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
- val title = view.type_title.value
- val eventIdWithTitle = activity.dbHelper.getEventTypeIdWithTitle(title)
- var isEventTypeTitleTaken = isNewEvent && eventIdWithTitle != -1
- if (!isEventTypeTitleTaken)
- isEventTypeTitleTaken = !isNewEvent && eventType!!.id != eventIdWithTitle && eventIdWithTitle != -1
-
- if (title.isEmpty()) {
- activity.toast(R.string.title_empty)
- return@setOnClickListener
- } else if (isEventTypeTitleTaken) {
- activity.toast(R.string.type_already_exists)
- return@setOnClickListener
- }
-
- eventType!!.title = title
- if (eventType!!.caldavCalendarId != 0)
- eventType!!.caldavDisplayName = title
-
- val eventTypeId = if (isNewEvent) {
- activity.dbHelper.insertEventType(eventType!!)
- } else {
- activity.dbHelper.updateEventType(eventType!!)
- }
-
- if (eventTypeId != -1) {
- dismiss()
- callback(eventTypeId)
- } else {
- activity.toast(R.string.editing_calendar_failed)
- }
- }
- }
- }
- }
-
- private fun setupColor(view: ImageView) {
- view.setBackgroundWithStroke(eventType!!.color, activity.config.backgroundColor)
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Activity.kt
deleted file mode 100644
index ce0577fe5..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Activity.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.simplemobiletools.calendar.extensions
-
-import com.simplemobiletools.calendar.BuildConfig
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.helpers.IcsExporter
-import com.simplemobiletools.commons.activities.BaseSimpleActivity
-import com.simplemobiletools.commons.extensions.getFilePublicUri
-import com.simplemobiletools.commons.extensions.shareUri
-import com.simplemobiletools.commons.extensions.toast
-import java.io.File
-
-fun BaseSimpleActivity.shareEvents(ids: List) {
- val file = getTempFile()
- if (file == null) {
- toast(R.string.unknown_error_occurred)
- return
- }
-
- val events = dbHelper.getEventsWithIds(ids)
- IcsExporter().exportEvents(this, file, events) {
- if (it == IcsExporter.ExportResult.EXPORT_OK) {
- val uri = getFilePublicUri(file, BuildConfig.APPLICATION_ID)
- shareUri(uri, BuildConfig.APPLICATION_ID)
- }
- }
-}
-
-fun BaseSimpleActivity.getTempFile(): File? {
- val folder = File(cacheDir, "events")
- if (!folder.exists()) {
- if (!folder.mkdir()) {
- toast(R.string.unknown_error_occurred)
- return null
- }
- }
-
- return File(folder, "events.ics")
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/BufferedWriter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/BufferedWriter.kt
deleted file mode 100644
index 195484320..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/BufferedWriter.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.simplemobiletools.calendar.extensions
-
-import java.io.BufferedWriter
-
-fun BufferedWriter.writeLn(line: String) {
- write(line)
- newLine()
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Context.kt
deleted file mode 100644
index 9bac6aefe..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Context.kt
+++ /dev/null
@@ -1,325 +0,0 @@
-package com.simplemobiletools.calendar.extensions
-
-import android.annotation.SuppressLint
-import android.app.*
-import android.appwidget.AppWidgetManager
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.res.Resources
-import android.net.Uri
-import android.support.v4.app.NotificationCompat
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.EventActivity
-import com.simplemobiletools.calendar.helpers.*
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.models.DayMonthly
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.calendar.receivers.CalDAVSyncReceiver
-import com.simplemobiletools.calendar.receivers.NotificationReceiver
-import com.simplemobiletools.calendar.services.SnoozeService
-import com.simplemobiletools.commons.extensions.*
-import org.joda.time.DateTime
-import org.joda.time.DateTimeZone
-import java.text.SimpleDateFormat
-import java.util.*
-
-val Context.config: Config get() = Config.newInstance(applicationContext)
-
-val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext)
-
-fun Context.updateWidgets() {
- val widgetsCnt = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(ComponentName(applicationContext, MyWidgetMonthlyProvider::class.java))
- if (widgetsCnt.isNotEmpty()) {
- val ids = intArrayOf(R.xml.widget_monthly_info)
- Intent(applicationContext, MyWidgetMonthlyProvider::class.java).apply {
- action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
- putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
- sendBroadcast(this)
- }
- }
-
- updateListWidget()
-}
-
-fun Context.updateListWidget() {
- val widgetsCnt = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(ComponentName(applicationContext, MyWidgetListProvider::class.java))
- if (widgetsCnt.isNotEmpty()) {
- val ids = intArrayOf(R.xml.widget_list_info)
- Intent(applicationContext, MyWidgetListProvider::class.java).apply {
- action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
- putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
- sendBroadcast(this)
- }
- }
-}
-
-fun Context.scheduleAllEvents() {
- val events = dbHelper.getEventsAtReboot()
- events.forEach {
- scheduleNextEventReminder(it, dbHelper)
- }
-}
-
-fun Context.scheduleNextEventReminder(event: Event, dbHelper: DBHelper) {
- if (event.getReminders().isEmpty())
- return
-
- val now = (System.currentTimeMillis() / 1000).toInt()
- val reminderSeconds = event.getReminders().reversed().map { it * 60 }
- dbHelper.getEvents(now, now + YEAR, event.id) {
- if (it.isNotEmpty()) {
- for (curEvent in it) {
- for (curReminder in reminderSeconds) {
- if (curEvent.getEventStartTS() - curReminder > now) {
- scheduleEventIn((curEvent.getEventStartTS() - curReminder) * 1000L, curEvent)
- return@getEvents
- }
- }
- }
- }
- }
-}
-
-fun Context.scheduleEventIn(notifTS: Long, event: Event) {
- if (notifTS < System.currentTimeMillis())
- return
-
- val pendingIntent = getNotificationIntent(applicationContext, event)
- val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
-
- when {
- isMarshmallowPlus() -> alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, notifTS, pendingIntent)
- isKitkatPlus() -> alarmManager.setExact(AlarmManager.RTC_WAKEUP, notifTS, pendingIntent)
- else -> alarmManager.set(AlarmManager.RTC_WAKEUP, notifTS, pendingIntent)
- }
-}
-
-fun Context.cancelNotification(id: Int) {
- val intent = Intent(applicationContext, NotificationReceiver::class.java)
- PendingIntent.getBroadcast(applicationContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT).cancel()
-}
-
-private fun getNotificationIntent(context: Context, event: Event): PendingIntent {
- val intent = Intent(context, NotificationReceiver::class.java)
- intent.putExtra(EVENT_ID, event.id)
- intent.putExtra(EVENT_OCCURRENCE_TS, event.startTS)
- return PendingIntent.getBroadcast(context, event.id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
-}
-
-fun Context.getFormattedMinutes(minutes: Int, showBefore: Boolean = true) = when (minutes) {
- -1 -> getString(R.string.no_reminder)
- 0 -> getString(R.string.at_start)
- else -> {
- if (minutes % 525600 == 0)
- resources.getQuantityString(R.plurals.years, minutes / 525600, minutes / 525600)
-
- when {
- minutes % 43200 == 0 -> resources.getQuantityString(R.plurals.months, minutes / 43200, minutes / 43200)
- minutes % 10080 == 0 -> resources.getQuantityString(R.plurals.weeks, minutes / 10080, minutes / 10080)
- minutes % 1440 == 0 -> resources.getQuantityString(R.plurals.days, minutes / 1440, minutes / 1440)
- minutes % 60 == 0 -> {
- val base = if (showBefore) R.plurals.hours_before else R.plurals.by_hours
- resources.getQuantityString(base, minutes / 60, minutes / 60)
- }
- else -> {
- val base = if (showBefore) R.plurals.minutes_before else R.plurals.by_minutes
- resources.getQuantityString(base, minutes, minutes)
- }
- }
- }
-}
-
-fun Context.getRepetitionText(seconds: Int) = when (seconds) {
- 0 -> getString(R.string.no_repetition)
- DAY -> getString(R.string.daily)
- WEEK -> getString(R.string.weekly)
- MONTH -> getString(R.string.monthly)
- YEAR -> getString(R.string.yearly)
- else -> {
- when {
- seconds % YEAR == 0 -> resources.getQuantityString(R.plurals.years, seconds / YEAR, seconds / YEAR)
- seconds % MONTH == 0 -> resources.getQuantityString(R.plurals.months, seconds / MONTH, seconds / MONTH)
- seconds % WEEK == 0 -> resources.getQuantityString(R.plurals.weeks, seconds / WEEK, seconds / WEEK)
- else -> resources.getQuantityString(R.plurals.days, seconds / DAY, seconds / DAY)
- }
- }
-}
-
-fun Context.getFilteredEvents(events: List): List {
- val displayEventTypes = config.displayEventTypes
- return events.filter { displayEventTypes.contains(it.eventType.toString()) }
-}
-
-fun Context.notifyRunningEvents() {
- dbHelper.getRunningEvents().forEach { notifyEvent(it) }
-}
-
-fun Context.notifyEvent(event: Event) {
- val pendingIntent = getPendingIntent(applicationContext, event)
- val startTime = Formatter.getTimeFromTS(applicationContext, event.startTS)
- val endTime = Formatter.getTimeFromTS(applicationContext, event.endTS)
- val timeRange = if (event.getIsAllDay()) getString(R.string.all_day) else getFormattedEventTime(startTime, endTime)
- val descriptionOrLocation = if (config.replaceDescription) event.location else event.description
- val notification = getNotification(applicationContext, pendingIntent, event, "$timeRange $descriptionOrLocation")
- val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- notificationManager.notify(event.id, notification)
-}
-
-@SuppressLint("NewApi")
-private fun getNotification(context: Context, pendingIntent: PendingIntent, event: Event, content: String): Notification {
- val channelId = "reminder_channel"
- if (context.isOreoPlus()) {
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- val name = context.resources.getString(R.string.event_reminders)
- val importance = NotificationManager.IMPORTANCE_HIGH
- NotificationChannel(channelId, name, importance).apply {
- enableLights(true)
- lightColor = event.color
- enableVibration(false)
- notificationManager.createNotificationChannel(this)
- }
- }
-
- val soundUri = Uri.parse(context.config.reminderSound)
- val builder = NotificationCompat.Builder(context)
- .setContentTitle(event.title)
- .setContentText(content)
- .setSmallIcon(R.drawable.ic_calendar)
- .setContentIntent(pendingIntent)
- .setPriority(Notification.PRIORITY_HIGH)
- .setDefaults(Notification.DEFAULT_LIGHTS)
- .setAutoCancel(true)
- .setSound(soundUri)
- .setChannelId(channelId)
- .addAction(R.drawable.ic_snooze, context.getString(R.string.snooze), getSnoozePendingIntent(context, event))
-
- if (context.isLollipopPlus())
- builder.setVisibility(Notification.VISIBILITY_PUBLIC)
-
- if (context.config.vibrateOnReminder)
- builder.setVibrate(longArrayOf(0, 300, 300, 300))
-
- return builder.build()
-}
-
-private fun getFormattedEventTime(startTime: String, endTime: String) = if (startTime == endTime) startTime else "$startTime - $endTime"
-
-private fun getPendingIntent(context: Context, event: Event): PendingIntent {
- val intent = Intent(context, EventActivity::class.java)
- intent.putExtra(EVENT_ID, event.id)
- intent.putExtra(EVENT_OCCURRENCE_TS, event.startTS)
- return PendingIntent.getActivity(context, event.id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
-}
-
-private fun getSnoozePendingIntent(context: Context, event: Event): PendingIntent {
- val intent = Intent(context, SnoozeService::class.java).setAction("snooze")
- intent.putExtra(EVENT_ID, event.id)
- intent.putExtra(EVENT_OCCURRENCE_TS, event.startTS)
- return PendingIntent.getService(context, event.id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
-}
-
-fun Context.launchNewEventIntent() {
- val code = Formatter.getDayCodeFromDateTime(DateTime(DateTimeZone.getDefault()))
- Intent(applicationContext, EventActivity::class.java).apply {
- putExtra(NEW_EVENT_START_TS, getNewEventTimestampFromCode(code))
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(this)
- }
-}
-
-fun Context.getNewEventTimestampFromCode(dayCode: String): Int {
- val currHour = DateTime(System.currentTimeMillis(), DateTimeZone.getDefault()).hourOfDay
- val dateTime = Formatter.getLocalDateTimeFromCode(dayCode).withHourOfDay(currHour)
- val newDateTime = dateTime.plusHours(1).withMinuteOfHour(0).withSecondOfMinute(0).withMillisOfSecond(0)
- // make sure the date doesn't change
- return newDateTime.withDate(dateTime.year, dateTime.monthOfYear, dateTime.dayOfMonth).seconds()
-}
-
-fun Context.getCurrentOffset() = SimpleDateFormat("Z", Locale.getDefault()).format(Date())
-
-fun Context.getSyncedCalDAVCalendars() = CalDAVHandler(applicationContext).getCalDAVCalendars(null, config.caldavSyncedCalendarIDs)
-
-fun Context.recheckCalDAVCalendars(callback: () -> Unit) {
- if (config.caldavSync) {
- Thread {
- CalDAVHandler(applicationContext).refreshCalendars(null, callback)
- updateWidgets()
- }.start()
- }
-}
-
-fun Context.scheduleCalDAVSync(activate: Boolean) {
- val syncIntent = Intent(applicationContext, CalDAVSyncReceiver::class.java)
- val pendingIntent = PendingIntent.getBroadcast(applicationContext, 0, syncIntent, PendingIntent.FLAG_UPDATE_CURRENT)
- val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
-
- if (activate) {
- val syncCheckInterval = 4 * AlarmManager.INTERVAL_HOUR
- alarm.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + syncCheckInterval, syncCheckInterval, pendingIntent)
- } else {
- alarm.cancel(pendingIntent)
- }
-}
-
-fun Context.addDayNumber(rawTextColor: Int, day: DayMonthly, linearLayout: LinearLayout, dayLabelHeight: Int, callback: (Int) -> Unit) {
- var textColor = rawTextColor
- if (!day.isThisMonth)
- textColor = textColor.adjustAlpha(LOW_ALPHA)
-
- (View.inflate(applicationContext, R.layout.day_monthly_number_view, null) as TextView).apply {
- setTextColor(textColor)
- text = day.value.toString()
- gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
- layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- linearLayout.addView(this)
-
- if (day.isToday) {
- val primaryColor = config.primaryColor
- setTextColor(config.primaryColor.getContrastColor())
- if (dayLabelHeight == 0) {
- onGlobalLayout {
- val height = this@apply.height
- if (height > 0) {
- callback(height)
- addTodaysBackground(this, resources, height, primaryColor)
- }
- }
- } else {
- addTodaysBackground(this, resources, dayLabelHeight, primaryColor)
- }
- }
- }
-}
-
-private fun addTodaysBackground(textView: TextView, res: Resources, dayLabelHeight: Int, mPrimaryColor: Int) =
- textView.addResizedBackgroundDrawable(res, dayLabelHeight, mPrimaryColor, R.drawable.monthly_today_circle)
-
-fun Context.addDayEvents(day: DayMonthly, linearLayout: LinearLayout, res: Resources, dividerMargin: Int) {
- val eventLayoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-
- day.dayEvents.sortedWith(compareBy({ it.startTS }, { it.endTS }, { it.title })).forEach {
- val backgroundDrawable = res.getDrawable(R.drawable.day_monthly_event_background)
- backgroundDrawable.applyColorFilter(it.color)
- eventLayoutParams.setMargins(dividerMargin, 0, dividerMargin, dividerMargin)
-
- var textColor = it.color.getContrastColor()
- if (!day.isThisMonth) {
- backgroundDrawable.alpha = 64
- textColor = textColor.adjustAlpha(0.25f)
- }
-
- (View.inflate(applicationContext, R.layout.day_monthly_event_view, null) as TextView).apply {
- setTextColor(textColor)
- text = it.title.replace(" ", "\u00A0") // allow word break by char
- background = backgroundDrawable
- layoutParams = eventLayoutParams
- linearLayout.addView(this)
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/DateTime.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/DateTime.kt
deleted file mode 100644
index d0110f8cc..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/DateTime.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.simplemobiletools.calendar.extensions
-
-import org.joda.time.DateTime
-
-fun DateTime.seconds() = (millis / 1000).toInt()
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Int.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Int.kt
deleted file mode 100644
index 98f89c0f9..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/Int.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.simplemobiletools.calendar.extensions
-
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.helpers.MONTH
-import com.simplemobiletools.calendar.helpers.WEEK
-import com.simplemobiletools.calendar.models.Event
-
-fun Int.isTsOnProperDay(event: Event): Boolean {
- val dateTime = Formatter.getDateTimeFromTS(this)
- val power = Math.pow(2.0, (dateTime.dayOfWeek - 1).toDouble()).toInt()
- return event.repeatRule and power != 0
-}
-
-fun Int.isXWeeklyRepetition() = this != 0 && this % WEEK == 0
-
-fun Int.isXMonthlyRepetition() = this != 0 && this % MONTH == 0
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/String.kt
deleted file mode 100644
index 41e61aef3..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/String.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.simplemobiletools.calendar.extensions
-
-fun String.substringTo(cnt: Int): String {
- return if (isEmpty()) {
- ""
- } else
- substring(0, Math.min(length, cnt))
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/DayFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/DayFragment.kt
deleted file mode 100644
index d00456c18..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/DayFragment.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-package com.simplemobiletools.calendar.fragments
-
-import android.content.Intent
-import android.content.res.Resources
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.support.v7.app.AlertDialog
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.DatePicker
-import android.widget.RelativeLayout
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.DayActivity
-import com.simplemobiletools.calendar.activities.EventActivity
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.adapters.DayEventsAdapter
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.extensions.getFilteredEvents
-import com.simplemobiletools.calendar.helpers.DAY_CODE
-import com.simplemobiletools.calendar.helpers.EVENT_ID
-import com.simplemobiletools.calendar.helpers.EVENT_OCCURRENCE_TS
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.interfaces.DeleteEventsListener
-import com.simplemobiletools.calendar.interfaces.NavigationListener
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.commons.extensions.applyColorFilter
-import com.simplemobiletools.commons.extensions.getDialogTheme
-import com.simplemobiletools.commons.extensions.setupDialogStuff
-import kotlinx.android.synthetic.main.fragment_day.view.*
-import kotlinx.android.synthetic.main.top_navigation.view.*
-import org.joda.time.DateTime
-import java.util.*
-
-class DayFragment : Fragment(), DeleteEventsListener {
- var mListener: NavigationListener? = null
- private var mTextColor = 0
- private var mDayCode = ""
- private var lastHash = 0
-
- lateinit var mRes: Resources
- lateinit var mHolder: RelativeLayout
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val view = inflater.inflate(R.layout.fragment_day, container, false)
- mRes = resources
- mHolder = view.day_holder
-
- mDayCode = arguments!!.getString(DAY_CODE)
- val day = Formatter.getDayTitle(context!!, mDayCode)
- mHolder.top_value.apply {
- text = day
- setOnClickListener { pickDay() }
- setTextColor(context.config.textColor)
- }
-
- setupButtons()
- return view
- }
-
- override fun onResume() {
- super.onResume()
- checkEvents()
- }
-
- private fun setupButtons() {
- mTextColor = context!!.config.textColor
-
- mHolder.apply {
- top_left_arrow.applyColorFilter(mTextColor)
- top_right_arrow.applyColorFilter(mTextColor)
- top_left_arrow.background = null
- top_right_arrow.background = null
-
- top_left_arrow.setOnClickListener {
- mListener?.goLeft()
- }
-
- top_right_arrow.setOnClickListener {
- mListener?.goRight()
- }
- }
- }
-
- fun getDayEventsAdapter() = mHolder.day_events?.adapter as? DayEventsAdapter
-
- private fun pickDay() {
- activity!!.setTheme(context!!.getDialogTheme())
- val view = layoutInflater.inflate(R.layout.date_picker, null)
- val datePicker = view.findViewById(R.id.date_picker)
-
- val dateTime = Formatter.getDateTimeFromCode(mDayCode)
- datePicker.init(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth, null)
-
- AlertDialog.Builder(context!!)
- .setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.ok) { dialog, which -> positivePressed(dateTime, datePicker) }
- .create().apply {
- activity?.setupDialogStuff(view, this)
- }
- }
-
- private fun positivePressed(dateTime: DateTime, datePicker: DatePicker) {
- val month = datePicker.month + 1
- val year = datePicker.year
- val day = datePicker.dayOfMonth
- val newDateTime = dateTime.withDate(year, month, day)
- mListener?.goToDateTime(newDateTime)
- }
-
- fun checkEvents() {
- val startTS = Formatter.getDayStartTS(mDayCode)
- val endTS = Formatter.getDayEndTS(mDayCode)
- context!!.dbHelper.getEvents(startTS, endTS) {
- receivedEvents(it)
- }
- }
-
- private fun receivedEvents(events: List) {
- val filtered = context?.getFilteredEvents(events) ?: ArrayList()
- val newHash = filtered.hashCode()
- if (newHash == lastHash || !isAdded) {
- return
- }
- lastHash = newHash
-
- val replaceDescription = context!!.config.replaceDescription
- val sorted = ArrayList(filtered.sortedWith(compareBy({ it.startTS }, { it.endTS }, { it.title }, {
- if (replaceDescription) it.location else it.description
- })))
-
- activity?.runOnUiThread {
- updateEvents(sorted)
- }
- }
-
- private fun updateEvents(events: ArrayList) {
- if (activity == null)
- return
-
- DayEventsAdapter(activity as SimpleActivity, events, this, mHolder.day_events) {
- editEvent(it as Event)
- }.apply {
- setupDragListener(true)
- addVerticalDividers(true)
- mHolder.day_events.adapter = this
- }
- }
-
- private fun editEvent(event: Event) {
- Intent(context, EventActivity::class.java).apply {
- putExtra(EVENT_ID, event.id)
- putExtra(EVENT_OCCURRENCE_TS, event.startTS)
- startActivity(this)
- }
- }
-
- override fun deleteItems(ids: ArrayList) {
- val eventIDs = Array(ids.size, { i -> (ids[i].toString()) })
- context!!.dbHelper.deleteEvents(eventIDs, true)
- }
-
- override fun addEventRepeatException(parentIds: ArrayList, timestamps: ArrayList) {
- parentIds.forEachIndexed { index, value ->
- context!!.dbHelper.addEventRepeatException(parentIds[index], timestamps[index])
- }
- (activity as DayActivity).recheckEvents()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/EventListFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/EventListFragment.kt
deleted file mode 100644
index 349931ff8..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/EventListFragment.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-package com.simplemobiletools.calendar.fragments
-
-import android.content.Intent
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.EventActivity
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.adapters.EventListAdapter
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.extensions.getFilteredEvents
-import com.simplemobiletools.calendar.extensions.seconds
-import com.simplemobiletools.calendar.helpers.EVENT_ID
-import com.simplemobiletools.calendar.helpers.EVENT_OCCURRENCE_TS
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.interfaces.DeleteEventsListener
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.calendar.models.ListEvent
-import com.simplemobiletools.calendar.models.ListItem
-import com.simplemobiletools.calendar.models.ListSection
-import com.simplemobiletools.commons.extensions.beGoneIf
-import com.simplemobiletools.commons.extensions.beVisibleIf
-import kotlinx.android.synthetic.main.fragment_event_list.view.*
-import org.joda.time.DateTime
-import java.util.*
-
-class EventListFragment : Fragment(), DeleteEventsListener {
- private var mEvents: List = ArrayList()
- private var prevEventsHash = 0
- private var lastHash = 0
- lateinit var mView: View
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- mView = inflater.inflate(R.layout.fragment_event_list, container, false)
- val placeholderText = String.format(getString(R.string.two_string_placeholder), "${getString(R.string.no_upcoming_events)}\n", getString(R.string.add_some_events))
- mView.calendar_empty_list_placeholder.text = placeholderText
- return mView
- }
-
- override fun onResume() {
- super.onResume()
- checkEvents()
- }
-
- private fun checkEvents() {
- val fromTS = DateTime().seconds() - context!!.config.displayPastEvents * 60
- val toTS = DateTime().plusYears(1).seconds()
- context!!.dbHelper.getEvents(fromTS, toTS) {
- receivedEvents(it)
- }
- }
-
- private fun receivedEvents(events: MutableList) {
- if (context == null || activity == null)
- return
-
- val newHash = events.hashCode()
- if (newHash == lastHash) {
- return
- }
- lastHash = newHash
-
- val filtered = context!!.getFilteredEvents(events)
- val hash = filtered.hashCode()
- if (prevEventsHash == hash)
- return
-
- prevEventsHash = hash
- mEvents = filtered
- val listItems = ArrayList(mEvents.size)
- val replaceDescription = context!!.config.replaceDescription
- val sorted = mEvents.sortedWith(compareBy({ it.startTS }, { it.endTS }, { it.title }, { if (replaceDescription) it.location else it.description }))
- val sublist = sorted.subList(0, Math.min(sorted.size, 100))
- var prevCode = ""
- sublist.forEach {
- val code = Formatter.getDayCodeFromTS(it.startTS)
- if (code != prevCode) {
- val day = Formatter.getDayTitle(context!!, code)
- listItems.add(ListSection(day))
- prevCode = code
- }
- listItems.add(ListEvent(it.id, it.startTS, it.endTS, it.title, it.description, it.getIsAllDay(), it.color, it.location))
- }
-
- val eventsAdapter = EventListAdapter(activity as SimpleActivity, listItems, true, this, mView.calendar_events_list) {
- if (it is ListEvent) {
- editEvent(it)
- }
- }
-
- activity?.runOnUiThread {
- mView.calendar_events_list.apply {
- this@apply.adapter = eventsAdapter
- }
- checkPlaceholderVisibility()
- }
- }
-
- private fun checkPlaceholderVisibility() {
- mView.calendar_empty_list_placeholder.beVisibleIf(mEvents.isEmpty())
- mView.calendar_events_list.beGoneIf(mEvents.isEmpty())
- if (activity != null)
- mView.calendar_empty_list_placeholder.setTextColor(activity!!.config.textColor)
- }
-
- private fun editEvent(event: ListEvent) {
- Intent(context, EventActivity::class.java).apply {
- putExtra(EVENT_ID, event.id)
- putExtra(EVENT_OCCURRENCE_TS, event.startTS)
- startActivity(this)
- }
- }
-
- override fun deleteItems(ids: ArrayList) {
- val eventIDs = Array(ids.size, { i -> (ids[i].toString()) })
- context!!.dbHelper.deleteEvents(eventIDs, true)
- checkEvents()
- }
-
- override fun addEventRepeatException(parentIds: ArrayList, timestamps: ArrayList) {
- parentIds.forEachIndexed { index, value ->
- context!!.dbHelper.addEventRepeatException(value, timestamps[index])
- }
- checkEvents()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/MonthFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/MonthFragment.kt
deleted file mode 100644
index 17cdaf443..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/MonthFragment.kt
+++ /dev/null
@@ -1,201 +0,0 @@
-package com.simplemobiletools.calendar.fragments
-
-import android.content.Context
-import android.content.Intent
-import android.content.res.Resources
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.support.v7.app.AlertDialog
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.DatePicker
-import android.widget.LinearLayout
-import android.widget.RelativeLayout
-import android.widget.TextView
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.DayActivity
-import com.simplemobiletools.calendar.extensions.addDayEvents
-import com.simplemobiletools.calendar.extensions.addDayNumber
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.helpers.*
-import com.simplemobiletools.calendar.interfaces.MonthlyCalendar
-import com.simplemobiletools.calendar.interfaces.NavigationListener
-import com.simplemobiletools.calendar.models.DayMonthly
-import com.simplemobiletools.commons.extensions.*
-import kotlinx.android.synthetic.main.first_row.*
-import kotlinx.android.synthetic.main.fragment_month.view.*
-import kotlinx.android.synthetic.main.top_navigation.view.*
-import org.joda.time.DateTime
-
-class MonthFragment : Fragment(), MonthlyCalendar {
- private var mTextColor = 0
- private var mPrimaryColor = 0
- private var mSundayFirst = false
- private var mDayCode = ""
- private var mPackageName = ""
- private var dayLabelHeight = 0
- private var lastHash = 0L
-
- var listener: NavigationListener? = null
-
- lateinit var mRes: Resources
- lateinit var mHolder: RelativeLayout
- lateinit var mConfig: Config
- lateinit var mCalendar: MonthlyCalendarImpl
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- val view = inflater.inflate(R.layout.fragment_month, container, false)
- mRes = resources
- mPackageName = activity!!.packageName
-
- mHolder = view.calendar_holder
- mDayCode = arguments!!.getString(DAY_CODE)
- mConfig = context!!.config
- mSundayFirst = mConfig.isSundayFirst
-
- setupButtons()
-
- setupLabels()
- mCalendar = MonthlyCalendarImpl(this, context!!)
-
- return view
- }
-
- override fun onResume() {
- super.onResume()
- if (mConfig.isSundayFirst != mSundayFirst) {
- mSundayFirst = mConfig.isSundayFirst
- setupLabels()
- }
-
- mCalendar.apply {
- mTargetDate = Formatter.getDateTimeFromCode(mDayCode)
- getDays(false) // prefill the screen asap, even if without events
- }
- updateCalendar()
- }
-
- fun updateCalendar() {
- mCalendar.updateMonthlyCalendar(Formatter.getDateTimeFromCode(mDayCode))
- }
-
- override fun updateMonthlyCalendar(context: Context, month: String, days: List, checkedEvents: Boolean) {
- val newHash = month.hashCode() + days.hashCode().toLong()
- if ((lastHash != 0L && !checkedEvents) || lastHash == newHash) {
- return
- }
- lastHash = newHash
-
- activity?.runOnUiThread {
- mHolder.top_value.apply {
- text = month
- setTextColor(mConfig.textColor)
- }
- updateDays(days)
- }
- }
-
- private fun setupButtons() {
- val baseColor = mConfig.textColor
- mTextColor = baseColor
- mPrimaryColor = mConfig.primaryColor
-
- mHolder.top_left_arrow.apply {
- applyColorFilter(mTextColor)
- background = null
- setOnClickListener {
- listener?.goLeft()
- }
- }
-
- mHolder.top_right_arrow.apply {
- applyColorFilter(mTextColor)
- background = null
- setOnClickListener {
- listener?.goRight()
- }
- }
-
- mHolder.top_value.setOnClickListener { showMonthDialog() }
- }
-
- private fun showMonthDialog() {
- activity!!.setTheme(context!!.getDialogTheme())
- val view = layoutInflater.inflate(R.layout.date_picker, null)
- val datePicker = view.findViewById(R.id.date_picker)
- datePicker.findViewById(Resources.getSystem().getIdentifier("day", "id", "android")).beGone()
-
- val dateTime = DateTime(mCalendar.mTargetDate.toString())
- datePicker.init(dateTime.year, dateTime.monthOfYear - 1, 1, null)
-
- AlertDialog.Builder(context!!)
- .setNegativeButton(R.string.cancel, null)
- .setPositiveButton(R.string.ok) { dialog, which -> positivePressed(dateTime, datePicker) }
- .create().apply {
- activity?.setupDialogStuff(view, this)
- }
- }
-
- private fun positivePressed(dateTime: DateTime, datePicker: DatePicker) {
- val month = datePicker.month + 1
- val year = datePicker.year
- val newDateTime = dateTime.withDate(year, month, 1)
- listener?.goToDateTime(newDateTime)
- }
-
- private fun setupLabels() {
- val letters = letterIDs
-
- for (i in 0..6) {
- var index = i
- if (!mSundayFirst)
- index = (index + 1) % letters.size
-
- mHolder.findViewById(mRes.getIdentifier("label_$i", "id", mPackageName)).apply {
- setTextColor(mTextColor)
- text = getString(letters[index])
- }
- }
- }
-
- private fun updateDays(days: List) {
- val displayWeekNumbers = mConfig.displayWeekNumbers
- val len = days.size
-
- if (week_num == null)
- return
-
- week_num.setTextColor(mTextColor)
- week_num.beVisibleIf(displayWeekNumbers)
-
- for (i in 0..5) {
- mHolder.findViewById(mRes.getIdentifier("week_num_$i", "id", mPackageName)).apply {
- text = "${days[i * 7 + 3].weekOfYear}:" // fourth day of the week matters
- setTextColor(mTextColor)
- beVisibleIf(displayWeekNumbers)
- }
- }
-
- val dividerMargin = mRes.displayMetrics.density.toInt()
- for (i in 0 until len) {
- mHolder.findViewById(mRes.getIdentifier("day_$i", "id", mPackageName)).apply {
- val day = days[i]
- setOnClickListener { openDay(day.code) }
-
- removeAllViews()
- context.addDayNumber(mTextColor, day, this, dayLabelHeight) { dayLabelHeight = it }
- context.addDayEvents(day, this, mRes, dividerMargin)
- }
- }
- }
-
- private fun openDay(code: String) {
- if (code.isNotEmpty()) {
- Intent(context, DayActivity::class.java).apply {
- putExtra(DAY_CODE, code)
- startActivity(this)
- }
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/WeekFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/WeekFragment.kt
deleted file mode 100644
index d1fa45543..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/WeekFragment.kt
+++ /dev/null
@@ -1,440 +0,0 @@
-package com.simplemobiletools.calendar.fragments
-
-import android.content.Intent
-import android.content.res.Resources
-import android.graphics.Rect
-import android.graphics.drawable.ColorDrawable
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.view.*
-import android.widget.ImageView
-import android.widget.RelativeLayout
-import android.widget.TextView
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.EventActivity
-import com.simplemobiletools.calendar.activities.MainActivity
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.getFilteredEvents
-import com.simplemobiletools.calendar.extensions.seconds
-import com.simplemobiletools.calendar.helpers.*
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.interfaces.WeeklyCalendar
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.calendar.views.MyScrollView
-import com.simplemobiletools.commons.extensions.applyColorFilter
-import com.simplemobiletools.commons.extensions.beGone
-import com.simplemobiletools.commons.extensions.getContrastColor
-import kotlinx.android.synthetic.main.fragment_week.*
-import kotlinx.android.synthetic.main.fragment_week.view.*
-import org.joda.time.DateTime
-import org.joda.time.Days
-import java.util.*
-
-class WeekFragment : Fragment(), WeeklyCalendar {
- private val CLICK_DURATION_THRESHOLD = 150
- private val PLUS_FADEOUT_DELAY = 5000L
-
- var mListener: WeekScrollListener? = null
- private var mWeekTimestamp = 0
- private var mRowHeight = 0
- private var minScrollY = -1
- private var maxScrollY = -1
- private var mWasDestroyed = false
- private var primaryColor = 0
- private var lastHash = 0
- private var isFragmentVisible = false
- private var wasFragmentInit = false
- private var wasExtraHeightAdded = false
- private var clickStartTime = 0L
- private var selectedGrid: View? = null
- private var todayColumnIndex = -1
- private var events = ArrayList()
- private var allDayHolders = ArrayList()
- private var allDayRows = ArrayList>()
-
- lateinit var inflater: LayoutInflater
- lateinit var mView: View
- lateinit var mCalendar: WeeklyCalendarImpl
- lateinit var mRes: Resources
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- this.inflater = inflater
- mRowHeight = (context!!.resources.getDimension(R.dimen.weekly_view_row_height)).toInt()
- minScrollY = mRowHeight * context!!.config.startWeeklyAt
- mWeekTimestamp = arguments!!.getInt(WEEK_START_TIMESTAMP)
- primaryColor = context!!.config.primaryColor
- mRes = resources
- allDayRows.add(HashSet())
-
- mView = inflater.inflate(R.layout.fragment_week, container, false).apply {
- week_events_scrollview.setOnScrollviewListener(object : MyScrollView.ScrollViewListener {
- override fun onScrollChanged(scrollView: MyScrollView, x: Int, y: Int, oldx: Int, oldy: Int) {
- checkScrollLimits(y)
- }
- })
-
- week_events_scrollview.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- week_events_scrollview.viewTreeObserver.removeOnGlobalLayoutListener(this)
- updateScrollY(Math.max(MainActivity.mWeekScrollY, minScrollY))
- }
- })
- }
-
- (0..6).map { inflater.inflate(R.layout.stroke_vertical_divider, mView.week_vertical_grid_holder) }
- (0..23).map { inflater.inflate(R.layout.stroke_horizontal_divider, mView.week_horizontal_grid_holder) }
-
- mCalendar = WeeklyCalendarImpl(this, context!!)
- wasFragmentInit = true
- return mView
- }
-
- override fun setMenuVisibility(menuVisible: Boolean) {
- super.setMenuVisibility(menuVisible)
- isFragmentVisible = menuVisible
- if (isFragmentVisible && wasFragmentInit) {
- (activity as MainActivity).updateHoursTopMargin(mView.week_top_holder.height)
- checkScrollLimits(mView.week_events_scrollview.scrollY)
- }
- }
-
- override fun onPause() {
- super.onPause()
- wasExtraHeightAdded = true
- }
-
- override fun onResume() {
- super.onResume()
- setupDayLabels()
- mCalendar.updateWeeklyCalendar(mWeekTimestamp)
-
- mView.week_events_scrollview.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- if (context == null)
- return
-
- mView.week_events_scrollview.viewTreeObserver.removeOnGlobalLayoutListener(this)
- minScrollY = mRowHeight * context!!.config.startWeeklyAt
- maxScrollY = mRowHeight * context!!.config.endWeeklyAt
-
- val bounds = Rect()
- week_events_holder.getGlobalVisibleRect(bounds)
- maxScrollY -= bounds.bottom - bounds.top
- if (minScrollY > maxScrollY)
- maxScrollY = -1
-
- checkScrollLimits(mView.week_events_scrollview.scrollY)
- }
- })
- }
-
- private fun setupDayLabels() {
- var curDay = Formatter.getDateTimeFromTS(mWeekTimestamp)
- val textColor = context!!.config.textColor
- val todayCode = Formatter.getDayCodeFromDateTime(DateTime())
- for (i in 0..6) {
- val dayCode = Formatter.getDayCodeFromDateTime(curDay)
- val dayLetter = getDayLetter(curDay.dayOfWeek)
- mView.findViewById(mRes.getIdentifier("week_day_label_$i", "id", context!!.packageName)).apply {
- text = "$dayLetter\n${curDay.dayOfMonth}"
- setTextColor(if (todayCode == dayCode) primaryColor else textColor)
- if (todayCode == dayCode)
- todayColumnIndex = i
- }
- curDay = curDay.plusDays(1)
- }
- }
-
- private fun getDayLetter(pos: Int): String {
- return mRes.getString(when (pos) {
- 1 -> R.string.monday_letter
- 2 -> R.string.tuesday_letter
- 3 -> R.string.wednesday_letter
- 4 -> R.string.thursday_letter
- 5 -> R.string.friday_letter
- 6 -> R.string.saturday_letter
- else -> R.string.sunday_letter
- })
- }
-
- private fun checkScrollLimits(y: Int) {
- if (minScrollY != -1 && y < minScrollY) {
- mView.week_events_scrollview.scrollY = minScrollY
- } else if (maxScrollY != -1 && y > maxScrollY) {
- mView.week_events_scrollview.scrollY = maxScrollY
- } else {
- if (isFragmentVisible)
- mListener?.scrollTo(y)
- }
- }
-
- private fun initGrid() {
- (0..6).map { getColumnWithId(it) }
- .forEachIndexed { index, layout ->
- layout.removeAllViews()
- layout.setOnTouchListener { view, motionEvent ->
- checkGridClick(motionEvent, index, layout)
- true
- }
- }
- }
-
- private fun checkGridClick(event: MotionEvent, index: Int, view: ViewGroup) {
- when (event.action) {
- MotionEvent.ACTION_DOWN -> clickStartTime = System.currentTimeMillis()
- MotionEvent.ACTION_UP -> {
- if (System.currentTimeMillis() - clickStartTime < CLICK_DURATION_THRESHOLD) {
- selectedGrid?.animation?.cancel()
- selectedGrid?.beGone()
-
- val rowHeight = resources.getDimension(R.dimen.weekly_view_row_height)
- val hour = (event.y / rowHeight).toInt()
- selectedGrid = (inflater.inflate(R.layout.week_grid_item, null, false) as View).apply {
- view.addView(this)
- background = ColorDrawable(primaryColor)
- layoutParams.width = view.width
- layoutParams.height = rowHeight.toInt()
- y = hour * rowHeight
-
- setOnClickListener {
- val timestamp = mWeekTimestamp + index * DAY_SECONDS + hour * 60 * 60
- Intent(context, EventActivity::class.java).apply {
- putExtra(NEW_EVENT_START_TS, timestamp)
- putExtra(NEW_EVENT_SET_HOUR_DURATION, true)
- startActivity(this)
- }
- }
- animate().alpha(0f).setStartDelay(PLUS_FADEOUT_DELAY).withEndAction {
- beGone()
- }
- }
- }
- }
- else -> {
- }
- }
- }
-
- override fun updateWeeklyCalendar(events: ArrayList) {
- val newHash = events.hashCode()
- if (newHash == lastHash) {
- return
- }
- lastHash = newHash
- this.events = events
- updateEvents()
- }
-
- fun updateEvents() {
- if (mWasDestroyed)
- return
-
- activity!!.runOnUiThread {
- if (context != null && isAdded)
- addEvents()
- }
- }
-
- private fun addEvents() {
- val filtered = context!!.getFilteredEvents(events)
-
- initGrid()
- allDayHolders.clear()
- allDayRows.clear()
- allDayRows.add(HashSet())
- week_all_day_holder?.removeAllViews()
-
- addNewLine()
-
- val fullHeight = mRes.getDimension(R.dimen.weekly_view_events_height)
- val minuteHeight = fullHeight / (24 * 60)
- val minimalHeight = mRes.getDimension(R.dimen.weekly_view_minimal_event_height).toInt()
-
- var hadAllDayEvent = false
- val replaceDescription = context!!.config.replaceDescription
- val sorted = filtered.sortedWith(compareBy({ it.startTS }, { it.endTS }, { it.title }, { if (replaceDescription) it.location else it.description }))
- for (event in sorted) {
- if (event.getIsAllDay() || Formatter.getDayCodeFromTS(event.startTS) != Formatter.getDayCodeFromTS(event.endTS)) {
- hadAllDayEvent = true
- addAllDayEvent(event)
- } else {
- val startDateTime = Formatter.getDateTimeFromTS(event.startTS)
- val endDateTime = Formatter.getDateTimeFromTS(event.endTS)
- val dayOfWeek = startDateTime.plusDays(if (context!!.config.isSundayFirst) 1 else 0).dayOfWeek - 1
- val layout = getColumnWithId(dayOfWeek)
-
- val startMinutes = startDateTime.minuteOfDay
- val duration = endDateTime.minuteOfDay - startMinutes
-
- (inflater.inflate(R.layout.week_event_marker, null, false) as TextView).apply {
- val backgroundColor = MainActivity.eventTypeColors.get(event.eventType, primaryColor)
- background = ColorDrawable(backgroundColor)
- setTextColor(backgroundColor.getContrastColor())
- text = event.title
- layout.addView(this)
- y = startMinutes * minuteHeight
- (layoutParams as RelativeLayout.LayoutParams).apply {
- width = layout.width - 1
- minHeight = if (event.startTS == event.endTS) minimalHeight else (duration * minuteHeight).toInt() - 1
- }
- setOnClickListener {
- Intent(context, EventActivity::class.java).apply {
- putExtra(EVENT_ID, event.id)
- putExtra(EVENT_OCCURRENCE_TS, event.startTS)
- startActivity(this)
- }
- }
- }
- }
- }
-
- if (!hadAllDayEvent) {
- checkTopHolderHeight()
- }
-
- addCurrentTimeIndicator(minuteHeight)
- }
-
- private fun addNewLine() {
- val allDaysLine = inflater.inflate(R.layout.all_day_events_holder_line, null, false) as RelativeLayout
- week_all_day_holder.addView(allDaysLine)
- allDayHolders.add(allDaysLine)
- }
-
- private fun addCurrentTimeIndicator(minuteHeight: Float) {
- if (todayColumnIndex != -1) {
- val minutes = DateTime().minuteOfDay
- val todayColumn = getColumnWithId(todayColumnIndex)
- (inflater.inflate(R.layout.week_now_marker, null, false) as ImageView).apply {
- applyColorFilter(primaryColor)
- mView.week_events_holder.addView(this, 0)
- val extraWidth = (todayColumn.width * 0.3).toInt()
- val markerHeight = resources.getDimension(R.dimen.weekly_view_now_height).toInt()
- (layoutParams as RelativeLayout.LayoutParams).apply {
- width = todayColumn.width + extraWidth
- height = markerHeight
- }
- x = todayColumn.x - extraWidth / 2
- y = minutes * minuteHeight - markerHeight / 2
- }
- }
- }
-
- private fun checkTopHolderHeight() {
- mView.week_top_holder.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- mView.week_top_holder.viewTreeObserver.removeOnGlobalLayoutListener(this)
- if (isFragmentVisible && activity != null) {
- (activity as MainActivity).updateHoursTopMargin(mView.week_top_holder.height)
- }
- }
- })
- }
-
- private fun addAllDayEvent(event: Event) {
- (inflater.inflate(R.layout.week_all_day_event_marker, null, false) as TextView).apply {
- if (activity == null)
- return
-
- val backgroundColor = MainActivity.eventTypeColors.get(event.eventType, primaryColor)
- background = ColorDrawable(backgroundColor)
- setTextColor(backgroundColor.getContrastColor())
- text = event.title
-
- val startDateTime = Formatter.getDateTimeFromTS(event.startTS)
- val endDateTime = Formatter.getDateTimeFromTS(event.endTS)
-
- val minTS = Math.max(startDateTime.seconds(), mWeekTimestamp)
- val maxTS = Math.min(endDateTime.seconds(), mWeekTimestamp + WEEK_SECONDS)
- val startDateTimeInWeek = Formatter.getDateTimeFromTS(minTS)
- val firstDayIndex = (startDateTimeInWeek.dayOfWeek - if (context.config.isSundayFirst) 0 else 1) % 7
- val daysCnt = Days.daysBetween(Formatter.getDateTimeFromTS(minTS).toLocalDate(), Formatter.getDateTimeFromTS(maxTS).toLocalDate()).days
-
- var doesEventFit: Boolean
- val cnt = allDayRows.size - 1
- var wasEventHandled = false
- var drawAtLine = 0
- for (index in 0..cnt) {
- doesEventFit = true
- drawAtLine = index
- val row = allDayRows[index]
- for (i in firstDayIndex..firstDayIndex + daysCnt) {
- if (row.contains(i)) {
- doesEventFit = false
- }
- }
-
- for (dayIndex in firstDayIndex..firstDayIndex + daysCnt) {
- if (doesEventFit) {
- row.add(dayIndex)
- wasEventHandled = true
- } else if (index == cnt) {
- if (allDayRows.size == index + 1) {
- allDayRows.add(HashSet())
- addNewLine()
- drawAtLine++
- wasEventHandled = true
- }
- allDayRows.last().add(dayIndex)
- }
- }
- if (wasEventHandled) {
- break
- }
- }
-
- allDayHolders[drawAtLine].addView(this)
- (layoutParams as RelativeLayout.LayoutParams).apply {
- topMargin = mRes.getDimension(R.dimen.tiny_margin).toInt()
- leftMargin = getColumnWithId(firstDayIndex).x.toInt()
- bottomMargin = 1
- width = getColumnWithId(Math.min(firstDayIndex + daysCnt, 6)).right - leftMargin - 1
- }
-
- calculateExtraHeight()
-
- setOnClickListener {
- Intent(context, EventActivity::class.java).apply {
- putExtra(EVENT_ID, event.id)
- putExtra(EVENT_OCCURRENCE_TS, event.startTS)
- startActivity(this)
- }
- }
- }
- }
-
- private fun calculateExtraHeight() {
- mView.week_top_holder.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- if (activity == null)
- return
-
- mView.week_top_holder.viewTreeObserver.removeOnGlobalLayoutListener(this)
- if (isFragmentVisible) {
- (activity as MainActivity).updateHoursTopMargin(mView.week_top_holder.height)
- }
-
- if (!wasExtraHeightAdded) {
- maxScrollY += mView.week_all_day_holder.height
- wasExtraHeightAdded = true
- }
- }
- })
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- mWasDestroyed = true
- }
-
- private fun getColumnWithId(id: Int) = mView.findViewById(mRes.getIdentifier("week_column_$id", "id", context!!.packageName))
-
- fun updateScrollY(y: Int) {
- if (wasFragmentInit)
- mView.week_events_scrollview.scrollY = y
- }
-
- interface WeekScrollListener {
- fun scrollTo(y: Int)
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/CalDAVHandler.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/CalDAVHandler.kt
deleted file mode 100644
index 53d8660b8..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/CalDAVHandler.kt
+++ /dev/null
@@ -1,406 +0,0 @@
-package com.simplemobiletools.calendar.helpers
-
-import android.content.ContentUris
-import android.content.ContentValues
-import android.content.Context
-import android.database.Cursor
-import android.provider.CalendarContract
-import android.provider.CalendarContract.Reminders
-import android.util.SparseIntArray
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.extensions.scheduleCalDAVSync
-import com.simplemobiletools.calendar.models.CalDAVCalendar
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.extensions.*
-import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALENDAR
-import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALENDAR
-import java.util.*
-import kotlin.collections.ArrayList
-
-class CalDAVHandler(val context: Context) {
- fun refreshCalendars(activity: SimpleActivity? = null, callback: () -> Unit) {
- val dbHelper = context.dbHelper
- for (calendar in getCalDAVCalendars(activity, context.config.caldavSyncedCalendarIDs)) {
- val localEventType = dbHelper.getEventTypeWithCalDAVCalendarId(calendar.id) ?: continue
- localEventType.apply {
- title = calendar.displayName
- caldavDisplayName = calendar.displayName
- caldavEmail = calendar.accountName
- color = calendar.color
- dbHelper.updateLocalEventType(this)
- }
-
- CalDAVHandler(context).fetchCalDAVCalendarEvents(calendar.id, localEventType.id, activity)
- }
- context.scheduleCalDAVSync(true)
- callback()
- }
-
- fun getCalDAVCalendars(activity: SimpleActivity? = null, ids: String = ""): List {
- val calendars = ArrayList()
- if (!context.hasPermission(PERMISSION_WRITE_CALENDAR) || !context.hasPermission(PERMISSION_READ_CALENDAR)) {
- return calendars
- }
-
- val uri = CalendarContract.Calendars.CONTENT_URI
- val projection = arrayOf(
- CalendarContract.Calendars._ID,
- CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
- CalendarContract.Calendars.ACCOUNT_NAME,
- CalendarContract.Calendars.OWNER_ACCOUNT,
- CalendarContract.Calendars.CALENDAR_COLOR,
- CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL)
-
- val selection = if (ids.trim().isNotEmpty()) "${CalendarContract.Calendars._ID} IN ($ids)" else null
- var cursor: Cursor? = null
- try {
- cursor = context.contentResolver.query(uri, projection, selection, null, null)
- if (cursor != null && cursor.moveToFirst()) {
- do {
- val id = cursor.getIntValue(CalendarContract.Calendars._ID)
- val displayName = cursor.getStringValue(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME)
- val accountName = cursor.getStringValue(CalendarContract.Calendars.ACCOUNT_NAME)
- val ownerName = cursor.getStringValue(CalendarContract.Calendars.OWNER_ACCOUNT)
- val color = cursor.getIntValue(CalendarContract.Calendars.CALENDAR_COLOR)
- val accessLevel = cursor.getIntValue(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL)
- val calendar = CalDAVCalendar(id, displayName, accountName, ownerName, color, accessLevel)
- calendars.add(calendar)
- } while (cursor.moveToNext())
- }
- } catch (e: Exception) {
- activity?.showErrorToast(e)
- } finally {
- cursor?.close()
- }
- return calendars
- }
-
- fun updateCalDAVCalendar(eventType: EventType): Boolean {
- val uri = CalendarContract.Calendars.CONTENT_URI
- val values = fillCalendarContentValues(eventType)
- val newUri = ContentUris.withAppendedId(uri, eventType.caldavCalendarId.toLong())
- return try {
- context.contentResolver.update(newUri, values, null, null) == 1
- } catch (e: IllegalArgumentException) {
- false
- }
- }
-
- private fun fillCalendarContentValues(eventType: EventType): ContentValues {
- val colorKey = getEventTypeColorKey(eventType)
- return ContentValues().apply {
- put(CalendarContract.Calendars.CALENDAR_COLOR_KEY, colorKey)
- put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, eventType.title)
- }
- }
-
- private fun getEventTypeColorKey(eventType: EventType): Int {
- val uri = CalendarContract.Colors.CONTENT_URI
- val projection = arrayOf(CalendarContract.Colors.COLOR_KEY)
- val selection = "${CalendarContract.Colors.COLOR_TYPE} = ? AND ${CalendarContract.Colors.COLOR} = ? AND ${CalendarContract.Colors.ACCOUNT_NAME} = ?"
- val selectionArgs = arrayOf(CalendarContract.Colors.TYPE_CALENDAR.toString(), eventType.color.toString(), eventType.caldavEmail)
-
- var cursor: Cursor? = null
- try {
- cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
- if (cursor?.moveToFirst() == true) {
- return cursor.getStringValue(CalendarContract.Colors.COLOR_KEY).toInt()
- }
- } finally {
- cursor?.close()
- }
-
- return -1
- }
-
- // it doesnt work properly, needs better SyncAdapter handling
- private fun insertNewColor(eventType: EventType): Int {
- val maxId = getMaxColorId(eventType) + 1
-
- val values = ContentValues().apply {
- put(CalendarContract.Colors.COLOR_KEY, maxId)
- put(CalendarContract.Colors.COLOR, eventType.color)
- put(CalendarContract.Colors.ACCOUNT_NAME, eventType.caldavEmail)
- put(CalendarContract.Colors.ACCOUNT_TYPE, "com.google")
- put(CalendarContract.Colors.COLOR_TYPE, CalendarContract.Colors.TYPE_CALENDAR)
- }
-
- val uri = CalendarContract.Colors.CONTENT_URI.buildUpon()
- .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
- .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, eventType.caldavEmail)
- .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, "com.google")
- .build()
-
- return if (context.contentResolver.insert(uri, values) != null) {
- maxId
- } else {
- 0
- }
- }
-
- private fun getMaxColorId(eventType: EventType): Int {
- val uri = CalendarContract.Colors.CONTENT_URI
- val projection = arrayOf(CalendarContract.Colors.COLOR_KEY, CalendarContract.Colors.COLOR)
- val selection = "${CalendarContract.Colors.COLOR_TYPE} = ? AND ${CalendarContract.Colors.ACCOUNT_NAME} = ?"
- val selectionArgs = arrayOf(CalendarContract.Colors.TYPE_CALENDAR.toString(), eventType.caldavEmail)
- var maxId = 1
-
- var cursor: Cursor? = null
- try {
- cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
- if (cursor != null && cursor.moveToFirst()) {
- do {
- maxId = Math.max(maxId, cursor.getIntValue(CalendarContract.Colors.COLOR_KEY))
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
-
- return maxId
- }
-
- fun getAvailableCalDAVCalendarColors(eventType: EventType): ArrayList {
- val colors = SparseIntArray()
- val uri = CalendarContract.Colors.CONTENT_URI
- val projection = arrayOf(CalendarContract.Colors.COLOR, CalendarContract.Colors.COLOR_KEY)
- val selection = "${CalendarContract.Colors.COLOR_TYPE} = ? AND ${CalendarContract.Colors.ACCOUNT_NAME} = ?"
- val selectionArgs = arrayOf(CalendarContract.Colors.TYPE_CALENDAR.toString(), eventType.caldavEmail)
-
- var cursor: Cursor? = null
- try {
- cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
- if (cursor != null && cursor.moveToFirst()) {
- do {
- val colorKey = cursor.getIntValue(CalendarContract.Colors.COLOR_KEY)
- val color = cursor.getIntValue(CalendarContract.Colors.COLOR)
- colors.put(colorKey, color)
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
-
- val sortedColors = ArrayList(colors.size())
- (0 until colors.size()).mapTo(sortedColors) { colors[it] }
-
- return sortedColors
- }
-
- private fun fetchCalDAVCalendarEvents(calendarId: Int, eventTypeId: Int, activity: SimpleActivity?) {
- val importIdsMap = HashMap()
- val fetchedEventIds = ArrayList()
- val existingEvents = context.dbHelper.getEventsFromCalDAVCalendar(calendarId)
- existingEvents.forEach {
- importIdsMap.put(it.importId, it)
- }
-
- val uri = CalendarContract.Events.CONTENT_URI
- val projection = arrayOf(
- CalendarContract.Events._ID,
- CalendarContract.Events.TITLE,
- CalendarContract.Events.DESCRIPTION,
- CalendarContract.Events.DTSTART,
- CalendarContract.Events.DTEND,
- CalendarContract.Events.DURATION,
- CalendarContract.Events.ALL_DAY,
- CalendarContract.Events.RRULE,
- CalendarContract.Events.EVENT_LOCATION)
-
- val selection = "${CalendarContract.Events.CALENDAR_ID} = $calendarId"
- var cursor: Cursor? = null
- try {
- cursor = context.contentResolver.query(uri, projection, selection, null, null)
- if (cursor != null && cursor.moveToFirst()) {
- do {
- val id = cursor.getLongValue(CalendarContract.Events._ID)
- val title = cursor.getStringValue(CalendarContract.Events.TITLE) ?: continue
- val description = cursor.getStringValue(CalendarContract.Events.DESCRIPTION) ?: ""
- val startTS = (cursor.getLongValue(CalendarContract.Events.DTSTART) / 1000).toInt()
- var endTS = (cursor.getLongValue(CalendarContract.Events.DTEND) / 1000).toInt()
- val allDay = cursor.getIntValue(CalendarContract.Events.ALL_DAY)
- val rrule = cursor.getStringValue(CalendarContract.Events.RRULE) ?: ""
- val location = cursor.getStringValue(CalendarContract.Events.EVENT_LOCATION) ?: ""
- val reminders = getCalDAVEventReminders(id)
-
- if (endTS == 0) {
- val duration = cursor.getStringValue(CalendarContract.Events.DURATION) ?: ""
- endTS = startTS + Parser().parseDurationSeconds(duration)
- }
-
- val importId = getCalDAVEventImportId(calendarId, id)
- val repeatRule = Parser().parseRepeatInterval(rrule, startTS)
- val event = Event(0, startTS, endTS, title, description, reminders.getOrElse(0, { -1 }),
- reminders.getOrElse(1, { -1 }), reminders.getOrElse(2, { -1 }), repeatRule.repeatInterval,
- importId, allDay, repeatRule.repeatLimit, repeatRule.repeatRule, eventTypeId, source = "$CALDAV-$calendarId",
- location = location)
-
- if (event.getIsAllDay() && endTS > startTS) {
- event.endTS -= DAY
- }
-
- fetchedEventIds.add(importId)
- if (importIdsMap.containsKey(event.importId)) {
- val existingEvent = importIdsMap[importId]
- val originalEventId = existingEvent!!.id
- existingEvent.id = 0
- if (existingEvent.hashCode() != event.hashCode()) {
- event.id = originalEventId
- context.dbHelper.update(event, false) {
- }
- }
- } else {
- context.dbHelper.insert(event, false) {
- importIdsMap.put(event.importId, event)
- }
- }
- } while (cursor.moveToNext())
- }
- } catch (e: Exception) {
- activity?.showErrorToast(e)
- } finally {
- cursor?.close()
- }
-
- val eventIdsToDelete = ArrayList()
- importIdsMap.keys.filter { !fetchedEventIds.contains(it) }.forEach {
- val caldavEventId = it
- existingEvents.forEach {
- if (it.importId == caldavEventId) {
- eventIdsToDelete.add(it.id.toString())
- }
- }
- }
-
- eventIdsToDelete.forEach {
- context.dbHelper.deleteEvents(eventIdsToDelete.toTypedArray(), false)
- }
- }
-
- fun insertCalDAVEvent(event: Event) {
- val uri = CalendarContract.Events.CONTENT_URI
- val values = fillEventContentValues(event)
- val newUri = context.contentResolver.insert(uri, values)
-
- val calendarId = event.getCalDAVCalendarId()
- val eventRemoteID = java.lang.Long.parseLong(newUri.lastPathSegment)
- event.importId = getCalDAVEventImportId(calendarId, eventRemoteID)
-
- setupCalDAVEventReminders(event)
- setupCalDAVEventImportId(event)
- }
-
- fun updateCalDAVEvent(event: Event) {
- val uri = CalendarContract.Events.CONTENT_URI
- val values = fillEventContentValues(event)
- val eventRemoteID = event.getCalDAVEventId()
- event.importId = getCalDAVEventImportId(event.getCalDAVCalendarId(), eventRemoteID)
-
- val newUri = ContentUris.withAppendedId(uri, eventRemoteID)
- context.contentResolver.update(newUri, values, null, null)
-
- setupCalDAVEventReminders(event)
- setupCalDAVEventImportId(event)
- }
-
- private fun setupCalDAVEventReminders(event: Event) {
- clearEventReminders(event)
- event.getReminders().forEach {
- ContentValues().apply {
- put(Reminders.MINUTES, it)
- put(Reminders.EVENT_ID, event.getCalDAVEventId())
- put(Reminders.METHOD, Reminders.METHOD_ALERT)
- context.contentResolver.insert(Reminders.CONTENT_URI, this)
- }
- }
- }
-
- private fun setupCalDAVEventImportId(event: Event) {
- context.dbHelper.updateEventImportIdAndSource(event.id, event.importId, "$CALDAV-${event.getCalDAVCalendarId()}")
- }
-
- private fun fillEventContentValues(event: Event): ContentValues {
- return ContentValues().apply {
- put(CalendarContract.Events.CALENDAR_ID, event.getCalDAVCalendarId())
- put(CalendarContract.Events.TITLE, event.title)
- put(CalendarContract.Events.DESCRIPTION, event.description)
- put(CalendarContract.Events.DTSTART, event.startTS * 1000L)
- put(CalendarContract.Events.ALL_DAY, if (event.getIsAllDay()) 1 else 0)
- put(CalendarContract.Events.RRULE, Parser().getRepeatCode(event))
- put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().toString())
- put(CalendarContract.Events.EVENT_LOCATION, event.location)
-
- if (event.getIsAllDay() && event.endTS > event.startTS)
- event.endTS += DAY
-
- if (event.repeatInterval > 0) {
- put(CalendarContract.Events.DURATION, getDurationCode(event))
- putNull(CalendarContract.Events.DTEND)
- } else {
- put(CalendarContract.Events.DTEND, event.endTS * 1000L)
- putNull(CalendarContract.Events.DURATION)
- }
- }
- }
-
- private fun clearEventReminders(event: Event) {
- val selection = "${Reminders.EVENT_ID} = ?"
- val selectionArgs = arrayOf(event.getCalDAVEventId().toString())
- context.contentResolver.delete(Reminders.CONTENT_URI, selection, selectionArgs)
- }
-
- private fun getDurationCode(event: Event): String {
- return if (event.getIsAllDay()) {
- val dur = Math.max(1, (event.endTS - event.startTS) / DAY)
- "P${dur}D"
- } else {
- Parser().getDurationCode((event.endTS - event.startTS) / 60)
- }
- }
-
- fun deleteCalDAVCalendarEvents(calendarId: Long) {
- val events = context.dbHelper.getCalDAVCalendarEvents(calendarId)
- val eventIds = events.map { it.id.toString() }.toTypedArray()
- context.dbHelper.deleteEvents(eventIds, false)
- }
-
- fun deleteCalDAVEvent(event: Event) {
- val uri = CalendarContract.Events.CONTENT_URI
- val contentUri = ContentUris.withAppendedId(uri, event.getCalDAVEventId())
- try {
- context.contentResolver.delete(contentUri, null, null)
- } catch (ignored: Exception) {
-
- }
- }
-
- private fun getCalDAVEventReminders(eventId: Long): List {
- val reminders = ArrayList()
- val uri = CalendarContract.Reminders.CONTENT_URI
- val projection = arrayOf(
- CalendarContract.Reminders.MINUTES,
- CalendarContract.Reminders.METHOD)
- val selection = "${CalendarContract.Reminders.EVENT_ID} = $eventId"
- var cursor: Cursor? = null
- try {
- cursor = context.contentResolver.query(uri, projection, selection, null, null)
- if (cursor != null && cursor.moveToFirst()) {
- do {
- val minutes = cursor.getIntValue(CalendarContract.Reminders.MINUTES)
- val method = cursor.getIntValue(CalendarContract.Reminders.METHOD)
- if (method == CalendarContract.Reminders.METHOD_ALERT) {
- reminders.add(minutes)
- }
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
- return reminders
- }
-
- private fun getCalDAVEventImportId(calendarId: Int, eventId: Long) = "$CALDAV-$calendarId-$eventId"
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Config.kt
deleted file mode 100644
index 94d087ea6..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Config.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-package com.simplemobiletools.calendar.helpers
-
-import android.content.Context
-import android.media.RingtoneManager
-import android.text.format.DateFormat
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.extensions.scheduleCalDAVSync
-import com.simplemobiletools.commons.helpers.BaseConfig
-import java.util.*
-
-class Config(context: Context) : BaseConfig(context) {
- companion object {
- fun newInstance(context: Context) = Config(context)
- }
-
- var isSundayFirst: Boolean
- get() {
- val isSundayFirst = Calendar.getInstance(Locale.getDefault()).firstDayOfWeek == Calendar.SUNDAY
- return prefs.getBoolean(SUNDAY_FIRST, isSundayFirst)
- }
- set(sundayFirst) = prefs.edit().putBoolean(SUNDAY_FIRST, sundayFirst).apply()
-
- var use24hourFormat: Boolean
- get() {
- val use24hourFormat = DateFormat.is24HourFormat(context)
- return prefs.getBoolean(USE_24_HOUR_FORMAT, use24hourFormat)
- }
- set(use24hourFormat) = prefs.edit().putBoolean(USE_24_HOUR_FORMAT, use24hourFormat).apply()
-
- var displayWeekNumbers: Boolean
- get() = prefs.getBoolean(WEEK_NUMBERS, false)
- set(displayWeekNumbers) = prefs.edit().putBoolean(WEEK_NUMBERS, displayWeekNumbers).apply()
-
- var startWeeklyAt: Int
- get() = prefs.getInt(START_WEEKLY_AT, 7)
- set(startWeeklyAt) = prefs.edit().putInt(START_WEEKLY_AT, startWeeklyAt).apply()
-
- var endWeeklyAt: Int
- get() = prefs.getInt(END_WEEKLY_AT, 23)
- set(endWeeklyAt) = prefs.edit().putInt(END_WEEKLY_AT, endWeeklyAt).apply()
-
- var vibrateOnReminder: Boolean
- get() = prefs.getBoolean(VIBRATE, false)
- set(vibrate) = prefs.edit().putBoolean(VIBRATE, vibrate).apply()
-
- var reminderSound: String
- get() = prefs.getString(REMINDER_SOUND, getDefaultNotificationSound())
- set(path) = prefs.edit().putString(REMINDER_SOUND, path).apply()
-
- var storedView: Int
- get() = prefs.getInt(VIEW, MONTHLY_VIEW)
- set(view) = prefs.edit().putInt(VIEW, view).apply()
-
- var defaultReminderMinutes: Int
- get() = prefs.getInt(REMINDER_MINUTES, 10)
- set(mins) = prefs.edit().putInt(REMINDER_MINUTES, mins).apply()
-
- var snoozeDelay: Int
- get() = prefs.getInt(SNOOZE_DELAY, 10)
- set(snoozeDelay) = prefs.edit().putInt(SNOOZE_DELAY, snoozeDelay).apply()
-
- var displayPastEvents: Int
- get() = prefs.getInt(DISPLAY_PAST_EVENTS, 0)
- set(displayPastEvents) = prefs.edit().putInt(DISPLAY_PAST_EVENTS, displayPastEvents).apply()
-
- var displayEventTypes: Set
- get() = prefs.getStringSet(DISPLAY_EVENT_TYPES, HashSet())
- set(displayEventTypes) = prefs.edit().remove(DISPLAY_EVENT_TYPES).putStringSet(DISPLAY_EVENT_TYPES, displayEventTypes).apply()
-
- var fontSize: Int
- get() = prefs.getInt(FONT_SIZE, FONT_SIZE_MEDIUM)
- set(size) = prefs.edit().putInt(FONT_SIZE, size).apply()
-
- var googleSync: Boolean
- get() = prefs.getBoolean(GOOGLE_SYNC, false)
- set(googleSync) = prefs.edit().putBoolean(GOOGLE_SYNC, googleSync).apply()
-
- var caldavSync: Boolean
- get() = prefs.getBoolean(CALDAV_SYNC, false)
- set(caldavSync) {
- context.scheduleCalDAVSync(caldavSync)
- prefs.edit().putBoolean(CALDAV_SYNC, caldavSync).apply()
- }
-
- var caldavSyncedCalendarIDs: String
- get() = prefs.getString(CALDAV_SYNCED_CALENDAR_IDS, "")
- set(calendarIDs) = prefs.edit().putString(CALDAV_SYNCED_CALENDAR_IDS, calendarIDs).apply()
-
- var lastUsedCaldavCalendar: Int
- get() = prefs.getInt(LAST_USED_CALDAV_CALENDAR, getSyncedCalendarIdsAsList().first().toInt())
- set(calendarId) = prefs.edit().putInt(LAST_USED_CALDAV_CALENDAR, calendarId).apply()
-
- var replaceDescription: Boolean
- get() = prefs.getBoolean(REPLACE_DESCRIPTION, false)
- set(replaceDescription) = prefs.edit().putBoolean(REPLACE_DESCRIPTION, replaceDescription).apply()
-
- fun getSyncedCalendarIdsAsList() = caldavSyncedCalendarIDs.split(",").filter { it.trim().isNotEmpty() } as ArrayList
-
- fun addDisplayEventType(type: String) {
- addDisplayEventTypes(HashSet(Arrays.asList(type)))
- }
-
- private fun addDisplayEventTypes(types: Set) {
- val currDisplayEventTypes = HashSet(displayEventTypes)
- currDisplayEventTypes.addAll(types)
- displayEventTypes = currDisplayEventTypes
- }
-
- fun removeDisplayEventTypes(types: Set) {
- val currDisplayEventTypes = HashSet(displayEventTypes)
- currDisplayEventTypes.removeAll(types)
- displayEventTypes = currDisplayEventTypes
- }
-
- private fun getDefaultNotificationSound(): String {
- return try {
- RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_NOTIFICATION)?.toString() ?: ""
- } catch (e: Exception) {
- ""
- }
- }
-
- fun getFontSize() = when (fontSize) {
- FONT_SIZE_SMALL -> getSmallFontSize()
- FONT_SIZE_MEDIUM -> getMediumFontSize()
- else -> getLargeFontSize()
- }
-
- private fun getSmallFontSize() = getMediumFontSize() - 3f
- private fun getMediumFontSize() = context.resources.getDimension(R.dimen.day_text_size) / context.resources.displayMetrics.density
- private fun getLargeFontSize() = getMediumFontSize() + 3f
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Constants.kt
deleted file mode 100644
index 1ab5147d2..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Constants.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.simplemobiletools.calendar.helpers
-
-import com.simplemobiletools.calendar.R
-
-val LOW_ALPHA = .3f
-val MEDIUM_ALPHA = .6f
-val STORED_LOCALLY_ONLY = 0
-
-val DAY_CODE = "day_code"
-val YEAR_LABEL = "year"
-val EVENT_ID = "event_id"
-val EVENT_OCCURRENCE_TS = "event_occurrence_ts"
-val NEW_EVENT_START_TS = "new_event_start_ts"
-val WEEK_START_TIMESTAMP = "week_start_timestamp"
-val NEW_EVENT_SET_HOUR_DURATION = "new_event_set_hour_duration"
-val CALDAV = "Caldav"
-
-val MONTHLY_VIEW = 1
-val YEARLY_VIEW = 2
-val EVENTS_LIST_VIEW = 3
-val WEEKLY_VIEW = 4
-
-val REMINDER_OFF = -1
-
-val DAY = 86400
-val WEEK = 604800
-val MONTH = 2592001 // exact value not taken into account, Joda is used for adding months and years
-val YEAR = 31536000
-
-val DAY_MINUTES = 24 * 60
-val DAY_SECONDS = 24 * 60 * 60
-val WEEK_SECONDS = 7 * DAY_SECONDS
-
-// Shared Preferences
-val USE_24_HOUR_FORMAT = "use_24_hour_format"
-val SUNDAY_FIRST = "sunday_first"
-val WEEK_NUMBERS = "week_numbers"
-val START_WEEKLY_AT = "start_weekly_at"
-val END_WEEKLY_AT = "end_weekly_at"
-val VIBRATE = "vibrate"
-val REMINDER_SOUND = "reminder_sound"
-val VIEW = "view"
-val REMINDER_MINUTES = "reminder_minutes"
-val DISPLAY_EVENT_TYPES = "display_event_types"
-val FONT_SIZE = "font_size"
-val CALDAV_SYNC = "caldav_sync"
-val CALDAV_SYNCED_CALENDAR_IDS = "caldav_synced_calendar_ids"
-val LAST_USED_CALDAV_CALENDAR = "last_used_caldav_calendar"
-val SNOOZE_DELAY = "snooze_delay"
-val DISPLAY_PAST_EVENTS = "display_past_events"
-val REPLACE_DESCRIPTION = "replace_description"
-val GOOGLE_SYNC = "google_sync" // deprecated
-
-val letterIDs = intArrayOf(R.string.sunday_letter, R.string.monday_letter, R.string.tuesday_letter, R.string.wednesday_letter,
- R.string.thursday_letter, R.string.friday_letter, R.string.saturday_letter)
-
-// repeat_rule for weekly repetition
-val MONDAY = 1
-val TUESDAY = 2
-val WEDNESDAY = 4
-val THURSDAY = 8
-val FRIDAY = 16
-val SATURDAY = 32
-val SUNDAY = 64
-val EVERY_DAY = 127
-
-// repeat_rule for monthly repetition
-val REPEAT_MONTH_SAME_DAY = 1 // ie 25th every month
-val REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST = 2 // ie every xth sunday. 4th if a month has 4 sundays, 5th if 5
-val REPEAT_MONTH_LAST_DAY = 3 // ie every last day of the month
-val REPEAT_MONTH_ORDER_WEEKDAY = 4 // ie every 4th sunday, even if a month has 4 sundays only (will stay 4th even at months with 5)
-
-// special event flags
-val FLAG_ALL_DAY = 1
-
-// constants related to ICS file exporting / importing
-val BEGIN_CALENDAR = "BEGIN:VCALENDAR"
-val END_CALENDAR = "END:VCALENDAR"
-val CALENDAR_PRODID = "PRODID:-//Simple Mobile Tools//NONSGML Event Calendar//EN"
-val CALENDAR_VERSION = "VERSION:2.0"
-val BEGIN_EVENT = "BEGIN:VEVENT"
-val END_EVENT = "END:VEVENT"
-val BEGIN_ALARM = "BEGIN:VALARM"
-val END_ALARM = "END:VALARM"
-val DTSTART = "DTSTART"
-val DTEND = "DTEND"
-val LAST_MODIFIED = "LAST-MODIFIED"
-val DURATION = "DURATION:"
-val SUMMARY = "SUMMARY"
-val DESCRIPTION = "DESCRIPTION:"
-val UID = "UID:"
-val ACTION = "ACTION:"
-val TRIGGER = "TRIGGER:"
-val RRULE = "RRULE:"
-val CATEGORIES = "CATEGORIES:"
-val STATUS = "STATUS:"
-val EXDATE = "EXDATE"
-val BYDAY = "BYDAY"
-val BYMONTHDAY = "BYMONTHDAY"
-val LOCATION = "LOCATION:"
-
-// this tag isn't a standard ICS tag, but there's no official way of adding a category color in an ics file
-val CATEGORY_COLOR = "CATEGORY_COLOR:"
-
-val DISPLAY = "DISPLAY"
-val FREQ = "FREQ"
-val UNTIL = "UNTIL"
-val COUNT = "COUNT"
-val INTERVAL = "INTERVAL"
-val CONFIRMED = "CONFIRMED"
-val VALUE = "VALUE"
-val DATE = "DATE"
-
-val DAILY = "DAILY"
-val WEEKLY = "WEEKLY"
-val MONTHLY = "MONTHLY"
-val YEARLY = "YEARLY"
-
-val MO = "MO"
-val TU = "TU"
-val WE = "WE"
-val TH = "TH"
-val FR = "FR"
-val SA = "SA"
-val SU = "SU"
-
-// font sizes
-val FONT_SIZE_SMALL = 0
-val FONT_SIZE_MEDIUM = 1
-val FONT_SIZE_LARGE = 2
-
-val SOURCE_SIMPLE_CALENDAR = "simple-calendar"
-val SOURCE_IMPORTED_ICS = "imported-ics"
-val SOURCE_CONTACT_BIRTHDAY = "contact-birthday"
-val SOURCE_CONTACT_ANNIVERSARY = "contact-anniversary"
-
-// deprecated
-val SOURCE_GOOGLE_CALENDAR = 1
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/DBHelper.kt
deleted file mode 100644
index aea2060dc..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/DBHelper.kt
+++ /dev/null
@@ -1,960 +0,0 @@
-package com.simplemobiletools.calendar.helpers
-
-import android.content.ContentValues
-import android.content.Context
-import android.database.Cursor
-import android.database.sqlite.SQLiteDatabase
-import android.database.sqlite.SQLiteException
-import android.database.sqlite.SQLiteOpenHelper
-import android.database.sqlite.SQLiteQueryBuilder
-import android.text.TextUtils
-import android.util.SparseIntArray
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.extensions.*
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.extensions.getIntValue
-import com.simplemobiletools.commons.extensions.getLongValue
-import com.simplemobiletools.commons.extensions.getStringValue
-import org.joda.time.DateTime
-import java.util.*
-import kotlin.collections.ArrayList
-
-class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
- private val MAIN_TABLE_NAME = "events"
- private val COL_ID = "id"
- private val COL_START_TS = "start_ts"
- private val COL_END_TS = "end_ts"
- private val COL_TITLE = "title"
- private val COL_DESCRIPTION = "description"
- private val COL_REMINDER_MINUTES = "reminder_minutes"
- private val COL_REMINDER_MINUTES_2 = "reminder_minutes_2"
- private val COL_REMINDER_MINUTES_3 = "reminder_minutes_3"
- private val COL_IMPORT_ID = "import_id"
- private val COL_FLAGS = "flags"
- private val COL_EVENT_TYPE = "event_type"
- private val COL_OFFSET = "offset"
- private val COL_IS_DST_INCLUDED = "is_dst_included"
- private val COL_LAST_UPDATED = "last_updated"
- private val COL_EVENT_SOURCE = "event_source"
- private val COL_LOCATION = "location"
- private val COL_SOURCE = "source" // deprecated
-
- private val META_TABLE_NAME = "events_meta"
- private val COL_EVENT_ID = "event_id"
- private val COL_REPEAT_START = "repeat_start"
- private val COL_REPEAT_INTERVAL = "repeat_interval"
- private val COL_REPEAT_RULE = "repeat_rule"
- private val COL_REPEAT_LIMIT = "repeat_limit"
-
- private val TYPES_TABLE_NAME = "event_types"
- private val COL_TYPE_ID = "event_type_id"
- private val COL_TYPE_TITLE = "event_type_title"
- private val COL_TYPE_COLOR = "event_type_color"
- private val COL_TYPE_CALDAV_CALENDAR_ID = "event_caldav_calendar_id"
- private val COL_TYPE_CALDAV_DISPLAY_NAME = "event_caldav_display_name"
- private val COL_TYPE_CALDAV_EMAIL = "event_caldav_email"
-
- private val EXCEPTIONS_TABLE_NAME = "event_repeat_exceptions"
- private val COL_EXCEPTION_ID = "event_exception_id"
- private val COL_OCCURRENCE_TIMESTAMP = "event_occurrence_timestamp"
- private val COL_OCCURRENCE_DAYCODE = "event_occurrence_daycode"
- private val COL_PARENT_EVENT_ID = "event_parent_id"
- private val COL_CHILD_EVENT_ID = "event_child_id"
-
- private val mDb: SQLiteDatabase = writableDatabase
-
- companion object {
- private val DB_VERSION = 19
- val DB_NAME = "events.db"
- val REGULAR_EVENT_TYPE_ID = 1
- var dbInstance: DBHelper? = null
-
- fun newInstance(context: Context): DBHelper {
- if (dbInstance == null)
- dbInstance = DBHelper(context)
-
- return dbInstance!!
- }
- }
-
- override fun onCreate(db: SQLiteDatabase) {
- db.execSQL("CREATE TABLE $MAIN_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_START_TS INTEGER, $COL_END_TS INTEGER, " +
- "$COL_TITLE TEXT, $COL_DESCRIPTION TEXT, $COL_REMINDER_MINUTES INTEGER, $COL_REMINDER_MINUTES_2 INTEGER, $COL_REMINDER_MINUTES_3 INTEGER, " +
- "$COL_IMPORT_ID TEXT, $COL_FLAGS INTEGER, $COL_EVENT_TYPE INTEGER NOT NULL DEFAULT $REGULAR_EVENT_TYPE_ID, " +
- "$COL_PARENT_EVENT_ID INTEGER, $COL_OFFSET TEXT, $COL_IS_DST_INCLUDED INTEGER, $COL_LAST_UPDATED INTEGER, $COL_EVENT_SOURCE TEXT, " +
- "$COL_LOCATION TEXT)")
-
- createMetaTable(db)
- createTypesTable(db)
- createExceptionsTable(db)
- }
-
- override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
- if (oldVersion == 1) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_REMINDER_MINUTES INTEGER DEFAULT -1")
- }
-
- if (oldVersion < 3) {
- createMetaTable(db)
- }
-
- if (oldVersion < 4) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_IMPORT_ID TEXT DEFAULT ''")
- }
-
- if (oldVersion < 5) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_FLAGS INTEGER NOT NULL DEFAULT 0")
- db.execSQL("ALTER TABLE $META_TABLE_NAME ADD COLUMN $COL_REPEAT_LIMIT INTEGER NOT NULL DEFAULT 0")
- }
-
- if (oldVersion < 6) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_REMINDER_MINUTES_2 INTEGER NOT NULL DEFAULT -1")
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_REMINDER_MINUTES_3 INTEGER NOT NULL DEFAULT -1")
- }
-
- if (oldVersion < 7) {
- createTypesTable(db)
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_EVENT_TYPE INTEGER NOT NULL DEFAULT $REGULAR_EVENT_TYPE_ID")
- }
-
- if (oldVersion < 8) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_PARENT_EVENT_ID INTEGER NOT NULL DEFAULT 0")
- createExceptionsTable(db)
- }
-
- if (oldVersion < 9) {
- try {
- db.execSQL("ALTER TABLE $EXCEPTIONS_TABLE_NAME ADD COLUMN $COL_OCCURRENCE_DAYCODE INTEGER NOT NULL DEFAULT 0")
- } catch (ignored: SQLiteException) {
- }
- convertExceptionTimestampToDaycode(db)
- }
-
- if (oldVersion < 11) {
- db.execSQL("ALTER TABLE $META_TABLE_NAME ADD COLUMN $COL_REPEAT_RULE INTEGER NOT NULL DEFAULT 0")
- setupRepeatRules(db)
- }
-
- if (oldVersion < 12) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_OFFSET TEXT DEFAULT ''")
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_IS_DST_INCLUDED INTEGER NOT NULL DEFAULT 0")
- }
-
- if (oldVersion < 13) {
- try {
- createExceptionsTable(db)
- } catch (e: Exception) {
- try {
- db.execSQL("ALTER TABLE $EXCEPTIONS_TABLE_NAME ADD COLUMN $COL_CHILD_EVENT_ID INTEGER NOT NULL DEFAULT 0")
- } catch (e: Exception) {
-
- }
- }
- }
-
- if (oldVersion < 14) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_LAST_UPDATED INTEGER NOT NULL DEFAULT 0")
- }
-
- if (oldVersion < 15) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_EVENT_SOURCE TEXT DEFAULT ''")
- }
-
- if (oldVersion < 16) {
- db.execSQL("ALTER TABLE $TYPES_TABLE_NAME ADD COLUMN $COL_TYPE_CALDAV_CALENDAR_ID INTEGER NOT NULL DEFAULT 0")
- }
-
- if (oldVersion < 17) {
- db.execSQL("ALTER TABLE $TYPES_TABLE_NAME ADD COLUMN $COL_TYPE_CALDAV_DISPLAY_NAME TEXT DEFAULT ''")
- db.execSQL("ALTER TABLE $TYPES_TABLE_NAME ADD COLUMN $COL_TYPE_CALDAV_EMAIL TEXT DEFAULT ''")
- }
-
- if (oldVersion < 18) {
- updateOldMonthlyEvents(db)
- }
-
- if (oldVersion < 19) {
- db.execSQL("ALTER TABLE $MAIN_TABLE_NAME ADD COLUMN $COL_LOCATION TEXT DEFAULT ''")
- }
- }
-
- private fun createMetaTable(db: SQLiteDatabase) {
- db.execSQL("CREATE TABLE $META_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_EVENT_ID INTEGER UNIQUE, $COL_REPEAT_START INTEGER, " +
- "$COL_REPEAT_INTERVAL INTEGER, $COL_REPEAT_LIMIT INTEGER, $COL_REPEAT_RULE INTEGER)")
- }
-
- private fun createTypesTable(db: SQLiteDatabase) {
- db.execSQL("CREATE TABLE $TYPES_TABLE_NAME ($COL_TYPE_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_TYPE_TITLE TEXT, $COL_TYPE_COLOR INTEGER, " +
- "$COL_TYPE_CALDAV_CALENDAR_ID INTEGER, $COL_TYPE_CALDAV_DISPLAY_NAME TEXT, $COL_TYPE_CALDAV_EMAIL TEXT)")
- addRegularEventType(db)
- }
-
- private fun createExceptionsTable(db: SQLiteDatabase) {
- db.execSQL("CREATE TABLE $EXCEPTIONS_TABLE_NAME ($COL_EXCEPTION_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_PARENT_EVENT_ID INTEGER, " +
- "$COL_OCCURRENCE_TIMESTAMP INTEGER, $COL_OCCURRENCE_DAYCODE INTEGER, $COL_CHILD_EVENT_ID INTEGER)")
- }
-
- private fun addRegularEventType(db: SQLiteDatabase) {
- val regularEvent = context.resources.getString(R.string.regular_event)
- val eventType = EventType(REGULAR_EVENT_TYPE_ID, regularEvent, context.config.primaryColor)
- addEventType(eventType, db)
- }
-
- fun insert(event: Event, addToCalDAV: Boolean, callback: (id: Int) -> Unit) {
- if (event.startTS > event.endTS || event.title.trim().isEmpty()) {
- callback(0)
- return
- }
-
- val eventValues = fillEventValues(event)
- val id = mDb.insert(MAIN_TABLE_NAME, null, eventValues)
- event.id = id.toInt()
-
- if (event.repeatInterval != 0 && event.parentId == 0) {
- val metaValues = fillMetaValues(event)
- mDb.insert(META_TABLE_NAME, null, metaValues)
- }
-
- context.updateWidgets()
- context.scheduleNextEventReminder(event, this)
-
- if (addToCalDAV && event.source != SOURCE_SIMPLE_CALENDAR && context.config.caldavSync) {
- CalDAVHandler(context).insertCalDAVEvent(event)
- }
-
- callback(event.id)
- }
-
- fun update(event: Event, updateAtCalDAV: Boolean, callback: () -> Unit) {
- val selectionArgs = arrayOf(event.id.toString())
- val values = fillEventValues(event)
- val selection = "$COL_ID = ?"
- mDb.update(MAIN_TABLE_NAME, values, selection, selectionArgs)
-
- if (event.repeatInterval == 0) {
- val metaSelection = "$COL_EVENT_ID = ?"
- mDb.delete(META_TABLE_NAME, metaSelection, selectionArgs)
- } else {
- val metaValues = fillMetaValues(event)
- mDb.insertWithOnConflict(META_TABLE_NAME, null, metaValues, SQLiteDatabase.CONFLICT_REPLACE)
- }
-
- context.updateWidgets()
- context.scheduleNextEventReminder(event, this)
- if (updateAtCalDAV && event.source != SOURCE_SIMPLE_CALENDAR && context.config.caldavSync) {
- CalDAVHandler(context).updateCalDAVEvent(event)
- }
- callback()
- }
-
- private fun fillEventValues(event: Event): ContentValues {
- return ContentValues().apply {
- put(COL_START_TS, event.startTS)
- put(COL_END_TS, event.endTS)
- put(COL_TITLE, event.title)
- put(COL_DESCRIPTION, event.description)
- put(COL_REMINDER_MINUTES, event.reminder1Minutes)
- put(COL_REMINDER_MINUTES_2, event.reminder2Minutes)
- put(COL_REMINDER_MINUTES_3, event.reminder3Minutes)
- put(COL_IMPORT_ID, event.importId)
- put(COL_FLAGS, event.flags)
- put(COL_EVENT_TYPE, event.eventType)
- put(COL_PARENT_EVENT_ID, event.parentId)
- put(COL_OFFSET, event.offset)
- put(COL_IS_DST_INCLUDED, if (event.isDstIncluded) 1 else 0)
- put(COL_LAST_UPDATED, event.lastUpdated)
- put(COL_EVENT_SOURCE, event.source)
- put(COL_LOCATION, event.location)
- }
- }
-
- private fun fillMetaValues(event: Event): ContentValues {
- return ContentValues().apply {
- put(COL_EVENT_ID, event.id)
- put(COL_REPEAT_START, event.startTS)
- put(COL_REPEAT_INTERVAL, event.repeatInterval)
- put(COL_REPEAT_LIMIT, event.repeatLimit)
- put(COL_REPEAT_RULE, event.repeatRule)
- }
- }
-
- private fun addEventType(eventType: EventType, db: SQLiteDatabase) {
- insertEventType(eventType, db)
- }
-
- fun insertEventType(eventType: EventType, db: SQLiteDatabase = mDb): Int {
- val values = fillEventTypeValues(eventType)
- val insertedId = db.insert(TYPES_TABLE_NAME, null, values).toInt()
- context.config.addDisplayEventType(insertedId.toString())
- return insertedId
- }
-
- fun updateEventType(eventType: EventType): Int {
- return if (eventType.caldavCalendarId != 0) {
- if (CalDAVHandler(context).updateCalDAVCalendar(eventType)) {
- updateLocalEventType(eventType)
- } else {
- -1
- }
- } else {
- updateLocalEventType(eventType)
- }
- }
-
- fun updateLocalEventType(eventType: EventType): Int {
- val selectionArgs = arrayOf(eventType.id.toString())
- val values = fillEventTypeValues(eventType)
- val selection = "$COL_TYPE_ID = ?"
- return mDb.update(TYPES_TABLE_NAME, values, selection, selectionArgs)
- }
-
- private fun fillEventTypeValues(eventType: EventType): ContentValues {
- return ContentValues().apply {
- put(COL_TYPE_TITLE, eventType.title)
- put(COL_TYPE_COLOR, eventType.color)
- put(COL_TYPE_CALDAV_CALENDAR_ID, eventType.caldavCalendarId)
- put(COL_TYPE_CALDAV_DISPLAY_NAME, eventType.caldavDisplayName)
- put(COL_TYPE_CALDAV_EMAIL, eventType.caldavEmail)
- }
- }
-
- private fun fillExceptionValues(parentEventId: Int, occurrenceTS: Int, callback: (values: ContentValues) -> Unit) {
- val childEvent = getEventWithId(parentEventId)
- if (childEvent == null) {
- callback(ContentValues())
- return
- }
-
- childEvent.apply {
- id = 0
- parentId = parentEventId
- startTS = 0
- endTS = 0
- }
-
- insert(childEvent, false) {
- callback(ContentValues().apply {
- put(COL_PARENT_EVENT_ID, parentEventId)
- put(COL_OCCURRENCE_DAYCODE, Formatter.getDayCodeFromTS(occurrenceTS))
- put(COL_CHILD_EVENT_ID, it)
- })
- }
- }
-
- fun getEventTypeIdWithTitle(title: String): Int {
- val cols = arrayOf(COL_TYPE_ID)
- val selection = "$COL_TYPE_TITLE = ? COLLATE NOCASE"
- val selectionArgs = arrayOf(title)
- var cursor: Cursor? = null
- try {
- cursor = mDb.query(TYPES_TABLE_NAME, cols, selection, selectionArgs, null, null, null)
- if (cursor?.moveToFirst() == true) {
- return cursor.getIntValue(COL_TYPE_ID)
- }
- } finally {
- cursor?.close()
- }
- return -1
- }
-
- fun getEventTypeWithCalDAVCalendarId(calendarId: Int): EventType? {
- val cols = arrayOf(COL_TYPE_ID)
- val selection = "$COL_TYPE_CALDAV_CALENDAR_ID = ?"
- val selectionArgs = arrayOf(calendarId.toString())
- var cursor: Cursor? = null
- try {
- cursor = mDb.query(TYPES_TABLE_NAME, cols, selection, selectionArgs, null, null, null)
- if (cursor?.moveToFirst() == true) {
- return getEventType(cursor.getIntValue(COL_TYPE_ID))
- }
- } finally {
- cursor?.close()
- }
- return null
- }
-
- fun getEventType(id: Int): EventType? {
- val cols = arrayOf(COL_TYPE_TITLE, COL_TYPE_COLOR, COL_TYPE_CALDAV_CALENDAR_ID, COL_TYPE_CALDAV_DISPLAY_NAME, COL_TYPE_CALDAV_EMAIL)
- val selection = "$COL_TYPE_ID = ?"
- val selectionArgs = arrayOf(id.toString())
- var cursor: Cursor? = null
- try {
- cursor = mDb.query(TYPES_TABLE_NAME, cols, selection, selectionArgs, null, null, null)
- if (cursor?.moveToFirst() == true) {
- val title = cursor.getStringValue(COL_TYPE_TITLE)
- val color = cursor.getIntValue(COL_TYPE_COLOR)
- val calendarId = cursor.getIntValue(COL_TYPE_CALDAV_CALENDAR_ID)
- val displayName = cursor.getStringValue(COL_TYPE_CALDAV_DISPLAY_NAME)
- val email = cursor.getStringValue(COL_TYPE_CALDAV_EMAIL)
- return EventType(id, title, color, calendarId, displayName, email)
- }
- } finally {
- cursor?.close()
- }
- return null
- }
-
- fun getBirthdays(): List {
- val selection = "$MAIN_TABLE_NAME.$COL_EVENT_SOURCE = ?"
- val selectionArgs = arrayOf(SOURCE_CONTACT_BIRTHDAY)
- val cursor = getEventsCursor(selection, selectionArgs)
- return fillEvents(cursor)
- }
-
- fun getAnniversaries(): List {
- val selection = "$MAIN_TABLE_NAME.$COL_EVENT_SOURCE = ?"
- val selectionArgs = arrayOf(SOURCE_CONTACT_ANNIVERSARY)
- val cursor = getEventsCursor(selection, selectionArgs)
- return fillEvents(cursor)
- }
-
- fun deleteEvents(ids: Array, deleteFromCalDAV: Boolean) {
- val args = TextUtils.join(", ", ids)
- val selection = "$MAIN_TABLE_NAME.$COL_ID IN ($args)"
- val cursor = getEventsCursor(selection)
- val events = fillEvents(cursor).filter { it.importId.isNotEmpty() }
-
- mDb.delete(MAIN_TABLE_NAME, selection, null)
-
- val metaSelection = "$COL_EVENT_ID IN ($args)"
- mDb.delete(META_TABLE_NAME, metaSelection, null)
-
- val exceptionSelection = "$COL_PARENT_EVENT_ID IN ($args)"
- mDb.delete(EXCEPTIONS_TABLE_NAME, exceptionSelection, null)
-
- context.updateWidgets()
-
- ids.forEach {
- context.cancelNotification(it.toInt())
- }
-
- if (deleteFromCalDAV && context.config.caldavSync) {
- events.forEach {
- CalDAVHandler(context).deleteCalDAVEvent(it)
- }
- }
-
- deleteChildEvents(args, deleteFromCalDAV)
- }
-
- private fun deleteChildEvents(ids: String, deleteFromCalDAV: Boolean) {
- val projection = arrayOf(COL_ID)
- val selection = "$COL_PARENT_EVENT_ID IN ($ids)"
- val childIds = ArrayList()
-
- var cursor: Cursor? = null
- try {
- cursor = mDb.query(MAIN_TABLE_NAME, projection, selection, null, null, null, null)
- if (cursor?.moveToFirst() == true) {
- do {
- childIds.add(cursor.getStringValue(COL_ID))
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
-
- if (childIds.isNotEmpty())
- deleteEvents(childIds.toTypedArray(), deleteFromCalDAV)
- }
-
- fun getCalDAVCalendarEvents(calendarId: Long): List {
- val selection = "$MAIN_TABLE_NAME.$COL_EVENT_SOURCE = (?)"
- val selectionArgs = arrayOf("$CALDAV-$calendarId")
- val cursor = getEventsCursor(selection, selectionArgs)
- return fillEvents(cursor).filter { it.importId.isNotEmpty() }
- }
-
- private fun updateOldMonthlyEvents(db: SQLiteDatabase) {
- val OLD_MONTH = 2592000
- val projection = arrayOf(COL_ID, COL_REPEAT_INTERVAL)
- val selection = "$COL_REPEAT_INTERVAL != 0 AND $COL_REPEAT_INTERVAL % $OLD_MONTH == 0"
- var cursor: Cursor? = null
- try {
- cursor = db.query(META_TABLE_NAME, projection, selection, null, null, null, null)
- if (cursor?.moveToFirst() == true) {
- do {
- val id = cursor.getIntValue(COL_ID)
- val repeatInterval = cursor.getIntValue(COL_REPEAT_INTERVAL)
- val multiplies = repeatInterval / OLD_MONTH
-
- val values = ContentValues().apply {
- put(COL_REPEAT_INTERVAL, multiplies * MONTH)
- }
-
- val updateSelection = "$COL_ID = $id"
- db.update(META_TABLE_NAME, values, updateSelection, null)
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
- }
-
- fun addEventRepeatException(parentEventId: Int, occurrenceTS: Int) {
- fillExceptionValues(parentEventId, occurrenceTS) {
- mDb.insert(EXCEPTIONS_TABLE_NAME, null, it)
- }
- }
-
- fun deleteEventTypes(eventTypes: ArrayList, deleteEvents: Boolean, callback: (deletedCnt: Int) -> Unit) {
- var deleteIds = eventTypes.filter { it.caldavCalendarId == 0 }.map { it.id }
- deleteIds = deleteIds.filter { it != DBHelper.REGULAR_EVENT_TYPE_ID } as ArrayList
-
- val deletedSet = HashSet()
- deleteIds.map { deletedSet.add(it.toString()) }
- context.config.removeDisplayEventTypes(deletedSet)
- if (deleteIds.isEmpty())
- return
-
- for (eventTypeId in deleteIds) {
- if (deleteEvents) {
- deleteEventsWithType(eventTypeId)
- } else {
- resetEventsWithType(eventTypeId)
- }
- }
-
- val args = TextUtils.join(", ", deleteIds)
- val selection = "$COL_TYPE_ID IN ($args)"
- callback(mDb.delete(TYPES_TABLE_NAME, selection, null))
- }
-
- fun deleteEventTypesWithCalendarId(calendarIds: String) {
- val selection = "$COL_TYPE_CALDAV_CALENDAR_ID IN ($calendarIds)"
- mDb.delete(TYPES_TABLE_NAME, selection, null)
- }
-
- private fun deleteEventsWithType(eventTypeId: Int) {
- val selection = "$MAIN_TABLE_NAME.$COL_EVENT_TYPE = ?"
- val selectionArgs = arrayOf(eventTypeId.toString())
- val cursor = getEventsCursor(selection, selectionArgs)
- val events = fillEvents(cursor)
- val eventIDs = Array(events.size, { i -> (events[i].id.toString()) })
- deleteEvents(eventIDs, true)
- }
-
- private fun resetEventsWithType(eventTypeId: Int) {
- val values = ContentValues()
- values.put(COL_EVENT_TYPE, REGULAR_EVENT_TYPE_ID)
-
- val selection = "$COL_EVENT_TYPE = ?"
- val selectionArgs = arrayOf(eventTypeId.toString())
- mDb.update(MAIN_TABLE_NAME, values, selection, selectionArgs)
- }
-
- fun updateEventImportIdAndSource(eventId: Int, importId: String, source: String) {
- val values = ContentValues()
- values.put(COL_IMPORT_ID, importId)
- values.put(COL_EVENT_SOURCE, source)
-
- val selection = "$MAIN_TABLE_NAME.$COL_ID = ?"
- val selectionArgs = arrayOf(eventId.toString())
- mDb.update(MAIN_TABLE_NAME, values, selection, selectionArgs)
- }
-
- fun getEventsWithImportIds() = getEvents("").filter { it.importId.trim().isNotEmpty() } as ArrayList
-
- fun getEventWithId(id: Int): Event? {
- val selection = "$MAIN_TABLE_NAME.$COL_ID = ?"
- val selectionArgs = arrayOf(id.toString())
- val cursor = getEventsCursor(selection, selectionArgs)
- val events = fillEvents(cursor)
- return if (events.isNotEmpty()) {
- events[0]
- } else {
- null
- }
- }
-
- fun getEvents(fromTS: Int, toTS: Int, eventId: Int = -1, callback: (events: MutableList) -> Unit) {
- Thread {
- getEventsInBackground(fromTS, toTS, eventId, callback)
- }.start()
- }
-
- fun getEventsInBackground(fromTS: Int, toTS: Int, eventId: Int = -1, callback: (events: MutableList) -> Unit) {
- val events = ArrayList()
-
- var selection = "$COL_START_TS <= ? AND $COL_END_TS >= ? AND $COL_REPEAT_INTERVAL IS NULL AND $COL_START_TS != 0"
- if (eventId != -1)
- selection += " AND $MAIN_TABLE_NAME.$COL_ID = $eventId"
- val selectionArgs = arrayOf(toTS.toString(), fromTS.toString())
- val cursor = getEventsCursor(selection, selectionArgs)
- events.addAll(fillEvents(cursor))
-
- events.addAll(getRepeatableEventsFor(fromTS, toTS, eventId))
-
- events.addAll(getAllDayEvents(fromTS, toTS, eventId))
-
- val filtered = events.distinct().filterNot { it.ignoreEventOccurrences.contains(Formatter.getDayCodeFromTS(it.startTS).toInt()) } as MutableList
- callback(filtered)
- }
-
- private fun getRepeatableEventsFor(fromTS: Int, toTS: Int, eventId: Int = -1): List {
- val newEvents = ArrayList()
-
- // get repeatable events
- var selection = "$COL_REPEAT_INTERVAL != 0 AND $COL_START_TS <= $toTS AND $COL_START_TS != 0"
- if (eventId != -1)
- selection += " AND $MAIN_TABLE_NAME.$COL_ID = $eventId"
- val events = getEvents(selection)
- val startTimes = SparseIntArray(events.size)
- events.forEach {
- startTimes.put(it.id, it.startTS)
- if (it.repeatLimit >= 0) {
- newEvents.addAll(getEventsRepeatingTillDateOrForever(fromTS, toTS, startTimes, it))
- } else {
- newEvents.addAll(getEventsRepeatingXTimes(fromTS, toTS, startTimes, it))
- }
- }
-
- return newEvents
- }
-
- private fun getEventsRepeatingTillDateOrForever(fromTS: Int, toTS: Int, startTimes: SparseIntArray, event: Event): ArrayList {
- val original = event.copy()
- val events = ArrayList()
- while (event.startTS <= toTS && (event.repeatLimit == 0 || event.repeatLimit >= event.startTS)) {
- if (event.endTS >= fromTS) {
- if (event.repeatInterval.isXWeeklyRepetition()) {
- if (event.startTS.isTsOnProperDay(event)) {
- if (isOnProperWeek(event, startTimes)) {
- events.add(event.copy())
- }
- }
- } else {
- events.add(event.copy())
- }
- }
-
- if (event.getIsAllDay()) {
- if (event.repeatInterval.isXWeeklyRepetition()) {
- if (event.endTS >= toTS && event.startTS.isTsOnProperDay(event)) {
- if (isOnProperWeek(event, startTimes)) {
- events.add(event.copy())
- }
- }
- } else {
- val dayCode = Formatter.getDayCodeFromTS(fromTS)
- val endDayCode = Formatter.getDayCodeFromTS(event.endTS)
- if (dayCode == endDayCode) {
- events.add(event.copy())
- }
- }
- }
- event.addIntervalTime(original)
- }
- return events
- }
-
- private fun getEventsRepeatingXTimes(fromTS: Int, toTS: Int, startTimes: SparseIntArray, event: Event): ArrayList {
- val original = event.copy()
- val events = ArrayList()
- while (event.repeatLimit < 0 && event.startTS <= toTS) {
- if (event.repeatInterval.isXWeeklyRepetition()) {
- if (event.startTS.isTsOnProperDay(event)) {
- if (isOnProperWeek(event, startTimes)) {
- if (event.endTS >= fromTS) {
- events.add(event.copy())
- }
- event.repeatLimit++
- }
- }
- } else {
- if (event.endTS >= fromTS) {
- events.add(event.copy())
- } else if (event.getIsAllDay()) {
- val dayCode = Formatter.getDayCodeFromTS(fromTS)
- val endDayCode = Formatter.getDayCodeFromTS(event.endTS)
- if (dayCode == endDayCode) {
- events.add(event.copy())
- }
- }
- event.repeatLimit++
- }
- event.addIntervalTime(original)
- }
- return events
- }
-
- private fun getAllDayEvents(fromTS: Int, toTS: Int, eventId: Int = -1): List {
- val events = ArrayList()
- var selection = "($COL_FLAGS & $FLAG_ALL_DAY) != 0"
- if (eventId != -1)
- selection += " AND $MAIN_TABLE_NAME.$COL_ID = $eventId"
-
- val dayCode = Formatter.getDayCodeFromTS(fromTS)
- val cursor = getEventsCursor(selection)
- events.addAll(fillEvents(cursor).filter { dayCode == Formatter.getDayCodeFromTS(it.startTS) })
- return events
- }
-
- // check if its the proper week, for events repeating by x weeks
- private fun isOnProperWeek(event: Event, startTimes: SparseIntArray): Boolean {
- val initialWeekOfYear = Formatter.getDateTimeFromTS(startTimes[event.id]).weekOfWeekyear
- val currentWeekOfYear = Formatter.getDateTimeFromTS(event.startTS).weekOfWeekyear
- return (currentWeekOfYear - initialWeekOfYear) % (event.repeatInterval / WEEK) == 0
- }
-
- fun getRunningEvents(): List {
- val events = ArrayList()
- val ts = (System.currentTimeMillis() / 1000).toInt()
-
- val selection = "$COL_START_TS <= ? AND $COL_END_TS >= ? AND $COL_REPEAT_INTERVAL IS 0 AND $COL_START_TS != 0"
- val selectionArgs = arrayOf(ts.toString(), ts.toString())
- val cursor = getEventsCursor(selection, selectionArgs)
- events.addAll(fillEvents(cursor))
-
- events.addAll(getRepeatableEventsFor(ts, ts))
- return events
- }
-
- private fun getEvents(selection: String): List {
- val events = ArrayList()
- var cursor: Cursor? = null
- try {
- cursor = getEventsCursor(selection)
- if (cursor != null) {
- val currEvents = fillEvents(cursor)
- events.addAll(currEvents)
- }
- } finally {
- cursor?.close()
- }
-
- return events
- }
-
- fun getEventsWithIds(ids: List): ArrayList {
- val args = TextUtils.join(", ", ids)
- val selection = "$MAIN_TABLE_NAME.$COL_ID IN ($args)"
- return getEvents(selection) as ArrayList
- }
-
- // get deprecated Google Sync events
- fun getGoogleSyncEvents(): ArrayList {
- val selection = "$MAIN_TABLE_NAME.$COL_SOURCE = $SOURCE_GOOGLE_CALENDAR"
- return getEvents(selection) as ArrayList
- }
-
- fun getEventsAtReboot(): List {
- val selection = "$COL_REMINDER_MINUTES != -1 AND ($COL_START_TS > ? OR $COL_REPEAT_INTERVAL != 0) AND $COL_START_TS != 0"
- val selectionArgs = arrayOf(DateTime.now().seconds().toString())
- val cursor = getEventsCursor(selection, selectionArgs)
- return fillEvents(cursor)
- }
-
- fun getEventsToExport(includePast: Boolean): ArrayList {
- val currTime = (System.currentTimeMillis() / 1000).toString()
- var events = ArrayList()
-
- // non repeating events
- var cursor = if (includePast) {
- getEventsCursor()
- } else {
- val selection = "$COL_END_TS > ?"
- val selectionArgs = arrayOf(currTime)
- getEventsCursor(selection, selectionArgs)
- }
- events.addAll(fillEvents(cursor))
-
- // repeating events
- if (!includePast) {
- val selection = "$COL_REPEAT_INTERVAL != 0 AND ($COL_REPEAT_LIMIT == 0 OR $COL_REPEAT_LIMIT > ?)"
- val selectionArgs = arrayOf(currTime)
- cursor = getEventsCursor(selection, selectionArgs)
- events.addAll(fillEvents(cursor))
- }
-
- events = events.distinctBy { it.id } as ArrayList
- return events
- }
-
- fun getEventsFromCalDAVCalendar(calendarId: Int): List {
- val selection = "$MAIN_TABLE_NAME.$COL_EVENT_SOURCE = ?"
- val selectionArgs = arrayOf("$CALDAV-$calendarId")
- val cursor = getEventsCursor(selection, selectionArgs)
- return fillEvents(cursor)
- }
-
- private fun getEventsCursor(selection: String = "", selectionArgs: Array? = null): Cursor? {
- val builder = SQLiteQueryBuilder()
- builder.tables = "$MAIN_TABLE_NAME LEFT OUTER JOIN $META_TABLE_NAME ON $COL_EVENT_ID = $MAIN_TABLE_NAME.$COL_ID"
- val projection = allColumns
- return builder.query(mDb, projection, selection, selectionArgs, "$MAIN_TABLE_NAME.$COL_ID", null, COL_START_TS)
- }
-
- private val allColumns: Array
- get() = arrayOf("$MAIN_TABLE_NAME.$COL_ID", COL_START_TS, COL_END_TS, COL_TITLE, COL_DESCRIPTION, COL_REMINDER_MINUTES, COL_REMINDER_MINUTES_2,
- COL_REMINDER_MINUTES_3, COL_REPEAT_INTERVAL, COL_REPEAT_RULE, COL_IMPORT_ID, COL_FLAGS, COL_REPEAT_LIMIT, COL_EVENT_TYPE, COL_OFFSET,
- COL_IS_DST_INCLUDED, COL_LAST_UPDATED, COL_EVENT_SOURCE, COL_LOCATION)
-
- private fun fillEvents(cursor: Cursor?): List {
- val eventTypeColors = SparseIntArray()
- fetchEventTypes().forEach {
- eventTypeColors.put(it.id, it.color)
- }
-
- val events = ArrayList()
- cursor?.use {
- if (cursor.moveToFirst()) {
- do {
- val id = cursor.getIntValue(COL_ID)
- val startTS = cursor.getIntValue(COL_START_TS)
- val endTS = cursor.getIntValue(COL_END_TS)
- val reminder1Minutes = cursor.getIntValue(COL_REMINDER_MINUTES)
- val reminder2Minutes = cursor.getIntValue(COL_REMINDER_MINUTES_2)
- val reminder3Minutes = cursor.getIntValue(COL_REMINDER_MINUTES_3)
- val repeatInterval = cursor.getIntValue(COL_REPEAT_INTERVAL)
- var repeatRule = cursor.getIntValue(COL_REPEAT_RULE)
- val title = cursor.getStringValue(COL_TITLE)
- val description = cursor.getStringValue(COL_DESCRIPTION)
- val importId = cursor.getStringValue(COL_IMPORT_ID) ?: ""
- val flags = cursor.getIntValue(COL_FLAGS)
- val repeatLimit = cursor.getIntValue(COL_REPEAT_LIMIT)
- val eventType = cursor.getIntValue(COL_EVENT_TYPE)
- val offset = cursor.getStringValue(COL_OFFSET)
- val isDstIncluded = cursor.getIntValue(COL_IS_DST_INCLUDED) == 1
- val lastUpdated = cursor.getLongValue(COL_LAST_UPDATED)
- val source = cursor.getStringValue(COL_EVENT_SOURCE)
- val location = cursor.getStringValue(COL_LOCATION)
- val color = eventTypeColors[eventType]
-
- val ignoreEventOccurrences = if (repeatInterval != 0) {
- getIgnoredOccurrences(id)
- } else {
- ArrayList()
- }
-
- if (repeatInterval > 0 && repeatInterval % MONTH == 0 && repeatRule == 0) {
- repeatRule = REPEAT_MONTH_SAME_DAY
- }
-
- val event = Event(id, startTS, endTS, title, description, reminder1Minutes, reminder2Minutes, reminder3Minutes,
- repeatInterval, importId, flags, repeatLimit, repeatRule, eventType, ignoreEventOccurrences, offset, isDstIncluded,
- 0, lastUpdated, source, color, location)
- events.add(event)
- } while (cursor.moveToNext())
- }
- }
- return events
- }
-
- fun getEventTypes(callback: (types: ArrayList) -> Unit) {
- Thread {
- callback(fetchEventTypes())
- }.start()
- }
-
- fun fetchEventTypes(): ArrayList {
- val eventTypes = ArrayList(4)
- val cols = arrayOf(COL_TYPE_ID, COL_TYPE_TITLE, COL_TYPE_COLOR, COL_TYPE_CALDAV_CALENDAR_ID, COL_TYPE_CALDAV_DISPLAY_NAME, COL_TYPE_CALDAV_EMAIL)
- var cursor: Cursor? = null
- try {
- cursor = mDb.query(TYPES_TABLE_NAME, cols, null, null, null, null, "$COL_TYPE_TITLE ASC")
- if (cursor?.moveToFirst() == true) {
- do {
- val id = cursor.getIntValue(COL_TYPE_ID)
- val title = cursor.getStringValue(COL_TYPE_TITLE)
- val color = cursor.getIntValue(COL_TYPE_COLOR)
- val calendarId = cursor.getIntValue(COL_TYPE_CALDAV_CALENDAR_ID)
- val displayName = cursor.getStringValue(COL_TYPE_CALDAV_DISPLAY_NAME)
- val email = cursor.getStringValue(COL_TYPE_CALDAV_EMAIL)
- val eventType = EventType(id, title, color, calendarId, displayName, email)
- eventTypes.add(eventType)
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
- return eventTypes
- }
-
- fun doEventTypesContainEvent(types: ArrayList): Boolean {
- val args = TextUtils.join(", ", types.map { it.id })
- val columns = arrayOf(COL_ID)
- val selection = "$COL_EVENT_TYPE IN ($args)"
- var cursor: Cursor? = null
- try {
- cursor = mDb.query(MAIN_TABLE_NAME, columns, selection, null, null, null, null)
- return cursor?.moveToFirst() == true
- } finally {
- cursor?.close()
- }
- }
-
- private fun getIgnoredOccurrences(eventId: Int): ArrayList {
- val projection = arrayOf(COL_OCCURRENCE_DAYCODE)
- val selection = "$COL_PARENT_EVENT_ID = ?"
- val selectionArgs = arrayOf(eventId.toString())
- val daycodes = ArrayList()
-
- var cursor: Cursor? = null
- try {
- cursor = mDb.query(EXCEPTIONS_TABLE_NAME, projection, selection, selectionArgs, null, null, COL_OCCURRENCE_DAYCODE)
- if (cursor?.moveToFirst() == true) {
- do {
- daycodes.add(cursor.getIntValue(COL_OCCURRENCE_DAYCODE))
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
- return daycodes
- }
-
- private fun convertExceptionTimestampToDaycode(db: SQLiteDatabase) {
- val projection = arrayOf(COL_EXCEPTION_ID, COL_OCCURRENCE_TIMESTAMP)
- var cursor: Cursor? = null
- try {
- cursor = db.query(EXCEPTIONS_TABLE_NAME, projection, null, null, null, null, null)
- if (cursor?.moveToFirst() == true) {
- do {
- val id = cursor.getIntValue(COL_EXCEPTION_ID)
- val ts = cursor.getIntValue(COL_OCCURRENCE_TIMESTAMP)
- val values = ContentValues()
- values.put(COL_OCCURRENCE_DAYCODE, Formatter.getDayCodeFromTS(ts))
-
- val selection = "$COL_EXCEPTION_ID = ?"
- val selectionArgs = arrayOf(id.toString())
- db.update(EXCEPTIONS_TABLE_NAME, values, selection, selectionArgs)
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
- }
-
- private fun setupRepeatRules(db: SQLiteDatabase) {
- val projection = arrayOf(COL_EVENT_ID, COL_REPEAT_INTERVAL, COL_REPEAT_START)
- val selection = "$COL_REPEAT_INTERVAL != 0"
- var cursor: Cursor? = null
- try {
- cursor = db.query(META_TABLE_NAME, projection, selection, null, null, null, null)
- if (cursor?.moveToFirst() == true) {
- do {
- val interval = cursor.getIntValue(COL_REPEAT_INTERVAL)
- if (interval != MONTH && interval % WEEK != 0)
- continue
-
- val eventId = cursor.getIntValue(COL_EVENT_ID)
- val start = cursor.getIntValue(COL_REPEAT_START)
- var rule = Math.pow(2.0, (Formatter.getDateTimeFromTS(start).dayOfWeek - 1).toDouble()).toInt()
- if (interval == MONTH) {
- rule = REPEAT_MONTH_SAME_DAY
- }
-
- val values = ContentValues()
- values.put(COL_REPEAT_RULE, rule)
- val curSelection = "$COL_EVENT_ID = ?"
- val curSelectionArgs = arrayOf(eventId.toString())
- db.update(META_TABLE_NAME, values, curSelection, curSelectionArgs)
- } while (cursor.moveToNext())
- }
- } finally {
- cursor?.close()
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/IcsImporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/IcsImporter.kt
deleted file mode 100644
index 71f6b3210..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/IcsImporter.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-package com.simplemobiletools.calendar.helpers
-
-import android.content.Context
-import android.widget.Toast
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.activities.SimpleActivity
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.helpers.IcsImporter.ImportResult.*
-import com.simplemobiletools.calendar.models.Event
-import com.simplemobiletools.calendar.models.EventType
-import com.simplemobiletools.commons.extensions.areDigitsOnly
-import com.simplemobiletools.commons.extensions.showErrorToast
-import java.io.File
-
-class IcsImporter(val activity: SimpleActivity) {
- enum class ImportResult {
- IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL
- }
-
- private var curStart = -1
- private var curEnd = -1
- private var curTitle = ""
- private var curDescription = ""
- private var curImportId = ""
- private var curFlags = 0
- private var curReminderMinutes = ArrayList()
- private var curRepeatExceptions = ArrayList()
- private var curRepeatInterval = 0
- private var curRepeatLimit = 0
- private var curRepeatRule = 0
- private var curEventType = DBHelper.REGULAR_EVENT_TYPE_ID
- private var curLastModified = 0L
- private var curLocation = ""
- private var curCategoryColor = -2
- private var isNotificationDescription = false
- private var isProperReminderAction = false
- private var curReminderTriggerMinutes = -1
-
- private var eventsImported = 0
- private var eventsFailed = 0
-
- fun importEvents(path: String, defaultEventType: Int): ImportResult {
- try {
- val existingEvents = activity.dbHelper.getEventsWithImportIds()
- var prevLine = ""
-
- val inputStream = if (path.contains("/")) {
- File(path).inputStream()
- } else {
- activity.assets.open(path)
- }
-
- inputStream.bufferedReader().use {
- while (true) {
- var line = it.readLine() ?: break
- if (line.trim().isEmpty())
- continue
-
- if (line.substring(0, 1) == " ") {
- line = prevLine + line.trim()
- eventsFailed--
- }
-
- if (line == BEGIN_EVENT) {
- resetValues()
- curEventType = defaultEventType
- } else if (line.startsWith(DTSTART)) {
- curStart = getTimestamp(line.substring(DTSTART.length))
- } else if (line.startsWith(DTEND)) {
- curEnd = getTimestamp(line.substring(DTEND.length))
- } else if (line.startsWith(DURATION)) {
- val duration = line.substring(DURATION.length)
- curEnd = curStart + Parser().parseDurationSeconds(duration)
- } else if (line.startsWith(SUMMARY) && !isNotificationDescription) {
- curTitle = line.substring(SUMMARY.length)
- curTitle = getTitle(curTitle).replace("\\n", "\n")
- } else if (line.startsWith(DESCRIPTION) && !isNotificationDescription) {
- curDescription = line.substring(DESCRIPTION.length).replace("\\n", "\n")
- } else if (line.startsWith(UID)) {
- curImportId = line.substring(UID.length).trim()
- } else if (line.startsWith(RRULE)) {
- val repeatRule = Parser().parseRepeatInterval(line.substring(RRULE.length), curStart)
- curRepeatRule = repeatRule.repeatRule
- curRepeatInterval = repeatRule.repeatInterval
- curRepeatLimit = repeatRule.repeatLimit
- } else if (line.startsWith(ACTION)) {
- isNotificationDescription = true
- isProperReminderAction = line.substring(ACTION.length) == DISPLAY
- } else if (line.startsWith(TRIGGER)) {
- curReminderTriggerMinutes = Parser().parseDurationSeconds(line.substring(TRIGGER.length)) / 60
- } else if (line.startsWith(CATEGORY_COLOR)) {
- val color = line.substring(CATEGORY_COLOR.length)
- if (color.trimStart('-').areDigitsOnly()) {
- curCategoryColor = Integer.parseInt(color)
- }
- } else if (line.startsWith(CATEGORIES)) {
- val categories = line.substring(CATEGORIES.length)
- tryAddCategories(categories, activity)
- } else if (line.startsWith(LAST_MODIFIED)) {
- curLastModified = getTimestamp(line.substring(LAST_MODIFIED.length)) * 1000L
- } else if (line.startsWith(EXDATE)) {
- var value = line.substring(EXDATE.length)
- if (value.endsWith('}'))
- value = value.substring(0, value.length - 1)
-
- curRepeatExceptions.add(getTimestamp(value))
- } else if (line.startsWith(LOCATION)) {
- curLocation = line.substring(LOCATION.length)
- } else if (line == END_ALARM) {
- if (isProperReminderAction && curReminderTriggerMinutes != -1) {
- curReminderMinutes.add(curReminderTriggerMinutes)
- }
- } else if (line == END_EVENT) {
- if (curStart != -1 && curEnd == -1)
- curEnd = curStart
-
- if (curTitle.isEmpty() || curStart == -1)
- continue
-
- val eventToUpdate = existingEvents.firstOrNull { curImportId.isNotEmpty() && curImportId == it.importId }
- if (eventToUpdate != null && eventToUpdate.lastUpdated >= curLastModified) {
- continue
- }
-
- val event = Event(0, curStart, curEnd, curTitle, curDescription, curReminderMinutes.getOrElse(0, { -1 }),
- curReminderMinutes.getOrElse(1, { -1 }), curReminderMinutes.getOrElse(2, { -1 }), curRepeatInterval,
- curImportId, curFlags, curRepeatLimit, curRepeatRule, curEventType, lastUpdated = curLastModified,
- source = SOURCE_IMPORTED_ICS, location = curLocation)
-
- if (event.getIsAllDay() && curEnd > curStart) {
- event.endTS -= DAY
- }
-
- if (eventToUpdate == null) {
- activity.dbHelper.insert(event, false) {
- for (exceptionTS in curRepeatExceptions) {
- activity.dbHelper.addEventRepeatException(it, exceptionTS)
- }
- existingEvents.add(event)
- }
- } else {
- event.id = eventToUpdate.id
- activity.dbHelper.update(event, true) {}
- }
- eventsImported++
- resetValues()
- }
- prevLine = line
- }
- }
- } catch (e: Exception) {
- activity.showErrorToast(e, Toast.LENGTH_LONG)
- eventsFailed++
- }
-
- return when {
- eventsImported == 0 -> IMPORT_FAIL
- eventsFailed > 0 -> IMPORT_PARTIAL
- else -> IMPORT_OK
- }
- }
-
- private fun getTimestamp(fullString: String): Int {
- return try {
- if (fullString.startsWith(';')) {
- val value = fullString.substring(fullString.lastIndexOf(':') + 1)
- if (!value.contains("T"))
- curFlags = curFlags or FLAG_ALL_DAY
-
- Parser().parseDateTimeValue(value)
- } else {
- Parser().parseDateTimeValue(fullString.substring(1))
- }
- } catch (e: Exception) {
- activity.showErrorToast(e, Toast.LENGTH_LONG)
- eventsFailed++
- -1
- }
- }
-
- private fun tryAddCategories(categories: String, context: Context) {
- val eventTypeTitle = if (categories.contains(",")) {
- categories.split(",")[0]
- } else {
- categories
- }
-
- val eventId = context.dbHelper.getEventTypeIdWithTitle(eventTypeTitle)
- curEventType = if (eventId == -1) {
- val newTypeColor = if (curCategoryColor == -2) context.resources.getColor(R.color.color_primary) else curCategoryColor
- val eventType = EventType(0, eventTypeTitle, newTypeColor)
- context.dbHelper.insertEventType(eventType)
- } else {
- eventId
- }
- }
-
- private fun getTitle(title: String): String {
- return if (title.startsWith(";") && title.contains(":")) {
- title.substring(title.lastIndexOf(':') + 1)
- } else {
- title.substring(1, Math.min(title.length, 80))
- }
- }
-
- private fun resetValues() {
- curStart = -1
- curEnd = -1
- curTitle = ""
- curDescription = ""
- curImportId = ""
- curFlags = 0
- curReminderMinutes = ArrayList()
- curRepeatExceptions = ArrayList()
- curRepeatInterval = 0
- curRepeatLimit = 0
- curRepeatRule = 0
- curEventType = DBHelper.REGULAR_EVENT_TYPE_ID
- curLastModified = 0L
- curCategoryColor = -2
- curLocation = ""
- isNotificationDescription = false
- isProperReminderAction = false
- curReminderTriggerMinutes = -1
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MonthlyCalendarImpl.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MonthlyCalendarImpl.kt
deleted file mode 100644
index 5e43361e7..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MonthlyCalendarImpl.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-package com.simplemobiletools.calendar.helpers
-
-import android.content.Context
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.extensions.getFilteredEvents
-import com.simplemobiletools.calendar.extensions.seconds
-import com.simplemobiletools.calendar.interfaces.MonthlyCalendar
-import com.simplemobiletools.calendar.models.DayMonthly
-import com.simplemobiletools.calendar.models.Event
-import org.joda.time.DateTime
-import java.util.*
-import kotlin.collections.ArrayList
-
-class MonthlyCalendarImpl(val mCallback: MonthlyCalendar, val mContext: Context) {
- private val DAYS_CNT = 42
- private val YEAR_PATTERN = "YYYY"
-
- private val mToday: String = DateTime().toString(Formatter.DAYCODE_PATTERN)
- private var mEvents = ArrayList()
- private var mFilterEventTypes = true
-
- lateinit var mTargetDate: DateTime
-
- fun updateMonthlyCalendar(targetDate: DateTime, filterEventTypes: Boolean = true) {
- mFilterEventTypes = filterEventTypes
- mTargetDate = targetDate
- val startTS = mTargetDate.minusMonths(1).seconds()
- val endTS = mTargetDate.plusMonths(1).seconds()
- mContext.dbHelper.getEvents(startTS, endTS) {
- gotEvents(it as ArrayList)
- }
- }
-
- fun getMonth(targetDate: DateTime) {
- updateMonthlyCalendar(targetDate)
- }
-
- fun getDays(markDaysWithEvents: Boolean) {
- val days = ArrayList(DAYS_CNT)
- val currMonthDays = mTargetDate.dayOfMonth().maximumValue
- var firstDayIndex = mTargetDate.withDayOfMonth(1).dayOfWeek
- if (!mContext.config.isSundayFirst)
- firstDayIndex -= 1
- val prevMonthDays = mTargetDate.minusMonths(1).dayOfMonth().maximumValue
-
- var isThisMonth = false
- var isToday: Boolean
- var value = prevMonthDays - firstDayIndex + 1
- var curDay: DateTime = mTargetDate
-
- for (i in 0 until DAYS_CNT) {
- when {
- i < firstDayIndex -> {
- isThisMonth = false
- curDay = mTargetDate.withDayOfMonth(1).minusMonths(1)
- }
- i == firstDayIndex -> {
- value = 1
- isThisMonth = true
- curDay = mTargetDate
- }
- value == currMonthDays + 1 -> {
- value = 1
- isThisMonth = false
- curDay = mTargetDate.withDayOfMonth(1).plusMonths(1)
- }
- }
-
- isToday = isThisMonth && isToday(mTargetDate, value)
-
- val newDay = curDay.withDayOfMonth(value)
- val dayCode = Formatter.getDayCodeFromDateTime(newDay)
- val day = DayMonthly(value, isThisMonth, isToday, dayCode, newDay.weekOfWeekyear, ArrayList())
- days.add(day)
- value++
- }
-
- if (markDaysWithEvents) {
- markDaysWithEvents(days)
- } else {
- mCallback.updateMonthlyCalendar(mContext, monthName, days, false)
- }
- }
-
- // it works more often than not, dont touch
- private fun markDaysWithEvents(days: ArrayList) {
- mContext.dbHelper.getEventTypes {
- val dayEvents = HashMap>()
- mEvents.forEach {
- val startDateTime = Formatter.getDateTimeFromTS(it.startTS)
- val endDateTime = Formatter.getDateTimeFromTS(it.endTS)
- val endCode = Formatter.getDayCodeFromDateTime(endDateTime)
-
- var currDay = startDateTime
- var dayCode = Formatter.getDayCodeFromDateTime(currDay)
- var currDayEvents = (dayEvents[dayCode] ?: ArrayList()).apply { add(it) }
- dayEvents.put(dayCode, currDayEvents)
-
- while (Formatter.getDayCodeFromDateTime(currDay) != endCode) {
- currDay = currDay.plusDays(1)
- dayCode = Formatter.getDayCodeFromDateTime(currDay)
- currDayEvents = (dayEvents[dayCode] ?: ArrayList()).apply { add(it) }
- dayEvents.put(dayCode, currDayEvents)
- }
- }
-
- days.filter { dayEvents.keys.contains(it.code) }.forEach {
- it.dayEvents = dayEvents[it.code]!!
- }
- mCallback.updateMonthlyCalendar(mContext, monthName, days, true)
- }
- }
-
- private fun isToday(targetDate: DateTime, curDayInMonth: Int): Boolean {
- return if (curDayInMonth > targetDate.dayOfMonth().maximumValue)
- false
- else
- targetDate.withDayOfMonth(curDayInMonth).toString(Formatter.DAYCODE_PATTERN) == mToday
- }
-
- private val monthName: String
- get() {
- var month = Formatter.getMonthName(mContext, mTargetDate.monthOfYear)
- val targetYear = mTargetDate.toString(YEAR_PATTERN)
- if (targetYear != DateTime().toString(YEAR_PATTERN)) {
- month += " $targetYear"
- }
- return month
- }
-
- private fun gotEvents(events: ArrayList) {
- mEvents = if (mFilterEventTypes) {
- mContext.getFilteredEvents(events) as ArrayList
- } else {
- events
- }
-
- getDays(true)
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/WeeklyCalendarImpl.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/WeeklyCalendarImpl.kt
deleted file mode 100644
index 49255a701..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/WeeklyCalendarImpl.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.simplemobiletools.calendar.helpers
-
-import android.content.Context
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.interfaces.WeeklyCalendar
-import com.simplemobiletools.calendar.models.Event
-import java.util.*
-
-class WeeklyCalendarImpl(val mCallback: WeeklyCalendar, val mContext: Context) {
- var mEvents = ArrayList()
-
- fun updateWeeklyCalendar(weekStartTS: Int) {
- val startTS = weekStartTS
- val endTS = startTS + WEEK_SECONDS
- mContext.dbHelper.getEvents(startTS, endTS) {
- mEvents = it as ArrayList
- mCallback.updateWeeklyCalendar(it)
- }
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/DeleteEventTypesListener.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/DeleteEventTypesListener.kt
deleted file mode 100644
index 18df856fa..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/DeleteEventTypesListener.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.simplemobiletools.calendar.interfaces
-
-import com.simplemobiletools.calendar.models.EventType
-import java.util.*
-
-interface DeleteEventTypesListener {
- fun deleteEventTypes(eventTypes: ArrayList, deleteEvents: Boolean)
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/DeleteEventsListener.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/DeleteEventsListener.kt
deleted file mode 100644
index 0fdebdec1..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/DeleteEventsListener.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.simplemobiletools.calendar.interfaces
-
-import java.util.*
-
-interface DeleteEventsListener {
- fun deleteItems(ids: ArrayList)
-
- fun addEventRepeatException(parentIds: ArrayList, timestamps: ArrayList)
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/MonthlyCalendar.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/MonthlyCalendar.kt
deleted file mode 100644
index 1f7c188b5..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/MonthlyCalendar.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.simplemobiletools.calendar.interfaces
-
-import android.content.Context
-import com.simplemobiletools.calendar.models.DayMonthly
-
-interface MonthlyCalendar {
- fun updateMonthlyCalendar(context: Context, month: String, days: List, checkedEvents: Boolean)
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/WeeklyCalendar.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/WeeklyCalendar.kt
deleted file mode 100644
index 6c7501506..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/WeeklyCalendar.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.simplemobiletools.calendar.interfaces
-
-import com.simplemobiletools.calendar.models.Event
-
-interface WeeklyCalendar {
- fun updateWeeklyCalendar(events: ArrayList)
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/CalDAVCalendar.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/models/CalDAVCalendar.kt
deleted file mode 100644
index f0258bb13..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/CalDAVCalendar.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.simplemobiletools.calendar.models
-
-data class CalDAVCalendar(val id: Int, val displayName: String, val accountName: String, val ownerName: String, val color: Int, val accessLevel: Int) {
- fun canWrite() = accessLevel >= 500
-
- fun getFullTitle() = "$displayName ($accountName)"
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/Event.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/models/Event.kt
deleted file mode 100644
index 8f9a2e3ba..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/Event.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.simplemobiletools.calendar.models
-
-import com.simplemobiletools.calendar.extensions.seconds
-import com.simplemobiletools.calendar.helpers.*
-import com.simplemobiletools.calendar.helpers.Formatter
-import org.joda.time.DateTime
-import java.io.Serializable
-import java.util.*
-
-data class Event(var id: Int = 0, var startTS: Int = 0, var endTS: Int = 0, var title: String = "", var description: String = "",
- var reminder1Minutes: Int = -1, var reminder2Minutes: Int = -1, var reminder3Minutes: Int = -1, var repeatInterval: Int = 0,
- var importId: String = "", var flags: Int = 0, var repeatLimit: Int = 0, var repeatRule: Int = 0,
- var eventType: Int = DBHelper.REGULAR_EVENT_TYPE_ID, var ignoreEventOccurrences: ArrayList = ArrayList(),
- var offset: String = "", var isDstIncluded: Boolean = false, var parentId: Int = 0, var lastUpdated: Long = 0L,
- var source: String = SOURCE_SIMPLE_CALENDAR, var color: Int = 0, var location: String = "") : Serializable {
-
- companion object {
- private val serialVersionUID = -32456795132345616L
- }
-
- fun addIntervalTime(original: Event) {
- val currStart = Formatter.getDateTimeFromTS(startTS)
- val newStart: DateTime
- newStart = when (repeatInterval) {
- DAY -> currStart.plusDays(1)
- else -> {
- when {
- repeatInterval % YEAR == 0 -> currStart.plusYears(repeatInterval / YEAR)
- repeatInterval % MONTH == 0 -> when (repeatRule) {
- REPEAT_MONTH_SAME_DAY -> addMonthsWithSameDay(currStart, original)
- REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST -> addXthDayInterval(currStart, original, true)
- REPEAT_MONTH_ORDER_WEEKDAY -> addXthDayInterval(currStart, original, false)
- else -> currStart.plusMonths(repeatInterval / MONTH).dayOfMonth().withMaximumValue()
- }
- repeatInterval % WEEK == 0 -> {
- // step through weekly repetition by days too, as events can trigger multiple times a week
- currStart.plusDays(1)
- }
- else -> currStart.plusSeconds(repeatInterval)
- }
- }
- }
- val newStartTS = newStart.seconds()
- val newEndTS = newStartTS + (endTS - startTS)
- startTS = newStartTS
- endTS = newEndTS
- }
-
- // if an event should happen on 31st with Same Day monthly repetition, dont show it at all at months with 30 or less days
- private fun addMonthsWithSameDay(currStart: DateTime, original: Event): DateTime {
- var newDateTime = currStart.plusMonths(repeatInterval / MONTH)
- if (newDateTime.dayOfMonth == currStart.dayOfMonth) {
- return newDateTime
- }
-
- while (newDateTime.dayOfMonth().maximumValue < Formatter.getDateTimeFromTS(original.startTS).dayOfMonth().maximumValue) {
- newDateTime = newDateTime.plusMonths(repeatInterval / MONTH)
- newDateTime = newDateTime.withDayOfMonth(newDateTime.dayOfMonth().maximumValue)
- }
- return newDateTime
- }
-
- // handle monthly repetitions like Third Monday
- private fun addXthDayInterval(currStart: DateTime, original: Event, forceLastWeekday: Boolean): DateTime {
- val day = currStart.dayOfWeek
- var order = (currStart.dayOfMonth - 1) / 7
- val properMonth = currStart.withDayOfMonth(7).plusMonths(repeatInterval / MONTH).withDayOfWeek(day)
- var firstProperDay = properMonth.dayOfMonth % 7
- if (firstProperDay == 0)
- firstProperDay = properMonth.dayOfMonth
-
- // check if it should be for example Fourth Monday, or Last Monday
- if (forceLastWeekday && (order == 3 || order == 4)) {
- val originalDateTime = Formatter.getDateTimeFromTS(original.startTS)
- val isLastWeekday = originalDateTime.monthOfYear != originalDateTime.plusDays(7).monthOfYear
- if (isLastWeekday)
- order = -1
- }
-
- val daysCnt = properMonth.dayOfMonth().maximumValue
- var wantedDay = firstProperDay + order * 7
- if (wantedDay > daysCnt)
- wantedDay -= 7
-
- if (order == -1) {
- wantedDay = firstProperDay + ((daysCnt - firstProperDay) / 7) * 7
- }
-
- return properMonth.withDayOfMonth(wantedDay)
- }
-
- fun getIsAllDay() = flags and FLAG_ALL_DAY != 0
-
- fun getReminders() = setOf(reminder1Minutes, reminder2Minutes, reminder3Minutes).filter { it != REMINDER_OFF }
-
- // properly return the start time of all-day events as midnight
- fun getEventStartTS(): Int {
- return if (getIsAllDay()) {
- Formatter.getDateTimeFromTS(startTS).withTime(0, 0, 0, 0).seconds()
- } else {
- startTS
- }
- }
-
- fun getCalDAVEventId(): Long {
- return try {
- (importId.split("-").lastOrNull() ?: "0").toString().toLong()
- } catch (e: NumberFormatException) {
- 0L
- }
- }
-
- fun getCalDAVCalendarId() = if (source.startsWith(CALDAV)) (source.split("-").lastOrNull() ?: "0").toString().toInt() else 0
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/EventType.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/models/EventType.kt
deleted file mode 100644
index c677bfa7f..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/EventType.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.simplemobiletools.calendar.models
-
-data class EventType(var id: Int = 0, var title: String, var color: Int, var caldavCalendarId: Int = 0, var caldavDisplayName: String = "", var caldavEmail: String = "") {
- fun getDisplayTitle() = if (caldavCalendarId == 0) title else "$caldavDisplayName ($caldavEmail)"
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListEvent.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListEvent.kt
deleted file mode 100644
index 5cfcaa46f..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListEvent.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.simplemobiletools.calendar.models
-
-class ListEvent(var id: Int = 0, var startTS: Int = 0, var endTS: Int = 0, var title: String = "", var description: String = "",
- var isAllDay: Boolean, var color: Int, var location: String = "") : ListItem()
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListItem.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListItem.kt
deleted file mode 100644
index 41e2ece59..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListItem.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.simplemobiletools.calendar.models
-
-open class ListItem
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListSection.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListSection.kt
deleted file mode 100644
index 91865f84c..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/ListSection.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.simplemobiletools.calendar.models
-
-class ListSection(val title: String, val isToday: Boolean = false) : ListItem() {
- override fun toString() = "ListSection {title=$title, isToday=$isToday}"
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/RepeatRule.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/models/RepeatRule.kt
deleted file mode 100644
index 2e6c20223..000000000
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/RepeatRule.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.simplemobiletools.calendar.models
-
-data class RepeatRule(val repeatInterval: Int, val repeatRule: Int, val repeatLimit: Int)
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt
new file mode 100644
index 000000000..2b7e6ab0c
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/App.kt
@@ -0,0 +1,11 @@
+package com.simplemobiletools.calendar.pro
+
+import androidx.multidex.MultiDexApplication
+import com.simplemobiletools.commons.extensions.checkUseEnglish
+
+class App : MultiDexApplication() {
+ override fun onCreate() {
+ super.onCreate()
+ checkUseEnglish()
+ }
+}
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
new file mode 100644
index 000000000..ab2d5e7fd
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt
@@ -0,0 +1,1507 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.app.Activity
+import android.app.DatePickerDialog
+import android.app.TimePickerDialog
+import android.content.Intent
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.provider.CalendarContract.Attendees
+import android.provider.ContactsContract.CommonDataKinds
+import android.provider.ContactsContract.CommonDataKinds.StructuredName
+import android.provider.ContactsContract.Data
+import android.text.TextUtils
+import android.text.method.LinkMovementMethod
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.WindowManager
+import android.view.inputmethod.EditorInfo
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import androidx.core.app.NotificationManagerCompat
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.adapters.AutoCompleteTextViewAdapter
+import com.simplemobiletools.calendar.pro.dialogs.*
+import com.simplemobiletools.calendar.pro.extensions.*
+import com.simplemobiletools.calendar.pro.helpers.*
+import com.simplemobiletools.calendar.pro.helpers.Formatter
+import com.simplemobiletools.calendar.pro.models.*
+import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.dialogs.RadioGroupDialog
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.models.RadioItem
+import com.simplemobiletools.commons.views.MyAutoCompleteTextView
+import kotlinx.android.synthetic.main.activity_event.*
+import kotlinx.android.synthetic.main.activity_event.view.*
+import kotlinx.android.synthetic.main.item_attendee.view.*
+import org.joda.time.DateTime
+import org.joda.time.DateTimeZone
+import java.util.*
+import java.util.regex.Pattern
+import kotlin.collections.ArrayList
+
+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 EVENT = "EVENT"
+ private val START_TS = "START_TS"
+ private val END_TS = "END_TS"
+ private val REMINDER_1_MINUTES = "REMINDER_1_MINUTES"
+ private val REMINDER_2_MINUTES = "REMINDER_2_MINUTES"
+ private val REMINDER_3_MINUTES = "REMINDER_3_MINUTES"
+ private val REMINDER_1_TYPE = "REMINDER_1_TYPE"
+ private val REMINDER_2_TYPE = "REMINDER_2_TYPE"
+ private val REMINDER_3_TYPE = "REMINDER_3_TYPE"
+ private val REPEAT_INTERVAL = "REPEAT_INTERVAL"
+ private val REPEAT_LIMIT = "REPEAT_LIMIT"
+ private val REPEAT_RULE = "REPEAT_RULE"
+ private val ATTENDEES = "ATTENDEES"
+ private val EVENT_TYPE_ID = "EVENT_TYPE_ID"
+ private val EVENT_CALENDAR_ID = "EVENT_CALENDAR_ID"
+ private val SELECT_TIME_ZONE_INTENT = 1
+
+ private var mReminder1Minutes = REMINDER_OFF
+ private var mReminder2Minutes = REMINDER_OFF
+ private var mReminder3Minutes = REMINDER_OFF
+ private var mReminder1Type = REMINDER_NOTIFICATION
+ private var mReminder2Type = REMINDER_NOTIFICATION
+ private var mReminder3Type = REMINDER_NOTIFICATION
+ private var mRepeatInterval = 0
+ private var mRepeatLimit = 0L
+ private var mRepeatRule = 0
+ private var mEventTypeId = REGULAR_EVENT_TYPE_ID
+ private var mDialogTheme = 0
+ private var mEventOccurrenceTS = 0L
+ private var mEventCalendarId = STORED_LOCALLY_ONLY
+ private var mWasActivityInitialized = false
+ private var mWasContactsPermissionChecked = false
+ private var mAttendees = ArrayList()
+ private var mAttendeeAutoCompleteViews = ArrayList()
+ private var mAvailableContacts = ArrayList()
+ private var mSelectedContacts = ArrayList()
+ private var mStoredEventTypes = ArrayList()
+ private var mOriginalTimeZone = DateTimeZone.getDefault().id
+
+ private lateinit var mEventStartDateTime: DateTime
+ private lateinit var mEventEndDateTime: DateTime
+ private lateinit var mEvent: Event
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_event)
+
+ if (checkAppSideloading()) {
+ return
+ }
+
+ supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_cross_vector)
+ val intent = intent ?: return
+ mDialogTheme = getDialogTheme()
+ mWasContactsPermissionChecked = hasPermission(PERMISSION_READ_CONTACTS)
+
+ val eventId = intent.getLongExtra(EVENT_ID, 0L)
+ ensureBackgroundThread {
+ mStoredEventTypes = eventTypesDB.getEventTypes().toMutableList() as ArrayList
+ val event = eventsDB.getEventWithId(eventId)
+ if (eventId != 0L && event == null) {
+ finish()
+ return@ensureBackgroundThread
+ }
+
+ val localEventType = mStoredEventTypes.firstOrNull { it.id == config.lastUsedLocalEventTypeId }
+ runOnUiThread {
+ if (!isDestroyed && !isFinishing) {
+ gotEvent(savedInstanceState, localEventType, event)
+ }
+ }
+ }
+ }
+
+ private fun gotEvent(savedInstanceState: Bundle?, localEventType: EventType?, event: Event?) {
+ if (localEventType == null || localEventType.caldavCalendarId != 0) {
+ config.lastUsedLocalEventTypeId = REGULAR_EVENT_TYPE_ID
+ }
+
+ mEventTypeId = if (config.defaultEventTypeId == -1L) config.lastUsedLocalEventTypeId else config.defaultEventTypeId
+
+ if (event != null) {
+ mEvent = event
+ mEventOccurrenceTS = intent.getLongExtra(EVENT_OCCURRENCE_TS, 0L)
+ if (savedInstanceState == null) {
+ setupEditEvent()
+ }
+
+ if (intent.getBooleanExtra(IS_DUPLICATE_INTENT, false)) {
+ mEvent.id = null
+ } else {
+ cancelNotification(mEvent.id!!)
+ }
+ } else {
+ mEvent = Event(null)
+ config.apply {
+ mReminder1Minutes = if (usePreviousEventReminders) lastEventReminderMinutes1 else defaultReminder1
+ mReminder2Minutes = if (usePreviousEventReminders) lastEventReminderMinutes2 else defaultReminder2
+ mReminder3Minutes = if (usePreviousEventReminders) lastEventReminderMinutes3 else defaultReminder3
+ }
+
+ if (savedInstanceState == null) {
+ setupNewEvent()
+ }
+ }
+
+ if (savedInstanceState == null) {
+ updateTexts()
+ updateEventType()
+ updateCalDAVCalendar()
+ }
+
+ event_show_on_map.setOnClickListener { showOnMap() }
+ event_start_date.setOnClickListener { setupStartDate() }
+ event_start_time.setOnClickListener { setupStartTime() }
+ event_end_date.setOnClickListener { setupEndDate() }
+ event_end_time.setOnClickListener { setupEndTime() }
+ event_time_zone.setOnClickListener { setupTimeZone() }
+
+ event_all_day.setOnCheckedChangeListener { compoundButton, isChecked -> toggleAllDay(isChecked) }
+ event_repetition.setOnClickListener { showRepeatIntervalDialog() }
+ event_repetition_rule_holder.setOnClickListener { showRepetitionRuleDialog() }
+ event_repetition_limit_holder.setOnClickListener { showRepetitionTypePicker() }
+
+ event_reminder_1.setOnClickListener {
+ handleNotificationAvailability {
+ if (config.wasAlarmWarningShown) {
+ showReminder1Dialog()
+ } else {
+ ConfirmationDialog(this, messageId = R.string.reminder_warning, positive = R.string.ok, negative = 0) {
+ config.wasAlarmWarningShown = true
+ showReminder1Dialog()
+ }
+ }
+ }
+ }
+
+ event_reminder_2.setOnClickListener { showReminder2Dialog() }
+ event_reminder_3.setOnClickListener { showReminder3Dialog() }
+
+ event_reminder_1_type.setOnClickListener {
+ showReminderTypePicker(mReminder1Type) {
+ mReminder1Type = it
+ updateReminderTypeImage(event_reminder_1_type, Reminder(mReminder1Minutes, mReminder1Type))
+ }
+ }
+
+ event_reminder_2_type.setOnClickListener {
+ showReminderTypePicker(mReminder2Type) {
+ mReminder2Type = it
+ updateReminderTypeImage(event_reminder_2_type, Reminder(mReminder2Minutes, mReminder2Type))
+ }
+ }
+
+ event_reminder_3_type.setOnClickListener {
+ showReminderTypePicker(mReminder3Type) {
+ mReminder3Type = it
+ updateReminderTypeImage(event_reminder_3_type, Reminder(mReminder3Minutes, mReminder3Type))
+ }
+ }
+
+ event_type_holder.setOnClickListener { showEventTypeDialog() }
+ event_all_day.apply {
+ isChecked = mEvent.flags and FLAG_ALL_DAY != 0
+ jumpDrawablesToCurrentState()
+ }
+
+ updateTextColors(event_scrollview)
+ updateIconColors()
+ event_time_zone_image.beVisibleIf(config.allowChangingTimeZones)
+ event_time_zone.beVisibleIf(config.allowChangingTimeZones)
+ mWasActivityInitialized = true
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_event, menu)
+ if (mWasActivityInitialized) {
+ menu.findItem(R.id.delete).isVisible = mEvent.id != null
+ menu.findItem(R.id.share).isVisible = mEvent.id != null
+ menu.findItem(R.id.duplicate).isVisible = mEvent.id != null
+ }
+ updateMenuItemColors(menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.save -> saveCurrentEvent()
+ R.id.delete -> deleteEvent()
+ R.id.duplicate -> duplicateEvent()
+ R.id.share -> shareEvent()
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ if (!mWasActivityInitialized) {
+ return
+ }
+
+ outState.apply {
+ putSerializable(EVENT, mEvent)
+ putLong(START_TS, mEventStartDateTime.seconds())
+ putLong(END_TS, mEventEndDateTime.seconds())
+ putString(TIME_ZONE, mEvent.timeZone)
+
+ putInt(REMINDER_1_MINUTES, mReminder1Minutes)
+ putInt(REMINDER_2_MINUTES, mReminder2Minutes)
+ putInt(REMINDER_3_MINUTES, mReminder3Minutes)
+
+ putInt(REMINDER_1_TYPE, mReminder1Type)
+ putInt(REMINDER_2_TYPE, mReminder2Type)
+ putInt(REMINDER_3_TYPE, mReminder3Type)
+
+ putInt(REPEAT_INTERVAL, mRepeatInterval)
+ putInt(REPEAT_RULE, mRepeatRule)
+ putLong(REPEAT_LIMIT, mRepeatLimit)
+
+ putString(ATTENDEES, getAllAttendees(false))
+
+ putLong(EVENT_TYPE_ID, mEventTypeId)
+ putInt(EVENT_CALENDAR_ID, mEventCalendarId)
+ }
+ }
+
+ override fun onRestoreInstanceState(savedInstanceState: Bundle) {
+ super.onRestoreInstanceState(savedInstanceState)
+ if (!savedInstanceState.containsKey(START_TS)) {
+ finish()
+ return
+ }
+
+ savedInstanceState.apply {
+ mEvent = getSerializable(EVENT) as Event
+ mEventStartDateTime = Formatter.getDateTimeFromTS(getLong(START_TS))
+ mEventEndDateTime = Formatter.getDateTimeFromTS(getLong(END_TS))
+ mEvent.timeZone = getString(TIME_ZONE) ?: TimeZone.getDefault().id
+
+ mReminder1Minutes = getInt(REMINDER_1_MINUTES)
+ mReminder2Minutes = getInt(REMINDER_2_MINUTES)
+ mReminder3Minutes = getInt(REMINDER_3_MINUTES)
+
+ mReminder1Type = getInt(REMINDER_1_TYPE)
+ mReminder2Type = getInt(REMINDER_2_TYPE)
+ mReminder3Type = getInt(REMINDER_3_TYPE)
+
+ mRepeatInterval = getInt(REPEAT_INTERVAL)
+ mRepeatRule = getInt(REPEAT_RULE)
+ mRepeatLimit = getLong(REPEAT_LIMIT)
+
+ mAttendees = Gson().fromJson>(getString(ATTENDEES), object : TypeToken>() {}.type)
+ ?: ArrayList()
+
+ mEventTypeId = getLong(EVENT_TYPE_ID)
+ mEventCalendarId = getInt(EVENT_CALENDAR_ID)
+ }
+
+ checkRepeatTexts(mRepeatInterval)
+ checkRepeatRule()
+ updateTexts()
+ updateEventType()
+ updateCalDAVCalendar()
+ checkAttendees()
+ }
+
+ 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 updateTexts() {
+ updateRepetitionText()
+ checkReminderTexts()
+ updateStartTexts()
+ updateEndTexts()
+ updateTimeZoneText()
+ updateAttendeesVisibility()
+ }
+
+ private fun setupEditEvent() {
+ val realStart = if (mEventOccurrenceTS == 0L) mEvent.startTS else mEventOccurrenceTS
+ val duration = mEvent.endTS - mEvent.startTS
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
+ updateActionBarTitle(getString(R.string.edit_event))
+ mOriginalTimeZone = mEvent.timeZone
+ if (config.allowChangingTimeZones) {
+ try {
+ mEventStartDateTime = Formatter.getDateTimeFromTS(realStart).withZone(DateTimeZone.forID(mOriginalTimeZone))
+ mEventEndDateTime = Formatter.getDateTimeFromTS(realStart + duration).withZone(DateTimeZone.forID(mOriginalTimeZone))
+ } catch (e: Exception) {
+ showErrorToast(e)
+ mEventStartDateTime = Formatter.getDateTimeFromTS(realStart)
+ mEventEndDateTime = Formatter.getDateTimeFromTS(realStart + duration)
+ }
+ } else {
+ mEventStartDateTime = Formatter.getDateTimeFromTS(realStart)
+ mEventEndDateTime = Formatter.getDateTimeFromTS(realStart + duration)
+ }
+
+ event_title.setText(mEvent.title)
+ event_location.setText(mEvent.location)
+ event_description.setText(mEvent.description)
+
+ mReminder1Minutes = mEvent.reminder1Minutes
+ mReminder2Minutes = mEvent.reminder2Minutes
+ mReminder3Minutes = mEvent.reminder3Minutes
+ mReminder1Type = mEvent.reminder1Type
+ mReminder2Type = mEvent.reminder2Type
+ mReminder3Type = mEvent.reminder3Type
+ mRepeatInterval = mEvent.repeatInterval
+ mRepeatLimit = mEvent.repeatLimit
+ mRepeatRule = mEvent.repeatRule
+ mEventTypeId = mEvent.eventType
+ mEventCalendarId = mEvent.getCalDAVCalendarId()
+ mAttendees = Gson().fromJson>(mEvent.attendees, object : TypeToken>() {}.type) ?: ArrayList()
+ checkRepeatTexts(mRepeatInterval)
+ checkAttendees()
+ }
+
+ private fun setupNewEvent() {
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
+ event_title.requestFocus()
+ updateActionBarTitle(getString(R.string.new_event))
+ if (config.defaultEventTypeId != -1L) {
+ config.lastUsedCaldavCalendarId = mStoredEventTypes.firstOrNull { it.id == config.defaultEventTypeId }?.caldavCalendarId ?: 0
+ }
+
+ val isLastCaldavCalendarOK = config.caldavSync && config.getSyncedCalendarIdsAsList().contains(config.lastUsedCaldavCalendarId)
+ mEventCalendarId = if (isLastCaldavCalendarOK) config.lastUsedCaldavCalendarId else STORED_LOCALLY_ONLY
+
+ if (intent.action == Intent.ACTION_EDIT || intent.action == Intent.ACTION_INSERT) {
+ val startTS = intent.getLongExtra("beginTime", System.currentTimeMillis()) / 1000L
+ mEventStartDateTime = Formatter.getDateTimeFromTS(startTS)
+
+ val endTS = intent.getLongExtra("endTime", System.currentTimeMillis()) / 1000L
+ mEventEndDateTime = Formatter.getDateTimeFromTS(endTS)
+
+ if (intent.getBooleanExtra("allDay", false)) {
+ mEvent.flags = mEvent.flags or FLAG_ALL_DAY
+ event_all_day.isChecked = true
+ toggleAllDay(true)
+ }
+
+ event_title.setText(intent.getStringExtra("title"))
+ event_location.setText(intent.getStringExtra("eventLocation"))
+ event_description.setText(intent.getStringExtra("description"))
+ if (event_description.value.isNotEmpty()) {
+ event_description.movementMethod = LinkMovementMethod.getInstance()
+ }
+ } else {
+ val startTS = intent.getLongExtra(NEW_EVENT_START_TS, 0L)
+ val dateTime = Formatter.getDateTimeFromTS(startTS)
+ mEventStartDateTime = dateTime
+
+ val addMinutes = if (intent.getBooleanExtra(NEW_EVENT_SET_HOUR_DURATION, false)) {
+ // if an event is created at 23:00 on the weekly view, make it end on 23:59 by default to avoid spanning across multiple days
+ if (mEventStartDateTime.hourOfDay == 23) {
+ 59
+ } else {
+ 60
+ }
+ } else {
+ config.defaultDuration
+ }
+ mEventEndDateTime = mEventStartDateTime.plusMinutes(addMinutes)
+ }
+
+ checkAttendees()
+ }
+
+ private fun checkAttendees() {
+ ensureBackgroundThread {
+ fillAvailableContacts()
+ runOnUiThread {
+ updateAttendees()
+ }
+ }
+ }
+
+ private fun handleNotificationAvailability(callback: () -> Unit) {
+ if (NotificationManagerCompat.from(applicationContext).areNotificationsEnabled()) {
+ callback()
+ } else {
+ ConfirmationDialog(this, messageId = R.string.notifications_disabled, positive = R.string.ok, negative = 0) {
+ callback()
+ }
+ }
+ }
+
+ private fun showReminder1Dialog() {
+ showPickSecondsDialogHelper(mReminder1Minutes) {
+ mReminder1Minutes = if (it <= 0) it else it / 60
+ checkReminderTexts()
+ }
+ }
+
+ private fun showReminder2Dialog() {
+ showPickSecondsDialogHelper(mReminder2Minutes) {
+ mReminder2Minutes = if (it <= 0) it else it / 60
+ checkReminderTexts()
+ }
+ }
+
+ private fun showReminder3Dialog() {
+ showPickSecondsDialogHelper(mReminder3Minutes) {
+ mReminder3Minutes = if (it <= 0) it else it / 60
+ checkReminderTexts()
+ }
+ }
+
+ private fun showRepeatIntervalDialog() {
+ showEventRepeatIntervalDialog(mRepeatInterval) {
+ setRepeatInterval(it)
+ }
+ }
+
+ private fun setRepeatInterval(interval: Int) {
+ mRepeatInterval = interval
+ updateRepetitionText()
+ checkRepeatTexts(interval)
+
+ when {
+ mRepeatInterval.isXWeeklyRepetition() -> setRepeatRule(Math.pow(2.0, (mEventStartDateTime.dayOfWeek - 1).toDouble()).toInt())
+ mRepeatInterval.isXMonthlyRepetition() -> setRepeatRule(REPEAT_SAME_DAY)
+ mRepeatInterval.isXYearlyRepetition() -> setRepeatRule(REPEAT_SAME_DAY)
+ }
+ }
+
+ private fun checkRepeatTexts(limit: Int) {
+ event_repetition_limit_holder.beGoneIf(limit == 0)
+ checkRepetitionLimitText()
+
+ event_repetition_rule_holder.beVisibleIf(mRepeatInterval.isXWeeklyRepetition() || mRepeatInterval.isXMonthlyRepetition() || mRepeatInterval.isXYearlyRepetition())
+ checkRepetitionRuleText()
+ }
+
+ private fun showRepetitionTypePicker() {
+ hideKeyboard()
+ RepeatLimitTypePickerDialog(this, mRepeatLimit, mEventStartDateTime.seconds()) {
+ setRepeatLimit(it)
+ }
+ }
+
+ private fun setRepeatLimit(limit: Long) {
+ mRepeatLimit = limit
+ checkRepetitionLimitText()
+ }
+
+ private fun checkRepetitionLimitText() {
+ event_repetition_limit.text = when {
+ mRepeatLimit == 0L -> {
+ event_repetition_limit_label.text = getString(R.string.repeat)
+ resources.getString(R.string.forever)
+ }
+ mRepeatLimit > 0 -> {
+ event_repetition_limit_label.text = getString(R.string.repeat_till)
+ val repeatLimitDateTime = Formatter.getDateTimeFromTS(mRepeatLimit)
+ Formatter.getFullDate(applicationContext, repeatLimitDateTime)
+ }
+ else -> {
+ event_repetition_limit_label.text = getString(R.string.repeat)
+ "${-mRepeatLimit} ${getString(R.string.times)}"
+ }
+ }
+ }
+
+ private fun showRepetitionRuleDialog() {
+ hideKeyboard()
+ when {
+ mRepeatInterval.isXWeeklyRepetition() -> RepeatRuleWeeklyDialog(this, mRepeatRule) {
+ setRepeatRule(it)
+ }
+ mRepeatInterval.isXMonthlyRepetition() -> {
+ val items = getAvailableMonthlyRepetitionRules()
+ RadioGroupDialog(this, items, mRepeatRule) {
+ setRepeatRule(it as Int)
+ }
+ }
+ mRepeatInterval.isXYearlyRepetition() -> {
+ val items = getAvailableYearlyRepetitionRules()
+ RadioGroupDialog(this, items, mRepeatRule) {
+ setRepeatRule(it as Int)
+ }
+ }
+ }
+ }
+
+ private fun getAvailableMonthlyRepetitionRules(): ArrayList {
+ val items = arrayListOf(RadioItem(REPEAT_SAME_DAY, getString(R.string.repeat_on_the_same_day_monthly)))
+
+ // split Every Last Sunday and Every Fourth Sunday of the month, if the month has 4 sundays
+ if (isLastWeekDayOfMonth()) {
+ val order = (mEventStartDateTime.dayOfMonth - 1) / 7 + 1
+ if (order == 4) {
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY, getRepeatXthDayString(true, REPEAT_ORDER_WEEKDAY)))
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayString(true, REPEAT_ORDER_WEEKDAY_USE_LAST)))
+ } else if (order == 5) {
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayString(true, REPEAT_ORDER_WEEKDAY_USE_LAST)))
+ }
+ } else {
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY, getRepeatXthDayString(true, REPEAT_ORDER_WEEKDAY)))
+ }
+
+ if (isLastDayOfTheMonth()) {
+ items.add(RadioItem(REPEAT_LAST_DAY, getString(R.string.repeat_on_the_last_day_monthly)))
+ }
+ return items
+ }
+
+ private fun getAvailableYearlyRepetitionRules(): ArrayList {
+ val items = arrayListOf(RadioItem(REPEAT_SAME_DAY, getString(R.string.repeat_on_the_same_day_yearly)))
+
+ if (isLastWeekDayOfMonth()) {
+ val order = (mEventStartDateTime.dayOfMonth - 1) / 7 + 1
+ if (order == 4) {
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY, getRepeatXthDayInMonthString(true, REPEAT_ORDER_WEEKDAY)))
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayInMonthString(true, REPEAT_ORDER_WEEKDAY_USE_LAST)))
+ } else if (order == 5) {
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayInMonthString(true, REPEAT_ORDER_WEEKDAY_USE_LAST)))
+ }
+ } else {
+ items.add(RadioItem(REPEAT_ORDER_WEEKDAY, getRepeatXthDayInMonthString(true, REPEAT_ORDER_WEEKDAY)))
+ }
+
+ return items
+ }
+
+ private fun isLastDayOfTheMonth() = mEventStartDateTime.dayOfMonth == mEventStartDateTime.dayOfMonth().withMaximumValue().dayOfMonth
+
+ private fun isLastWeekDayOfMonth() = mEventStartDateTime.monthOfYear != mEventStartDateTime.plusDays(7).monthOfYear
+
+ private fun getRepeatXthDayString(includeBase: Boolean, repeatRule: Int): String {
+ val dayOfWeek = mEventStartDateTime.dayOfWeek
+ val base = getBaseString(dayOfWeek)
+ val order = getOrderString(repeatRule)
+ val dayString = getDayString(dayOfWeek)
+ return if (includeBase) {
+ "$base $order $dayString"
+ } else {
+ val everyString = getString(if (isMaleGender(mEventStartDateTime.dayOfWeek)) R.string.every_m else R.string.every_f)
+ "$everyString $order $dayString"
+ }
+ }
+
+ private fun getBaseString(day: Int): String {
+ return getString(if (isMaleGender(day)) {
+ R.string.repeat_every_m
+ } else {
+ R.string.repeat_every_f
+ })
+ }
+
+ private fun isMaleGender(day: Int) = day == 1 || day == 2 || day == 4 || day == 5
+
+ private fun getOrderString(repeatRule: Int): String {
+ val dayOfMonth = mEventStartDateTime.dayOfMonth
+ var order = (dayOfMonth - 1) / 7 + 1
+ if (order == 4 && isLastWeekDayOfMonth() && repeatRule == REPEAT_ORDER_WEEKDAY_USE_LAST) {
+ order = -1
+ }
+
+ val isMale = isMaleGender(mEventStartDateTime.dayOfWeek)
+ return getString(when (order) {
+ 1 -> if (isMale) R.string.first_m else R.string.first_f
+ 2 -> if (isMale) R.string.second_m else R.string.second_f
+ 3 -> if (isMale) R.string.third_m else R.string.third_f
+ 4 -> if (isMale) R.string.fourth_m else R.string.fourth_f
+ else -> if (isMale) R.string.last_m else R.string.last_f
+ })
+ }
+
+ private fun getDayString(day: Int): String {
+ return getString(when (day) {
+ 1 -> R.string.monday_alt
+ 2 -> R.string.tuesday_alt
+ 3 -> R.string.wednesday_alt
+ 4 -> R.string.thursday_alt
+ 5 -> R.string.friday_alt
+ 6 -> R.string.saturday_alt
+ else -> R.string.sunday_alt
+ })
+ }
+
+ private fun getRepeatXthDayInMonthString(includeBase: Boolean, repeatRule: Int): String {
+ val weekDayString = getRepeatXthDayString(includeBase, repeatRule)
+ val monthString = resources.getStringArray(R.array.in_months)[mEventStartDateTime.monthOfYear - 1]
+ return "$weekDayString $monthString"
+ }
+
+ private fun setRepeatRule(rule: Int) {
+ mRepeatRule = rule
+ checkRepetitionRuleText()
+ if (rule == 0) {
+ setRepeatInterval(0)
+ }
+ }
+
+ private fun checkRepetitionRuleText() {
+ when {
+ mRepeatInterval.isXWeeklyRepetition() -> {
+ event_repetition_rule.text = if (mRepeatRule == EVERY_DAY_BIT) getString(R.string.every_day) else getSelectedDaysString(mRepeatRule)
+ }
+ mRepeatInterval.isXMonthlyRepetition() -> {
+ val repeatString = if (mRepeatRule == REPEAT_ORDER_WEEKDAY_USE_LAST || mRepeatRule == REPEAT_ORDER_WEEKDAY)
+ R.string.repeat else R.string.repeat_on
+
+ event_repetition_rule_label.text = getString(repeatString)
+ event_repetition_rule.text = getMonthlyRepetitionRuleText()
+ }
+ mRepeatInterval.isXYearlyRepetition() -> {
+ val repeatString = if (mRepeatRule == REPEAT_ORDER_WEEKDAY_USE_LAST || mRepeatRule == REPEAT_ORDER_WEEKDAY)
+ R.string.repeat else R.string.repeat_on
+
+ event_repetition_rule_label.text = getString(repeatString)
+ event_repetition_rule.text = getYearlyRepetitionRuleText()
+ }
+ }
+ }
+
+ private fun getMonthlyRepetitionRuleText() = when (mRepeatRule) {
+ REPEAT_SAME_DAY -> getString(R.string.the_same_day)
+ REPEAT_LAST_DAY -> getString(R.string.the_last_day)
+ else -> getRepeatXthDayString(false, mRepeatRule)
+ }
+
+ private fun getYearlyRepetitionRuleText() = when (mRepeatRule) {
+ REPEAT_SAME_DAY -> getString(R.string.the_same_day)
+ else -> getRepeatXthDayInMonthString(false, mRepeatRule)
+ }
+
+ private fun showEventTypeDialog() {
+ hideKeyboard()
+ SelectEventTypeDialog(this, mEventTypeId, false, true, false, true) {
+ mEventTypeId = it.id!!
+ updateEventType()
+ }
+ }
+
+ private fun checkReminderTexts() {
+ updateReminder1Text()
+ updateReminder2Text()
+ updateReminder3Text()
+ updateReminderTypeImages()
+ }
+
+ private fun updateReminder1Text() {
+ event_reminder_1.text = getFormattedMinutes(mReminder1Minutes)
+ }
+
+ private fun updateReminder2Text() {
+ event_reminder_2.apply {
+ beGoneIf(event_reminder_2.isGone() && mReminder1Minutes == REMINDER_OFF)
+ if (mReminder2Minutes == REMINDER_OFF) {
+ text = resources.getString(R.string.add_another_reminder)
+ alpha = 0.4f
+ } else {
+ text = getFormattedMinutes(mReminder2Minutes)
+ alpha = 1f
+ }
+ }
+ }
+
+ private fun updateReminder3Text() {
+ event_reminder_3.apply {
+ beGoneIf(event_reminder_3.isGone() && (mReminder2Minutes == REMINDER_OFF || mReminder1Minutes == REMINDER_OFF))
+ if (mReminder3Minutes == REMINDER_OFF) {
+ text = resources.getString(R.string.add_another_reminder)
+ alpha = 0.4f
+ } else {
+ text = getFormattedMinutes(mReminder3Minutes)
+ alpha = 1f
+ }
+ }
+ }
+
+ private fun showReminderTypePicker(currentValue: Int, callback: (Int) -> Unit) {
+ val items = arrayListOf(
+ RadioItem(REMINDER_NOTIFICATION, getString(R.string.notification)),
+ RadioItem(REMINDER_EMAIL, getString(R.string.email))
+ )
+ RadioGroupDialog(this, items, currentValue) {
+ callback(it as Int)
+ }
+ }
+
+ private fun updateReminderTypeImages() {
+ updateReminderTypeImage(event_reminder_1_type, Reminder(mReminder1Minutes, mReminder1Type))
+ updateReminderTypeImage(event_reminder_2_type, Reminder(mReminder2Minutes, mReminder2Type))
+ updateReminderTypeImage(event_reminder_3_type, Reminder(mReminder3Minutes, mReminder3Type))
+ }
+
+ private fun updateAttendeesVisibility() {
+ val isSyncedEvent = mEventCalendarId != STORED_LOCALLY_ONLY
+ event_attendees_image.beVisibleIf(isSyncedEvent)
+ event_attendees_holder.beVisibleIf(isSyncedEvent)
+ event_attendees_divider.beVisibleIf(isSyncedEvent)
+ }
+
+ private fun updateReminderTypeImage(view: ImageView, reminder: Reminder) {
+ view.beVisibleIf(reminder.minutes != REMINDER_OFF && mEventCalendarId != STORED_LOCALLY_ONLY)
+ val drawable = if (reminder.type == REMINDER_NOTIFICATION) R.drawable.ic_bell_vector else R.drawable.ic_email_vector
+ val icon = resources.getColoredDrawableWithColor(drawable, config.textColor)
+ view.setImageDrawable(icon)
+ }
+
+ private fun updateRepetitionText() {
+ event_repetition.text = getRepetitionText(mRepeatInterval)
+ }
+
+ private fun updateEventType() {
+ ensureBackgroundThread {
+ val eventType = eventTypesDB.getEventTypeWithId(mEventTypeId)
+ if (eventType != null) {
+ runOnUiThread {
+ event_type.text = eventType.title
+ event_type_color.setFillWithStroke(eventType.color, config.backgroundColor)
+ }
+ }
+ }
+ }
+
+ private fun updateCalDAVCalendar() {
+ if (config.caldavSync) {
+ event_caldav_calendar_image.beVisible()
+ event_caldav_calendar_holder.beVisible()
+ event_caldav_calendar_divider.beVisible()
+
+ val calendars = calDAVHelper.getCalDAVCalendars("", true).filter {
+ it.canWrite() && config.getSyncedCalendarIdsAsList().contains(it.id)
+ }
+ updateCurrentCalendarInfo(if (mEventCalendarId == STORED_LOCALLY_ONLY) null else getCalendarWithId(calendars, getCalendarId()))
+
+ event_caldav_calendar_holder.setOnClickListener {
+ hideKeyboard()
+ SelectEventCalendarDialog(this, calendars, mEventCalendarId) {
+ if (mEventCalendarId != STORED_LOCALLY_ONLY && it == STORED_LOCALLY_ONLY) {
+ mEventTypeId = config.lastUsedLocalEventTypeId
+ updateEventType()
+ }
+ mEventCalendarId = it
+ config.lastUsedCaldavCalendarId = it
+ updateCurrentCalendarInfo(getCalendarWithId(calendars, it))
+ updateReminderTypeImages()
+ updateAttendeesVisibility()
+ }
+ }
+ } else {
+ updateCurrentCalendarInfo(null)
+ }
+ }
+
+ private fun getCalendarId() = if (mEvent.source == SOURCE_SIMPLE_CALENDAR) config.lastUsedCaldavCalendarId else mEvent.getCalDAVCalendarId()
+
+ private fun getCalendarWithId(calendars: List, calendarId: Int): CalDAVCalendar? =
+ calendars.firstOrNull { it.id == calendarId }
+
+ private fun updateCurrentCalendarInfo(currentCalendar: CalDAVCalendar?) {
+ event_type_image.beVisibleIf(currentCalendar == null)
+ event_type_holder.beVisibleIf(currentCalendar == null)
+ event_caldav_calendar_divider.beVisibleIf(currentCalendar == null)
+ event_caldav_calendar_email.beGoneIf(currentCalendar == null)
+ event_caldav_calendar_color.beGoneIf(currentCalendar == null)
+
+ if (currentCalendar == null) {
+ mEventCalendarId = STORED_LOCALLY_ONLY
+ val mediumMargin = resources.getDimension(R.dimen.medium_margin).toInt()
+ event_caldav_calendar_name.apply {
+ text = getString(R.string.store_locally_only)
+ setPadding(paddingLeft, paddingTop, paddingRight, mediumMargin)
+ }
+
+ event_caldav_calendar_holder.apply {
+ setPadding(paddingLeft, mediumMargin, paddingRight, mediumMargin)
+ }
+ } else {
+ event_caldav_calendar_email.text = currentCalendar.accountName
+
+ ensureBackgroundThread {
+ val calendarColor = eventsHelper.getEventTypeWithCalDAVCalendarId(currentCalendar.id)?.color ?: currentCalendar.color
+
+ runOnUiThread {
+ event_caldav_calendar_color.setFillWithStroke(calendarColor, config.backgroundColor)
+ event_caldav_calendar_name.apply {
+ text = currentCalendar.displayName
+ setPadding(paddingLeft, paddingTop, paddingRight, resources.getDimension(R.dimen.tiny_margin).toInt())
+ }
+
+ event_caldav_calendar_holder.apply {
+ setPadding(paddingLeft, 0, paddingRight, 0)
+ }
+ }
+ }
+ }
+ }
+
+ private fun resetTime() {
+ if (mEventEndDateTime.isBefore(mEventStartDateTime) &&
+ mEventStartDateTime.dayOfMonth() == mEventEndDateTime.dayOfMonth() &&
+ mEventStartDateTime.monthOfYear() == mEventEndDateTime.monthOfYear()) {
+
+ mEventEndDateTime = mEventEndDateTime.withTime(mEventStartDateTime.hourOfDay, mEventStartDateTime.minuteOfHour, mEventStartDateTime.secondOfMinute, 0)
+ updateEndTimeText()
+ checkStartEndValidity()
+ }
+ }
+
+ private fun toggleAllDay(isChecked: Boolean) {
+ hideKeyboard()
+ event_start_time.beGoneIf(isChecked)
+ event_end_time.beGoneIf(isChecked)
+ resetTime()
+ }
+
+ private fun shareEvent() {
+ shareEvents(arrayListOf(mEvent.id!!))
+ }
+
+ private fun deleteEvent() {
+ if (mEvent.id == null) {
+ return
+ }
+
+ DeleteEventDialog(this, arrayListOf(mEvent.id!!), mEvent.repeatInterval > 0) {
+ ensureBackgroundThread {
+ when (it) {
+ DELETE_SELECTED_OCCURRENCE -> eventsHelper.addEventRepetitionException(mEvent.id!!, mEventOccurrenceTS, true)
+ DELETE_FUTURE_OCCURRENCES -> eventsHelper.addEventRepeatLimit(mEvent.id!!, mEventOccurrenceTS)
+ DELETE_ALL_OCCURRENCES -> eventsHelper.deleteEvent(mEvent.id!!, true)
+ }
+ runOnUiThread {
+ finish()
+ }
+ }
+ }
+ }
+
+ private fun duplicateEvent() {
+ // the activity has the singleTask launchMode to avoid some glitches, so finish it before relaunching
+ finish()
+ Intent(this, EventActivity::class.java).apply {
+ putExtra(EVENT_ID, mEvent.id)
+ putExtra(EVENT_OCCURRENCE_TS, mEventOccurrenceTS)
+ putExtra(IS_DUPLICATE_INTENT, true)
+ startActivity(this)
+ }
+ }
+
+ private fun saveCurrentEvent() {
+ ensureBackgroundThread {
+ saveEvent()
+ }
+ }
+
+ private fun saveEvent() {
+ val newTitle = event_title.value
+ if (newTitle.isEmpty()) {
+ toast(R.string.title_empty)
+ runOnUiThread {
+ event_title.requestFocus()
+ }
+ return
+ }
+
+ val offset = if (!config.allowChangingTimeZones || mEvent.getTimeZoneString().equals(mOriginalTimeZone, true)) {
+ 0
+ } else {
+ val original = if (mOriginalTimeZone.isEmpty()) DateTimeZone.getDefault().id else mOriginalTimeZone
+ (DateTimeZone.forID(mEvent.getTimeZoneString()).getOffset(System.currentTimeMillis()) - DateTimeZone.forID(original).getOffset(System.currentTimeMillis())) / 1000L
+ }
+
+ val newStartTS = mEventStartDateTime.withSecondOfMinute(0).withMillisOfSecond(0).seconds() - offset
+ val newEndTS = mEventEndDateTime.withSecondOfMinute(0).withMillisOfSecond(0).seconds() - offset
+
+ if (newStartTS > newEndTS) {
+ toast(R.string.end_before_start)
+ return
+ }
+
+ val wasRepeatable = mEvent.repeatInterval > 0
+ val oldSource = mEvent.source
+ val newImportId = if (mEvent.id != null) mEvent.importId else UUID.randomUUID().toString().replace("-", "") + System.currentTimeMillis().toString()
+
+ val newEventType = if (!config.caldavSync || config.lastUsedCaldavCalendarId == 0 || mEventCalendarId == STORED_LOCALLY_ONLY) {
+ mEventTypeId
+ } else {
+ calDAVHelper.getCalDAVCalendars("", true).firstOrNull { it.id == mEventCalendarId }?.apply {
+ if (!canWrite()) {
+ runOnUiThread {
+ toast(R.string.insufficient_permissions)
+ }
+ return
+ }
+ }
+
+ eventsHelper.getEventTypeWithCalDAVCalendarId(mEventCalendarId)?.id ?: config.lastUsedLocalEventTypeId
+ }
+
+ val newSource = if (!config.caldavSync || mEventCalendarId == STORED_LOCALLY_ONLY) {
+ config.lastUsedLocalEventTypeId = newEventType
+ SOURCE_SIMPLE_CALENDAR
+ } else {
+ "$CALDAV-$mEventCalendarId"
+ }
+
+ var reminders = arrayListOf(
+ Reminder(mReminder1Minutes, mReminder1Type),
+ Reminder(mReminder2Minutes, mReminder2Type),
+ Reminder(mReminder3Minutes, mReminder3Type)
+ )
+ reminders = reminders.filter { it.minutes != REMINDER_OFF }.sortedBy { it.minutes }.toMutableList() as ArrayList
+
+ val reminder1 = reminders.getOrNull(0) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION)
+ val reminder2 = reminders.getOrNull(1) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION)
+ val reminder3 = reminders.getOrNull(2) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION)
+
+ mReminder1Type = if (mEventCalendarId == STORED_LOCALLY_ONLY) REMINDER_NOTIFICATION else reminder1.type
+ mReminder2Type = if (mEventCalendarId == STORED_LOCALLY_ONLY) REMINDER_NOTIFICATION else reminder2.type
+ mReminder3Type = if (mEventCalendarId == STORED_LOCALLY_ONLY) REMINDER_NOTIFICATION else reminder3.type
+
+ config.apply {
+ if (usePreviousEventReminders) {
+ lastEventReminderMinutes1 = reminder1.minutes
+ lastEventReminderMinutes2 = reminder2.minutes
+ lastEventReminderMinutes3 = reminder3.minutes
+ }
+ }
+
+ mEvent.apply {
+ startTS = newStartTS
+ endTS = newEndTS
+ title = newTitle
+ description = event_description.value
+ reminder1Minutes = reminder1.minutes
+ reminder2Minutes = reminder2.minutes
+ reminder3Minutes = reminder3.minutes
+ reminder1Type = mReminder1Type
+ reminder2Type = mReminder2Type
+ reminder3Type = mReminder3Type
+ repeatInterval = mRepeatInterval
+ importId = newImportId
+ timeZone = if (mEvent.timeZone.isEmpty()) TimeZone.getDefault().id else timeZone
+ flags = mEvent.flags.addBitIf(event_all_day.isChecked, FLAG_ALL_DAY)
+ repeatLimit = if (repeatInterval == 0) 0 else mRepeatLimit
+ repeatRule = mRepeatRule
+ attendees = if (mEventCalendarId == STORED_LOCALLY_ONLY) "" else getAllAttendees(true)
+ eventType = newEventType
+ lastUpdated = System.currentTimeMillis()
+ source = newSource
+ location = event_location.value
+ }
+
+ // recreate the event if it was moved in a different CalDAV calendar
+ if (mEvent.id != null && oldSource != newSource) {
+ eventsHelper.deleteEvent(mEvent.id!!, true)
+ mEvent.id = null
+ }
+
+ storeEvent(wasRepeatable)
+ }
+
+ private fun storeEvent(wasRepeatable: Boolean) {
+ if (mEvent.id == null || mEvent.id == null) {
+ eventsHelper.insertEvent(mEvent, true, true) {
+ if (DateTime.now().isAfter(mEventStartDateTime.millis)) {
+ if (mEvent.repeatInterval == 0 && mEvent.getReminders().any { it.type == REMINDER_NOTIFICATION }) {
+ notifyEvent(mEvent)
+ }
+ }
+
+ finish()
+ }
+ } else {
+ if (mRepeatInterval > 0 && wasRepeatable) {
+ runOnUiThread {
+ showEditRepeatingEventDialog()
+ }
+ } else {
+ eventsHelper.updateEvent(mEvent, true, true) {
+ finish()
+ }
+ }
+ }
+ }
+
+ private fun showEditRepeatingEventDialog() {
+ EditRepeatingEventDialog(this) {
+ if (it) {
+ ensureBackgroundThread {
+ eventsHelper.updateEvent(mEvent, true, true) {
+ finish()
+ }
+ }
+ } else {
+ ensureBackgroundThread {
+ eventsHelper.addEventRepetitionException(mEvent.id!!, mEventOccurrenceTS, true)
+ mEvent.apply {
+ parentId = id!!.toLong()
+ id = null
+ repeatRule = 0
+ repeatInterval = 0
+ repeatLimit = 0
+ }
+
+ eventsHelper.insertEvent(mEvent, true, true) {
+ finish()
+ }
+ }
+ }
+ }
+ }
+
+ private fun updateStartTexts() {
+ updateStartDateText()
+ updateStartTimeText()
+ }
+
+ private fun updateStartDateText() {
+ event_start_date.text = Formatter.getDate(applicationContext, mEventStartDateTime)
+ checkStartEndValidity()
+ }
+
+ private fun updateStartTimeText() {
+ event_start_time.text = Formatter.getTime(this, mEventStartDateTime)
+ checkStartEndValidity()
+ }
+
+ private fun updateEndTexts() {
+ updateEndDateText()
+ updateEndTimeText()
+ }
+
+ private fun updateEndDateText() {
+ event_end_date.text = Formatter.getDate(applicationContext, mEventEndDateTime)
+ checkStartEndValidity()
+ }
+
+ private fun updateEndTimeText() {
+ event_end_time.text = Formatter.getTime(this, mEventEndDateTime)
+ checkStartEndValidity()
+ }
+
+ private fun updateTimeZoneText() {
+ event_time_zone.text = mEvent.getTimeZoneString()
+ }
+
+ private fun checkStartEndValidity() {
+ val textColor = if (mEventStartDateTime.isAfter(mEventEndDateTime)) resources.getColor(R.color.red_text) else config.textColor
+ event_end_date.setTextColor(textColor)
+ event_end_time.setTextColor(textColor)
+ }
+
+ private fun showOnMap() {
+ if (event_location.value.isEmpty()) {
+ toast(R.string.please_fill_location)
+ return
+ }
+
+ val pattern = Pattern.compile(LAT_LON_PATTERN)
+ val locationValue = event_location.value
+ val uri = if (pattern.matcher(locationValue).find()) {
+ val delimiter = if (locationValue.contains(';')) ";" else ","
+ val parts = locationValue.split(delimiter)
+ val latitude = parts.first()
+ val longitude = parts.last()
+ Uri.parse("geo:$latitude,$longitude")
+ } else {
+ val location = Uri.encode(locationValue)
+ Uri.parse("geo:0,0?q=$location")
+ }
+
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ if (intent.resolveActivity(packageManager) != null) {
+ startActivity(intent)
+ } else {
+ toast(R.string.no_app_found)
+ }
+ }
+
+ private fun setupStartDate() {
+ hideKeyboard()
+ config.backgroundColor.getContrastColor()
+ val datepicker = DatePickerDialog(this, mDialogTheme, startDateSetListener, mEventStartDateTime.year, mEventStartDateTime.monthOfYear - 1,
+ mEventStartDateTime.dayOfMonth)
+
+ datepicker.datePicker.firstDayOfWeek = if (config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY
+ datepicker.show()
+ }
+
+ private fun setupStartTime() {
+ hideKeyboard()
+ TimePickerDialog(this, mDialogTheme, startTimeSetListener, mEventStartDateTime.hourOfDay, mEventStartDateTime.minuteOfHour, config.use24HourFormat).show()
+ }
+
+ private fun setupEndDate() {
+ hideKeyboard()
+ val datepicker = DatePickerDialog(this, mDialogTheme, endDateSetListener, mEventEndDateTime.year, mEventEndDateTime.monthOfYear - 1,
+ mEventEndDateTime.dayOfMonth)
+
+ datepicker.datePicker.firstDayOfWeek = if (config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY
+ datepicker.show()
+ }
+
+ private fun setupEndTime() {
+ hideKeyboard()
+ TimePickerDialog(this, mDialogTheme, endTimeSetListener, mEventEndDateTime.hourOfDay, mEventEndDateTime.minuteOfHour, config.use24HourFormat).show()
+ }
+
+ private val startDateSetListener = DatePickerDialog.OnDateSetListener { view, year, monthOfYear, dayOfMonth ->
+ dateSet(year, monthOfYear, dayOfMonth, true)
+ }
+
+ private val startTimeSetListener = TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute ->
+ timeSet(hourOfDay, minute, true)
+ }
+
+ private val endDateSetListener = DatePickerDialog.OnDateSetListener { view, year, monthOfYear, dayOfMonth -> dateSet(year, monthOfYear, dayOfMonth, false) }
+
+ private val endTimeSetListener = TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute -> timeSet(hourOfDay, minute, false) }
+
+ private fun dateSet(year: Int, month: Int, day: Int, isStart: Boolean) {
+ if (isStart) {
+ val diff = mEventEndDateTime.seconds() - mEventStartDateTime.seconds()
+
+ mEventStartDateTime = mEventStartDateTime.withDate(year, month + 1, day)
+ updateStartDateText()
+ checkRepeatRule()
+
+ mEventEndDateTime = mEventStartDateTime.plusSeconds(diff.toInt())
+ updateEndTexts()
+ } else {
+ mEventEndDateTime = mEventEndDateTime.withDate(year, month + 1, day)
+ updateEndDateText()
+ }
+ }
+
+ private fun timeSet(hours: Int, minutes: Int, isStart: Boolean) {
+ try {
+ if (isStart) {
+ val diff = mEventEndDateTime.seconds() - mEventStartDateTime.seconds()
+
+ mEventStartDateTime = mEventStartDateTime.withHourOfDay(hours).withMinuteOfHour(minutes)
+ updateStartTimeText()
+
+ mEventEndDateTime = mEventStartDateTime.plusSeconds(diff.toInt())
+ updateEndTexts()
+ } else {
+ mEventEndDateTime = mEventEndDateTime.withHourOfDay(hours).withMinuteOfHour(minutes)
+ updateEndTimeText()
+ }
+ } catch (e: Exception) {
+ timeSet(hours + 1, minutes, isStart)
+ return
+ }
+ }
+
+ private fun setupTimeZone() {
+ Intent(this, SelectTimeZoneActivity::class.java).apply {
+ putExtra(CURRENT_TIME_ZONE, mEvent.getTimeZoneString())
+ startActivityForResult(this, SELECT_TIME_ZONE_INTENT)
+ }
+ }
+
+ private fun checkRepeatRule() {
+ if (mRepeatInterval.isXWeeklyRepetition()) {
+ val day = mRepeatRule
+ if (day == MONDAY_BIT || day == TUESDAY_BIT || day == WEDNESDAY_BIT || day == THURSDAY_BIT || day == FRIDAY_BIT || day == SATURDAY_BIT || day == SUNDAY_BIT) {
+ setRepeatRule(Math.pow(2.0, (mEventStartDateTime.dayOfWeek - 1).toDouble()).toInt())
+ }
+ } else if (mRepeatInterval.isXMonthlyRepetition() || mRepeatInterval.isXYearlyRepetition()) {
+ if (mRepeatRule == REPEAT_LAST_DAY && !isLastDayOfTheMonth()) {
+ mRepeatRule = REPEAT_SAME_DAY
+ }
+ checkRepetitionRuleText()
+ }
+ }
+
+ private fun fillAvailableContacts() {
+ mAvailableContacts = getEmails()
+
+ val names = getNames()
+ mAvailableContacts.forEach {
+ val contactId = it.contactId
+ val contact = names.firstOrNull { it.contactId == contactId }
+ val name = contact?.name
+ if (name != null) {
+ it.name = name
+ }
+
+ val photoUri = contact?.photoUri
+ if (photoUri != null) {
+ it.photoUri = photoUri
+ }
+ }
+ }
+
+ private fun updateAttendees() {
+ val currentCalendar = calDAVHelper.getCalDAVCalendars("", true).firstOrNull { it.id == mEventCalendarId }
+ mAttendees.forEach {
+ it.isMe = it.email == currentCalendar?.accountName
+ }
+
+ mAttendees.sortWith(compareBy
+ { it.isMe }.thenBy
+ { it.status == Attendees.ATTENDEE_STATUS_ACCEPTED }.thenBy
+ { it.status == Attendees.ATTENDEE_STATUS_DECLINED }.thenBy
+ { it.status == Attendees.ATTENDEE_STATUS_TENTATIVE }.thenBy
+ { it.status })
+ mAttendees.reverse()
+
+ mAttendees.forEach {
+ val attendee = it
+ val deviceContact = mAvailableContacts.firstOrNull { it.email.isNotEmpty() && it.email == attendee.email && it.photoUri.isNotEmpty() }
+ if (deviceContact != null) {
+ attendee.photoUri = deviceContact.photoUri
+ }
+ addAttendee(attendee)
+ }
+ addAttendee()
+
+ val imageHeight = event_repetition_image.height
+ if (imageHeight > 0) {
+ event_attendees_image.layoutParams.height = imageHeight
+ } else {
+ event_repetition_image.onGlobalLayout {
+ event_attendees_image.layoutParams.height = event_repetition_image.height
+ }
+ }
+ }
+
+ private fun addAttendee(attendee: Attendee? = null) {
+ val attendeeHolder = layoutInflater.inflate(R.layout.item_attendee, event_attendees_holder, false) as RelativeLayout
+ val autoCompleteView = attendeeHolder.event_attendee
+ val selectedAttendeeHolder = attendeeHolder.event_contact_attendee
+ val selectedAttendeeDismiss = attendeeHolder.event_contact_dismiss
+
+ mAttendeeAutoCompleteViews.add(autoCompleteView)
+ autoCompleteView.onTextChangeListener {
+ if (mWasContactsPermissionChecked) {
+ checkNewAttendeeField()
+ } else {
+ handlePermission(PERMISSION_READ_CONTACTS) {
+ checkNewAttendeeField()
+ mWasContactsPermissionChecked = true
+ }
+ }
+ }
+
+ event_attendees_holder.addView(attendeeHolder)
+
+ val textColor = config.textColor
+ autoCompleteView.setColors(textColor, getAdjustedPrimaryColor(), config.backgroundColor)
+ selectedAttendeeHolder.event_contact_name.setColors(textColor, getAdjustedPrimaryColor(), config.backgroundColor)
+ selectedAttendeeHolder.event_contact_me_status.setColors(textColor, getAdjustedPrimaryColor(), config.backgroundColor)
+ selectedAttendeeDismiss.applyColorFilter(textColor)
+
+ selectedAttendeeDismiss.setOnClickListener {
+ attendeeHolder.beGone()
+ mSelectedContacts = mSelectedContacts.filter { it.toString() != selectedAttendeeDismiss.tag }.toMutableList() as ArrayList
+ }
+
+ val adapter = AutoCompleteTextViewAdapter(this, mAvailableContacts)
+ autoCompleteView.setAdapter(adapter)
+ autoCompleteView.imeOptions = EditorInfo.IME_ACTION_NEXT
+ autoCompleteView.setOnItemClickListener { parent, view, position, id ->
+ val currAttendees = (autoCompleteView.adapter as AutoCompleteTextViewAdapter).resultList
+ val selectedAttendee = currAttendees[position]
+ addSelectedAttendee(selectedAttendee, autoCompleteView, selectedAttendeeHolder)
+ }
+
+ if (attendee != null) {
+ addSelectedAttendee(attendee, autoCompleteView, selectedAttendeeHolder)
+ }
+ }
+
+ private fun addSelectedAttendee(attendee: Attendee, autoCompleteView: MyAutoCompleteTextView, selectedAttendeeHolder: RelativeLayout) {
+ mSelectedContacts.add(attendee)
+
+ autoCompleteView.beGone()
+ autoCompleteView.focusSearch(View.FOCUS_DOWN)?.requestFocus()
+
+ selectedAttendeeHolder.apply {
+ beVisible()
+
+ val attendeeStatusBackground = resources.getDrawable(R.drawable.attendee_status_circular_background)
+ (attendeeStatusBackground as LayerDrawable).findDrawableByLayerId(R.id.attendee_status_circular_background).applyColorFilter(config.backgroundColor)
+ event_contact_status_image.apply {
+ background = attendeeStatusBackground
+ setImageDrawable(getAttendeeStatusImage(attendee))
+ beVisibleIf(attendee.showStatusImage())
+ }
+
+ event_contact_name.text = if (attendee.isMe) getString(R.string.my_status) else attendee.getPublicName()
+ if (attendee.isMe) {
+ (event_contact_name.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.START_OF, event_contact_me_status.id)
+ }
+
+ val placeholder = BitmapDrawable(resources, SimpleContactsHelper(context).getContactLetterIcon(event_contact_name.value))
+ event_contact_image.apply {
+ attendee.updateImage(applicationContext, this, placeholder)
+ beVisible()
+ }
+
+ event_contact_dismiss.apply {
+ tag = attendee.toString()
+ beGoneIf(attendee.isMe)
+ }
+
+ if (attendee.isMe) {
+ updateAttendeeMe(this, attendee)
+ }
+
+ event_contact_me_status.apply {
+ beVisibleIf(attendee.isMe)
+ }
+
+ if (attendee.isMe) {
+ event_contact_attendee.setOnClickListener {
+ val items = arrayListOf(
+ RadioItem(Attendees.ATTENDEE_STATUS_ACCEPTED, getString(R.string.going)),
+ RadioItem(Attendees.ATTENDEE_STATUS_DECLINED, getString(R.string.not_going)),
+ RadioItem(Attendees.ATTENDEE_STATUS_TENTATIVE, getString(R.string.maybe_going))
+ )
+
+ RadioGroupDialog(this@EventActivity, items, attendee.status) {
+ attendee.status = it as Int
+ updateAttendeeMe(this, attendee)
+ }
+ }
+ }
+ }
+ }
+
+ private fun getAttendeeStatusImage(attendee: Attendee): Drawable {
+ return resources.getDrawable(when (attendee.status) {
+ Attendees.ATTENDEE_STATUS_ACCEPTED -> R.drawable.ic_check_green
+ Attendees.ATTENDEE_STATUS_DECLINED -> R.drawable.ic_cross_red
+ else -> R.drawable.ic_question_yellow
+ })
+ }
+
+ private fun updateAttendeeMe(holder: RelativeLayout, attendee: Attendee) {
+ holder.apply {
+ event_contact_me_status.text = getString(when (attendee.status) {
+ Attendees.ATTENDEE_STATUS_ACCEPTED -> R.string.going
+ Attendees.ATTENDEE_STATUS_DECLINED -> R.string.not_going
+ Attendees.ATTENDEE_STATUS_TENTATIVE -> R.string.maybe_going
+ else -> R.string.invited
+ })
+
+ event_contact_status_image.apply {
+ beVisibleIf(attendee.showStatusImage())
+ setImageDrawable(getAttendeeStatusImage(attendee))
+ }
+
+ mAttendees.firstOrNull { it.isMe }?.status = attendee.status
+ }
+ }
+
+ private fun checkNewAttendeeField() {
+ if (mAttendeeAutoCompleteViews.none { it.isVisible() && it.value.isEmpty() }) {
+ addAttendee()
+ }
+ }
+
+ private fun getAllAttendees(isSavingEvent: Boolean): String {
+ var attendees = ArrayList()
+ mSelectedContacts.forEach {
+ attendees.add(it)
+ }
+
+ val customEmails = mAttendeeAutoCompleteViews.filter { it.isVisible() }.map { it.value }.filter { it.isNotEmpty() }.toMutableList() as ArrayList
+ customEmails.mapTo(attendees) {
+ Attendee(0, "", it, Attendees.ATTENDEE_STATUS_INVITED, "", false, Attendees.RELATIONSHIP_NONE)
+ }
+ attendees = attendees.distinctBy { it.email }.toMutableList() as ArrayList
+
+ if (mEvent.id == null && isSavingEvent && attendees.isNotEmpty()) {
+ val currentCalendar = calDAVHelper.getCalDAVCalendars("", true).firstOrNull { it.id == mEventCalendarId }
+ mAvailableContacts.firstOrNull { it.email == currentCalendar?.accountName }?.apply {
+ attendees = attendees.filter { it.email != currentCalendar?.accountName }.toMutableList() as ArrayList
+ status = Attendees.ATTENDEE_STATUS_ACCEPTED
+ relationship = Attendees.RELATIONSHIP_ORGANIZER
+ attendees.add(this)
+ }
+ }
+
+ return Gson().toJson(attendees)
+ }
+
+ private fun getNames(): List {
+ val contacts = ArrayList()
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(
+ Data.CONTACT_ID,
+ StructuredName.PREFIX,
+ StructuredName.GIVEN_NAME,
+ StructuredName.MIDDLE_NAME,
+ StructuredName.FAMILY_NAME,
+ StructuredName.SUFFIX,
+ StructuredName.PHOTO_THUMBNAIL_URI)
+
+ val selection = "${Data.MIMETYPE} = ?"
+ val selectionArgs = arrayOf(StructuredName.CONTENT_ITEM_TYPE)
+
+ queryCursor(uri, projection, selection, selectionArgs) { cursor ->
+ val id = cursor.getIntValue(Data.CONTACT_ID)
+ val prefix = cursor.getStringValue(StructuredName.PREFIX) ?: ""
+ val firstName = cursor.getStringValue(StructuredName.GIVEN_NAME) ?: ""
+ val middleName = cursor.getStringValue(StructuredName.MIDDLE_NAME) ?: ""
+ val surname = cursor.getStringValue(StructuredName.FAMILY_NAME) ?: ""
+ val suffix = cursor.getStringValue(StructuredName.SUFFIX) ?: ""
+ val photoUri = cursor.getStringValue(StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
+
+ val names = arrayListOf(prefix, firstName, middleName, surname, suffix).filter { it.trim().isNotEmpty() }
+ val fullName = TextUtils.join(" ", names).trim()
+ if (fullName.isNotEmpty() || photoUri.isNotEmpty()) {
+ val contact = Attendee(id, fullName, "", Attendees.ATTENDEE_STATUS_NONE, photoUri, false, Attendees.RELATIONSHIP_NONE)
+ contacts.add(contact)
+ }
+ }
+ return contacts
+ }
+
+ private fun getEmails(): ArrayList {
+ val contacts = ArrayList()
+ val uri = CommonDataKinds.Email.CONTENT_URI
+ val projection = arrayOf(
+ Data.CONTACT_ID,
+ CommonDataKinds.Email.DATA
+ )
+
+ queryCursor(uri, projection) { cursor ->
+ val id = cursor.getIntValue(Data.CONTACT_ID)
+ val email = cursor.getStringValue(CommonDataKinds.Email.DATA) ?: return@queryCursor
+ val contact = Attendee(id, "", email, Attendees.ATTENDEE_STATUS_NONE, "", false, Attendees.RELATIONSHIP_NONE)
+ contacts.add(contact)
+ }
+
+ return contacts
+ }
+
+ private fun updateIconColors() {
+ event_show_on_map.applyColorFilter(getAdjustedPrimaryColor())
+ val textColor = config.textColor
+ arrayOf(event_time_image, event_time_zone_image, event_repetition_image, event_reminder_image, event_type_image, event_caldav_calendar_image,
+ event_reminder_1_type, event_reminder_2_type, event_reminder_3_type, event_attendees_image).forEach {
+ it.applyColorFilter(textColor)
+ }
+ }
+}
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
new file mode 100644
index 000000000..87efc1937
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt
@@ -0,0 +1,1007 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.SearchManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.pm.ShortcutManager
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.LayerDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.provider.ContactsContract.*
+import android.view.Menu
+import android.view.MenuItem
+import android.widget.Toast
+import androidx.appcompat.widget.SearchView
+import androidx.core.view.MenuItemCompat
+import com.simplemobiletools.calendar.pro.BuildConfig
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.adapters.EventListAdapter
+import com.simplemobiletools.calendar.pro.databases.EventsDatabase
+import com.simplemobiletools.calendar.pro.dialogs.ExportEventsDialog
+import com.simplemobiletools.calendar.pro.dialogs.FilterEventTypesDialog
+import com.simplemobiletools.calendar.pro.dialogs.ImportEventsDialog
+import com.simplemobiletools.calendar.pro.dialogs.SetRemindersDialog
+import com.simplemobiletools.calendar.pro.extensions.*
+import com.simplemobiletools.calendar.pro.fragments.*
+import com.simplemobiletools.calendar.pro.helpers.*
+import com.simplemobiletools.calendar.pro.helpers.Formatter
+import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult
+import com.simplemobiletools.calendar.pro.helpers.IcsImporter.ImportResult
+import com.simplemobiletools.calendar.pro.jobs.CalDAVUpdateListener
+import com.simplemobiletools.calendar.pro.models.Event
+import com.simplemobiletools.calendar.pro.models.EventType
+import com.simplemobiletools.calendar.pro.models.ListEvent
+import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.dialogs.FilePickerDialog
+import com.simplemobiletools.commons.dialogs.RadioGroupDialog
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
+import com.simplemobiletools.commons.models.FAQItem
+import com.simplemobiletools.commons.models.RadioItem
+import com.simplemobiletools.commons.models.Release
+import kotlinx.android.synthetic.main.activity_main.*
+import org.joda.time.DateTime
+import org.joda.time.DateTimeZone
+import java.io.FileOutputStream
+import java.io.OutputStream
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.collections.ArrayList
+
+class MainActivity : SimpleActivity(), RefreshRecyclerViewListener {
+ private val PICK_IMPORT_SOURCE_INTENT = 1
+ private val PICK_EXPORT_FILE_INTENT = 2
+
+ private var showCalDAVRefreshToast = false
+ private var mShouldFilterBeVisible = false
+ private var mIsSearchOpen = false
+ private var mLatestSearchQuery = ""
+ private var mSearchMenuItem: MenuItem? = null
+ private var shouldGoToTodayBeVisible = false
+ private var goToTodayButton: MenuItem? = null
+ private var currentFragments = ArrayList()
+ private var eventTypesToExport = ArrayList()
+
+ private var mStoredTextColor = 0
+ private var mStoredBackgroundColor = 0
+ private var mStoredPrimaryColor = 0
+ private var mStoredDayCode = ""
+ private var mStoredIsSundayFirst = false
+ private var mStoredUse24HourFormat = false
+ private var mStoredDimPastEvents = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ appLaunched(BuildConfig.APPLICATION_ID)
+
+ checkWhatsNewDialog()
+ calendar_fab.beVisibleIf(config.storedView != YEARLY_VIEW)
+ calendar_fab.setOnClickListener {
+ launchNewEventIntent(currentFragments.last().getNewEventDayCode())
+ }
+
+ storeStateVariables()
+
+ if (!hasPermission(PERMISSION_WRITE_CALENDAR) || !hasPermission(PERMISSION_READ_CALENDAR)) {
+ config.caldavSync = false
+ }
+
+ if (config.caldavSync) {
+ refreshCalDAVCalendars(false)
+ }
+
+ swipe_refresh_layout.setOnRefreshListener {
+ refreshCalDAVCalendars(false)
+ }
+
+ checkIsViewIntent()
+
+ if (!checkIsOpenIntent()) {
+ updateViewPager()
+ }
+
+ checkAppOnSDCard()
+
+ if (savedInstanceState == null) {
+ checkCalDAVUpdateListener()
+ }
+
+ if (!config.wasUpgradedFromFreeShown && isPackageInstalled("com.simplemobiletools.calendar")) {
+ ConfirmationDialog(this, "", R.string.upgraded_from_free, R.string.ok, 0) {}
+ config.wasUpgradedFromFreeShown = true
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (mStoredTextColor != config.textColor || mStoredBackgroundColor != config.backgroundColor || mStoredPrimaryColor != config.primaryColor
+ || mStoredDayCode != Formatter.getTodayCode() || mStoredDimPastEvents != config.dimPastEvents) {
+ updateViewPager()
+ }
+
+ eventsHelper.getEventTypes(this, false) {
+ val newShouldFilterBeVisible = it.size > 1 || config.displayEventTypes.isEmpty()
+ if (newShouldFilterBeVisible != mShouldFilterBeVisible) {
+ mShouldFilterBeVisible = newShouldFilterBeVisible
+ }
+ }
+
+ if (config.storedView == WEEKLY_VIEW) {
+ if (mStoredIsSundayFirst != config.isSundayFirst || mStoredUse24HourFormat != config.use24HourFormat) {
+ updateViewPager()
+ }
+ }
+
+ storeStateVariables()
+ updateWidgets()
+ updateTextColors(calendar_coordinator)
+
+ search_holder.background = ColorDrawable(config.backgroundColor)
+ checkSwipeRefreshAvailability()
+ checkShortcuts()
+ invalidateOptionsMenu()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ storeStateVariables()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ closeSearch()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ if (!isChangingConfigurations) {
+ EventsDatabase.destroyInstance()
+ stopCalDAVUpdateListener()
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_main, menu)
+ menu.apply {
+ goToTodayButton = findItem(R.id.go_to_today)
+ findItem(R.id.filter).isVisible = mShouldFilterBeVisible
+ findItem(R.id.go_to_today).isVisible = (shouldGoToTodayBeVisible || config.storedView == EVENTS_LIST_VIEW) && !mIsSearchOpen
+ findItem(R.id.go_to_date).isVisible = config.storedView != EVENTS_LIST_VIEW
+ }
+
+ setupSearch(menu)
+ updateMenuItemColors(menu)
+ return true
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu): Boolean {
+ menu.apply {
+ findItem(R.id.refresh_caldav_calendars).isVisible = config.caldavSync
+ }
+
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.change_view -> showViewDialog()
+ R.id.go_to_today -> goToToday()
+ R.id.go_to_date -> showGoToDateDialog()
+ R.id.filter -> showFilterDialog()
+ R.id.refresh_caldav_calendars -> refreshCalDAVCalendars(true)
+ R.id.add_holidays -> addHolidays()
+ R.id.add_birthdays -> tryAddBirthdays()
+ R.id.add_anniversaries -> tryAddAnniversaries()
+ R.id.import_events -> tryImportEvents()
+ R.id.export_events -> tryExportEvents()
+ R.id.settings -> launchSettings()
+ R.id.about -> launchAbout()
+ android.R.id.home -> onBackPressed()
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun onBackPressed() {
+ swipe_refresh_layout.isRefreshing = false
+ checkSwipeRefreshAvailability()
+ if (currentFragments.size > 1) {
+ removeTopFragment()
+ } else {
+ super.onBackPressed()
+ }
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ checkIsOpenIntent()
+ checkIsViewIntent()
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+ super.onActivityResult(requestCode, resultCode, resultData)
+ if (requestCode == PICK_IMPORT_SOURCE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
+ tryImportEventsFromFile(resultData.data!!)
+ } else if (requestCode == PICK_EXPORT_FILE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
+ val outputStream = contentResolver.openOutputStream(resultData.data!!)
+ exportEventsTo(eventTypesToExport, outputStream)
+ }
+ }
+
+ private fun storeStateVariables() {
+ config.apply {
+ mStoredIsSundayFirst = isSundayFirst
+ mStoredTextColor = textColor
+ mStoredPrimaryColor = primaryColor
+ mStoredBackgroundColor = backgroundColor
+ mStoredUse24HourFormat = use24HourFormat
+ mStoredDimPastEvents = dimPastEvents
+ }
+ mStoredDayCode = Formatter.getTodayCode()
+ }
+
+ private fun setupSearch(menu: Menu) {
+ val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
+ mSearchMenuItem = menu.findItem(R.id.search)
+ (mSearchMenuItem!!.actionView as SearchView).apply {
+ setSearchableInfo(searchManager.getSearchableInfo(componentName))
+ isSubmitButtonEnabled = false
+ setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String) = false
+
+ override fun onQueryTextChange(newText: String): Boolean {
+ if (mIsSearchOpen) {
+ searchQueryChanged(newText)
+ }
+ return true
+ }
+ })
+ }
+
+ MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener {
+ override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
+ mIsSearchOpen = true
+ search_holder.beVisible()
+ calendar_fab.beGone()
+ searchQueryChanged("")
+ invalidateOptionsMenu()
+ return true
+ }
+
+ override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
+ mIsSearchOpen = false
+ search_holder.beGone()
+ calendar_fab.beVisibleIf(currentFragments.last() !is YearFragmentsHolder)
+ invalidateOptionsMenu()
+ return true
+ }
+ })
+ }
+
+ private fun closeSearch() {
+ mSearchMenuItem?.collapseActionView()
+ }
+
+ private fun checkCalDAVUpdateListener() {
+ if (isNougatPlus()) {
+ val updateListener = CalDAVUpdateListener()
+ if (config.caldavSync) {
+ if (!updateListener.isScheduled(applicationContext)) {
+ updateListener.scheduleJob(applicationContext)
+ }
+ } else {
+ updateListener.cancelJob(applicationContext)
+ }
+ }
+ }
+
+ private fun stopCalDAVUpdateListener() {
+ if (isNougatPlus()) {
+ if (!config.caldavSync) {
+ val updateListener = CalDAVUpdateListener()
+ updateListener.cancelJob(applicationContext)
+ }
+ }
+ }
+
+ @SuppressLint("NewApi")
+ private fun checkShortcuts() {
+ val appIconColor = config.appIconColor
+ if (isNougatMR1Plus() && config.lastHandledShortcutColor != appIconColor) {
+ val newEvent = getString(R.string.new_event)
+ val manager = getSystemService(ShortcutManager::class.java)
+ val drawable = resources.getDrawable(R.drawable.shortcut_plus)
+ (drawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_plus_background).applyColorFilter(appIconColor)
+ val bmp = drawable.convertToBitmap()
+
+ val intent = Intent(this, SplashActivity::class.java)
+ intent.action = SHORTCUT_NEW_EVENT
+ val shortcut = ShortcutInfo.Builder(this, "new_event")
+ .setShortLabel(newEvent)
+ .setLongLabel(newEvent)
+ .setIcon(Icon.createWithBitmap(bmp))
+ .setIntent(intent)
+ .build()
+
+ try {
+ manager.dynamicShortcuts = Arrays.asList(shortcut)
+ config.lastHandledShortcutColor = appIconColor
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ private fun checkIsOpenIntent(): Boolean {
+ val dayCodeToOpen = intent.getStringExtra(DAY_CODE) ?: ""
+ val viewToOpen = intent.getIntExtra(VIEW_TO_OPEN, DAILY_VIEW)
+ intent.removeExtra(VIEW_TO_OPEN)
+ intent.removeExtra(DAY_CODE)
+ if (dayCodeToOpen.isNotEmpty()) {
+ calendar_fab.beVisible()
+ if (viewToOpen != LAST_VIEW) {
+ config.storedView = viewToOpen
+ }
+ updateViewPager(dayCodeToOpen)
+ return true
+ }
+
+ val eventIdToOpen = intent.getLongExtra(EVENT_ID, 0L)
+ val eventOccurrenceToOpen = intent.getLongExtra(EVENT_OCCURRENCE_TS, 0L)
+ intent.removeExtra(EVENT_ID)
+ intent.removeExtra(EVENT_OCCURRENCE_TS)
+ if (eventIdToOpen != 0L && eventOccurrenceToOpen != 0L) {
+ Intent(this, EventActivity::class.java).apply {
+ putExtra(EVENT_ID, eventIdToOpen)
+ putExtra(EVENT_OCCURRENCE_TS, eventOccurrenceToOpen)
+ startActivity(this)
+ }
+ }
+
+ return false
+ }
+
+ private fun checkIsViewIntent() {
+ if (intent?.action == Intent.ACTION_VIEW && intent.data != null) {
+ val uri = intent.data
+ if (uri?.authority?.equals("com.android.calendar") == true || uri?.authority?.substringAfter("@") == "com.android.calendar") {
+ if (uri.path!!.startsWith("/events")) {
+ ensureBackgroundThread {
+ // intents like content://com.android.calendar/events/1756
+ val eventId = uri.lastPathSegment
+ val id = eventsDB.getEventIdWithLastImportId("%-$eventId")
+ if (id != null) {
+ Intent(this, EventActivity::class.java).apply {
+ putExtra(EVENT_ID, id)
+ startActivity(this)
+ }
+ } else {
+ toast(R.string.caldav_event_not_found, Toast.LENGTH_LONG)
+ }
+ }
+ } else if (uri.path!!.startsWith("/time") || intent?.extras?.getBoolean("DETAIL_VIEW", false) == true) {
+ // clicking date on a third party widget: content://com.android.calendar/time/1507309245683
+ // or content://0@com.android.calendar/time/1584958526435
+ val timestamp = uri.pathSegments.last()
+ if (timestamp.areDigitsOnly()) {
+ openDayAt(timestamp.toLong())
+ return
+ }
+ }
+ } else {
+ tryImportEventsFromFile(uri!!)
+ }
+ }
+ }
+
+ private fun showViewDialog() {
+ val items = arrayListOf(
+ RadioItem(DAILY_VIEW, getString(R.string.daily_view)),
+ RadioItem(WEEKLY_VIEW, getString(R.string.weekly_view)),
+ RadioItem(MONTHLY_VIEW, getString(R.string.monthly_view)),
+ RadioItem(YEARLY_VIEW, getString(R.string.yearly_view)),
+ RadioItem(EVENTS_LIST_VIEW, getString(R.string.simple_event_list)))
+
+ RadioGroupDialog(this, items, config.storedView) {
+ calendar_fab.beVisibleIf(it as Int != YEARLY_VIEW)
+ resetActionBarTitle()
+ closeSearch()
+ updateView(it)
+ shouldGoToTodayBeVisible = false
+ invalidateOptionsMenu()
+ }
+ }
+
+ private fun goToToday() {
+ currentFragments.last().goToToday()
+ }
+
+ fun showGoToDateDialog() {
+ currentFragments.last().showGoToDateDialog()
+ }
+
+ private fun resetActionBarTitle() {
+ updateActionBarTitle(getString(R.string.app_launcher_name))
+ updateActionBarSubtitle("")
+ }
+
+ private fun showFilterDialog() {
+ FilterEventTypesDialog(this) {
+ refreshViewPager()
+ updateWidgets()
+ }
+ }
+
+ fun toggleGoToTodayVisibility(beVisible: Boolean) {
+ shouldGoToTodayBeVisible = beVisible
+ if (goToTodayButton?.isVisible != beVisible) {
+ invalidateOptionsMenu()
+ }
+ }
+
+ private fun refreshCalDAVCalendars(showRefreshToast: Boolean) {
+ showCalDAVRefreshToast = showRefreshToast
+ if (showRefreshToast) {
+ toast(R.string.refreshing)
+ }
+
+ syncCalDAVCalendars {
+ calDAVHelper.refreshCalendars(true) {
+ calDAVChanged()
+ }
+ }
+ }
+
+ private fun calDAVChanged() {
+ refreshViewPager()
+ if (showCalDAVRefreshToast) {
+ toast(R.string.refreshing_complete)
+ }
+ runOnUiThread {
+ swipe_refresh_layout.isRefreshing = false
+ }
+ }
+
+ private fun addHolidays() {
+ val items = getHolidayRadioItems()
+ RadioGroupDialog(this, items) {
+ toast(R.string.importing)
+ ensureBackgroundThread {
+ val holidays = getString(R.string.holidays)
+ var eventTypeId = eventsHelper.getEventTypeIdWithTitle(holidays)
+ if (eventTypeId == -1L) {
+ val eventType = EventType(null, holidays, resources.getColor(R.color.default_holidays_color))
+ eventTypeId = eventsHelper.insertOrUpdateEventTypeSync(eventType)
+ }
+
+ val result = IcsImporter(this).importEvents(it as String, eventTypeId, 0, false)
+ handleParseResult(result)
+ if (result != ImportResult.IMPORT_FAIL) {
+ runOnUiThread {
+ updateViewPager()
+ }
+ }
+ }
+ }
+ }
+
+ private fun tryAddBirthdays() {
+ handlePermission(PERMISSION_READ_CONTACTS) {
+ if (it) {
+ SetRemindersDialog(this) {
+ val reminders = it
+ ensureBackgroundThread {
+ addContactEvents(true, reminders) {
+ when {
+ it > 0 -> {
+ toast(R.string.birthdays_added)
+ updateViewPager()
+ }
+ it == -1 -> toast(R.string.no_new_birthdays)
+ else -> toast(R.string.no_birthdays)
+ }
+ }
+ }
+ }
+ } else {
+ toast(R.string.no_contacts_permission)
+ }
+ }
+ }
+
+ private fun tryAddAnniversaries() {
+ handlePermission(PERMISSION_READ_CONTACTS) {
+ if (it) {
+ SetRemindersDialog(this) {
+ val reminders = it
+ ensureBackgroundThread {
+ addContactEvents(false, reminders) {
+ when {
+ it > 0 -> {
+ toast(R.string.anniversaries_added)
+ updateViewPager()
+ }
+ it == -1 -> toast(R.string.no_new_anniversaries)
+ else -> toast(R.string.no_anniversaries)
+ }
+ }
+ }
+ }
+ } else {
+ toast(R.string.no_contacts_permission)
+ }
+ }
+ }
+
+ private fun handleParseResult(result: ImportResult) {
+ toast(when (result) {
+ ImportResult.IMPORT_NOTHING_NEW -> R.string.no_new_items
+ ImportResult.IMPORT_OK -> R.string.holidays_imported_successfully
+ ImportResult.IMPORT_PARTIAL -> R.string.importing_some_holidays_failed
+ else -> R.string.importing_holidays_failed
+ }, Toast.LENGTH_LONG)
+ }
+
+ private fun addContactEvents(birthdays: Boolean, reminders: ArrayList, callback: (Int) -> Unit) {
+ var eventsAdded = 0
+ var eventsFound = 0
+ val uri = Data.CONTENT_URI
+ val projection = arrayOf(Contacts.DISPLAY_NAME,
+ CommonDataKinds.Event.CONTACT_ID,
+ CommonDataKinds.Event.CONTACT_LAST_UPDATED_TIMESTAMP,
+ CommonDataKinds.Event.START_DATE)
+
+ val selection = "${Data.MIMETYPE} = ? AND ${CommonDataKinds.Event.TYPE} = ?"
+ val type = if (birthdays) CommonDataKinds.Event.TYPE_BIRTHDAY else CommonDataKinds.Event.TYPE_ANNIVERSARY
+ val selectionArgs = arrayOf(CommonDataKinds.Event.CONTENT_ITEM_TYPE, type.toString())
+
+ val dateFormats = getDateFormats()
+ val existingEvents = if (birthdays) eventsDB.getBirthdays() else eventsDB.getAnniversaries()
+ val importIDs = HashMap()
+ existingEvents.forEach {
+ importIDs[it.importId] = it.startTS
+ }
+
+ val eventTypeId = if (birthdays) getBirthdaysEventTypeId() else getAnniversariesEventTypeId()
+
+ queryCursor(uri, projection, selection, selectionArgs, showErrors = true) { cursor ->
+ val contactId = cursor.getIntValue(CommonDataKinds.Event.CONTACT_ID).toString()
+ val name = cursor.getStringValue(Contacts.DISPLAY_NAME)
+ val startDate = cursor.getStringValue(CommonDataKinds.Event.START_DATE)
+
+ for (format in dateFormats) {
+ try {
+ val formatter = SimpleDateFormat(format, Locale.getDefault())
+ val date = formatter.parse(startDate)
+ if (date.year < 70) {
+ date.year = 70
+ }
+
+ val timestamp = date.time / 1000L
+ val source = if (birthdays) SOURCE_CONTACT_BIRTHDAY else SOURCE_CONTACT_ANNIVERSARY
+ val lastUpdated = cursor.getLongValue(CommonDataKinds.Event.CONTACT_LAST_UPDATED_TIMESTAMP)
+ val event = Event(null, timestamp, timestamp, name, reminder1Minutes = reminders[0], reminder2Minutes = reminders[1],
+ reminder3Minutes = reminders[2], importId = contactId, timeZone = DateTimeZone.getDefault().id, flags = FLAG_ALL_DAY,
+ 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)
+ }
+ }
+ }
+
+ importIDsToDelete.forEach {
+ importIDs.remove(it)
+ }
+
+ eventsFound++
+ if (!importIDs.containsKey(contactId)) {
+ eventsHelper.insertEvent(event, false, false) {
+ eventsAdded++
+ }
+ }
+ break
+ } catch (e: Exception) {
+ }
+ }
+ }
+
+ runOnUiThread {
+ callback(if (eventsAdded == 0 && eventsFound > 0) -1 else eventsAdded)
+ }
+ }
+
+ private fun getBirthdaysEventTypeId(): Long {
+ val birthdays = getString(R.string.birthdays)
+ var eventTypeId = eventsHelper.getEventTypeIdWithTitle(birthdays)
+ if (eventTypeId == -1L) {
+ val eventType = EventType(null, birthdays, resources.getColor(R.color.default_birthdays_color))
+ eventTypeId = eventsHelper.insertOrUpdateEventTypeSync(eventType)
+ }
+ return eventTypeId
+ }
+
+ private fun getAnniversariesEventTypeId(): Long {
+ val anniversaries = getString(R.string.anniversaries)
+ var eventTypeId = eventsHelper.getEventTypeIdWithTitle(anniversaries)
+ if (eventTypeId == -1L) {
+ val eventType = EventType(null, anniversaries, resources.getColor(R.color.default_anniversaries_color))
+ eventTypeId = eventsHelper.insertOrUpdateEventTypeSync(eventType)
+ }
+ return eventTypeId
+ }
+
+ private fun updateView(view: Int) {
+ calendar_fab.beVisibleIf(view != YEARLY_VIEW)
+ config.storedView = view
+ checkSwipeRefreshAvailability()
+ updateViewPager()
+ if (goToTodayButton?.isVisible == true) {
+ shouldGoToTodayBeVisible = false
+ invalidateOptionsMenu()
+ }
+ }
+
+ private fun updateViewPager(dayCode: String? = Formatter.getTodayCode()) {
+ val fragment = getFragmentsHolder()
+ currentFragments.forEach {
+ supportFragmentManager.beginTransaction().remove(it).commitNow()
+ }
+ currentFragments.clear()
+ currentFragments.add(fragment)
+ val bundle = Bundle()
+
+ when (config.storedView) {
+ DAILY_VIEW, MONTHLY_VIEW -> bundle.putString(DAY_CODE, dayCode)
+ WEEKLY_VIEW -> bundle.putString(WEEK_START_DATE_TIME, getThisWeekDateTime())
+ }
+
+ fragment.arguments = bundle
+ supportFragmentManager.beginTransaction().add(R.id.fragments_holder, fragment).commitNow()
+ }
+
+ fun openMonthFromYearly(dateTime: DateTime) {
+ if (currentFragments.last() is MonthFragmentsHolder) {
+ return
+ }
+
+ val fragment = MonthFragmentsHolder()
+ currentFragments.add(fragment)
+ val bundle = Bundle()
+ bundle.putString(DAY_CODE, Formatter.getDayCodeFromDateTime(dateTime))
+ fragment.arguments = bundle
+ supportFragmentManager.beginTransaction().add(R.id.fragments_holder, fragment).commitNow()
+ resetActionBarTitle()
+ calendar_fab.beVisible()
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ }
+
+ fun openDayFromMonthly(dateTime: DateTime) {
+ if (currentFragments.last() is DayFragmentsHolder) {
+ return
+ }
+
+ val fragment = DayFragmentsHolder()
+ currentFragments.add(fragment)
+ val bundle = Bundle()
+ bundle.putString(DAY_CODE, Formatter.getDayCodeFromDateTime(dateTime))
+ fragment.arguments = bundle
+ try {
+ supportFragmentManager.beginTransaction().add(R.id.fragments_holder, fragment).commitNow()
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ } catch (e: Exception) {
+ }
+ }
+
+ private fun getThisWeekDateTime(): String {
+ var thisweek = DateTime().withDayOfWeek(1).withTimeAtStartOfDay().minusDays(if (config.isSundayFirst) 1 else 0)
+ if (DateTime().minusDays(7).seconds() > thisweek.seconds()) {
+ thisweek = thisweek.plusDays(7)
+ }
+ return thisweek.toString()
+ }
+
+ private fun getFragmentsHolder() = when (config.storedView) {
+ DAILY_VIEW -> DayFragmentsHolder()
+ MONTHLY_VIEW -> MonthFragmentsHolder()
+ YEARLY_VIEW -> YearFragmentsHolder()
+ EVENTS_LIST_VIEW -> EventListFragment()
+ else -> WeekFragmentsHolder()
+ }
+
+ private fun removeTopFragment() {
+ supportFragmentManager.beginTransaction().remove(currentFragments.last()).commit()
+ currentFragments.removeAt(currentFragments.size - 1)
+ toggleGoToTodayVisibility(currentFragments.last().shouldGoToTodayBeVisible())
+ currentFragments.last().apply {
+ refreshEvents()
+ updateActionBarTitle()
+ }
+ calendar_fab.beGoneIf(currentFragments.size == 1 && config.storedView == YEARLY_VIEW)
+ supportActionBar?.setDisplayHomeAsUpEnabled(currentFragments.size > 1)
+ }
+
+ private fun refreshViewPager() {
+ runOnUiThread {
+ if (!isDestroyed) {
+ currentFragments.last().refreshEvents()
+ }
+ }
+ }
+
+ private fun tryImportEvents() {
+ if (isQPlus()) {
+ Intent(Intent.ACTION_GET_CONTENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "text/calendar"
+ startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT)
+ }
+ } else {
+ handlePermission(PERMISSION_READ_STORAGE) {
+ if (it) {
+ importEvents()
+ }
+ }
+ }
+ }
+
+ private fun importEvents() {
+ FilePickerDialog(this) {
+ showImportEventsDialog(it)
+ }
+ }
+
+ private fun tryImportEventsFromFile(uri: Uri) {
+ when {
+ uri.scheme == "file" -> showImportEventsDialog(uri.path!!)
+ uri.scheme == "content" -> {
+ val tempFile = getTempFile()
+ if (tempFile == null) {
+ toast(R.string.unknown_error_occurred)
+ return
+ }
+
+ val inputStream = contentResolver.openInputStream(uri)
+ val out = FileOutputStream(tempFile)
+ inputStream!!.copyTo(out)
+ showImportEventsDialog(tempFile.absolutePath)
+ }
+ else -> toast(R.string.invalid_file_format)
+ }
+ }
+
+ private fun showImportEventsDialog(path: String) {
+ ImportEventsDialog(this, path) {
+ if (it) {
+ runOnUiThread {
+ updateViewPager()
+ }
+ }
+ }
+ }
+
+ private fun tryExportEvents() {
+ if (isQPlus()) {
+ ExportEventsDialog(this, config.lastExportPath, true) { file, eventTypes ->
+ eventTypesToExport = eventTypes
+
+ Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ type = "text/calendar"
+ putExtra(Intent.EXTRA_TITLE, file.name)
+ addCategory(Intent.CATEGORY_OPENABLE)
+
+ startActivityForResult(this, PICK_EXPORT_FILE_INTENT)
+ }
+ }
+ } else {
+ handlePermission(PERMISSION_WRITE_STORAGE) {
+ if (it) {
+ ExportEventsDialog(this, config.lastExportPath, false) { file, eventTypes ->
+ getFileOutputStream(file.toFileDirItem(this), true) {
+ exportEventsTo(eventTypes, it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun exportEventsTo(eventTypes: ArrayList, outputStream: OutputStream?) {
+ ensureBackgroundThread {
+ val events = eventsHelper.getEventsToExport(eventTypes)
+ if (events.isEmpty()) {
+ toast(R.string.no_entries_for_exporting)
+ } else {
+ IcsExporter().exportEvents(this, outputStream, events, true) {
+ toast(when (it) {
+ ExportResult.EXPORT_OK -> R.string.exporting_successful
+ ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_entries_failed
+ else -> R.string.exporting_failed
+ })
+ }
+ }
+ }
+ }
+
+ private fun launchSettings() {
+ startActivity(Intent(applicationContext, SettingsActivity::class.java))
+ }
+
+ private fun launchAbout() {
+ val licenses = LICENSE_JODA
+
+ val faqItems = arrayListOf(
+ FAQItem(R.string.faq_1_title_commons, R.string.faq_1_text_commons),
+ FAQItem(R.string.faq_4_title_commons, R.string.faq_4_text_commons),
+ FAQItem(R.string.faq_1_title, R.string.faq_1_text),
+ FAQItem(R.string.faq_2_title, R.string.faq_2_text),
+ FAQItem(R.string.faq_3_title, R.string.faq_3_text),
+ FAQItem(R.string.faq_4_title, R.string.faq_4_text),
+ FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons),
+ FAQItem(R.string.faq_6_title_commons, R.string.faq_6_text_commons),
+ FAQItem(R.string.faq_7_title_commons, R.string.faq_7_text_commons))
+
+ startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
+ }
+
+ private fun searchQueryChanged(text: String) {
+ mLatestSearchQuery = text
+ search_placeholder_2.beGoneIf(text.length >= 2)
+ if (text.length >= 2) {
+ eventsHelper.getEventsWithSearchQuery(text, this) { searchedText, events ->
+ if (searchedText == mLatestSearchQuery) {
+ search_results_list.beVisibleIf(events.isNotEmpty())
+ search_placeholder.beVisibleIf(events.isEmpty())
+ val listItems = getEventListItems(events)
+ val eventsAdapter = EventListAdapter(this, listItems, true, this, search_results_list) {
+ if (it is ListEvent) {
+ Intent(applicationContext, EventActivity::class.java).apply {
+ putExtra(EVENT_ID, it.id)
+ startActivity(this)
+ }
+ }
+ }
+
+ search_results_list.adapter = eventsAdapter
+ }
+ }
+ } else {
+ search_placeholder.beVisible()
+ search_results_list.beGone()
+ }
+ }
+
+ private fun checkSwipeRefreshAvailability() {
+ swipe_refresh_layout.isEnabled = config.caldavSync && config.pullToRefresh && config.storedView != WEEKLY_VIEW
+ if (!swipe_refresh_layout.isEnabled) {
+ swipe_refresh_layout.isRefreshing = false
+ }
+ }
+
+ // only used at active search
+ override fun refreshItems() {
+ searchQueryChanged(mLatestSearchQuery)
+ refreshViewPager()
+ }
+
+ private fun openDayAt(timestamp: Long) {
+ val dayCode = Formatter.getDayCodeFromTS(timestamp / 1000L)
+ calendar_fab.beVisible()
+ config.storedView = DAILY_VIEW
+ updateViewPager(dayCode)
+ }
+
+ private fun getHolidayRadioItems(): ArrayList {
+ val items = ArrayList()
+
+ LinkedHashMap().apply {
+ put("Algeria", "algeria.ics")
+ put("Argentina", "argentina.ics")
+ put("Australia", "australia.ics")
+ put("België", "belgium.ics")
+ put("Bolivia", "bolivia.ics")
+ put("Brasil", "brazil.ics")
+ put("Canada", "canada.ics")
+ put("China", "china.ics")
+ put("Colombia", "colombia.ics")
+ put("Česká republika", "czech.ics")
+ put("Danmark", "denmark.ics")
+ put("Deutschland", "germany.ics")
+ put("Eesti", "estonia.ics")
+ put("España", "spain.ics")
+ put("Éire", "ireland.ics")
+ put("France", "france.ics")
+ put("한국", "southkorea.ics")
+ put("Hellas", "greece.ics")
+ put("Hrvatska", "croatia.ics")
+ put("India", "india.ics")
+ put("Indonesia", "indonesia.ics")
+ put("Ísland", "iceland.ics")
+ put("Italia", "italy.ics")
+ put("Latvija", "latvia.ics")
+ put("Lietuva", "lithuania.ics")
+ put("Luxemburg", "luxembourg.ics")
+ put("Makedonija", "macedonia.ics")
+ put("Malaysia", "malaysia.ics")
+ put("Magyarország", "hungary.ics")
+ put("México", "mexico.ics")
+ put("Nederland", "netherlands.ics")
+ put("日本", "japan.ics")
+ put("Nigeria", "nigeria.ics")
+ put("Norge", "norway.ics")
+ put("Österreich", "austria.ics")
+ put("Pākistān", "pakistan.ics")
+ put("Polska", "poland.ics")
+ put("Portugal", "portugal.ics")
+ put("Россия", "russia.ics")
+ put("România", "romania.ics")
+ put("Schweiz", "switzerland.ics")
+ put("Singapore", "singapore.ics")
+ put("Srbija", "serbia.ics")
+ put("Slovenija", "slovenia.ics")
+ put("Slovensko", "slovakia.ics")
+ put("South Africa", "southafrica.ics")
+ put("Suomi", "finland.ics")
+ put("Sverige", "sweden.ics")
+ put("Taiwan", "taiwan.ics")
+ put("Ukraine", "ukraine.ics")
+ put("United Kingdom", "unitedkingdom.ics")
+ put("United States", "unitedstates.ics")
+
+ var i = 0
+ for ((country, file) in this) {
+ items.add(RadioItem(i++, country, file))
+ }
+ }
+
+ return items
+ }
+
+ private fun checkWhatsNewDialog() {
+ arrayListOf().apply {
+ add(Release(39, R.string.release_39))
+ add(Release(40, R.string.release_40))
+ add(Release(42, R.string.release_42))
+ add(Release(44, R.string.release_44))
+ add(Release(46, R.string.release_46))
+ add(Release(48, R.string.release_48))
+ add(Release(49, R.string.release_49))
+ add(Release(51, R.string.release_51))
+ add(Release(52, R.string.release_52))
+ add(Release(54, R.string.release_54))
+ add(Release(57, R.string.release_57))
+ add(Release(59, R.string.release_59))
+ add(Release(60, R.string.release_60))
+ add(Release(62, R.string.release_62))
+ add(Release(67, R.string.release_67))
+ add(Release(69, R.string.release_69))
+ add(Release(71, R.string.release_71))
+ add(Release(73, R.string.release_73))
+ add(Release(76, R.string.release_76))
+ add(Release(77, R.string.release_77))
+ add(Release(80, R.string.release_80))
+ add(Release(84, R.string.release_84))
+ add(Release(86, R.string.release_86))
+ add(Release(88, R.string.release_88))
+ add(Release(98, R.string.release_98))
+ add(Release(117, R.string.release_117))
+ add(Release(119, R.string.release_119))
+ add(Release(129, R.string.release_129))
+ add(Release(143, R.string.release_143))
+ add(Release(155, R.string.release_155))
+ add(Release(167, R.string.release_167))
+ checkWhatsNew(this, BuildConfig.VERSION_CODE)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/ManageEventTypesActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/ManageEventTypesActivity.kt
similarity index 56%
rename from app/src/main/kotlin/com/simplemobiletools/calendar/activities/ManageEventTypesActivity.kt
rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/ManageEventTypesActivity.kt
index 4b6cc8ed1..6740d4427 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/ManageEventTypesActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/ManageEventTypesActivity.kt
@@ -1,16 +1,17 @@
-package com.simplemobiletools.calendar.activities
+package com.simplemobiletools.calendar.pro.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.adapters.ManageEventTypesAdapter
-import com.simplemobiletools.calendar.dialogs.UpdateEventTypeDialog
-import com.simplemobiletools.calendar.extensions.dbHelper
-import com.simplemobiletools.calendar.interfaces.DeleteEventTypesListener
-import com.simplemobiletools.calendar.models.EventType
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.adapters.ManageEventTypesAdapter
+import com.simplemobiletools.calendar.pro.dialogs.EditEventTypeDialog
+import com.simplemobiletools.calendar.pro.extensions.eventsHelper
+import com.simplemobiletools.calendar.pro.interfaces.DeleteEventTypesListener
+import com.simplemobiletools.calendar.pro.models.EventType
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.extensions.updateTextColors
+import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import kotlinx.android.synthetic.main.activity_manage_event_types.*
import java.util.*
@@ -24,25 +25,23 @@ class ManageEventTypesActivity : SimpleActivity(), DeleteEventTypesListener {
}
private fun showEventTypeDialog(eventType: EventType? = null) {
- UpdateEventTypeDialog(this, eventType?.copy()) {
+ EditEventTypeDialog(this, eventType?.copy()) {
getEventTypes()
}
}
private fun getEventTypes() {
- dbHelper.getEventTypes {
- runOnUiThread {
- val adapter = ManageEventTypesAdapter(this, it, this, manage_event_types_list) {
- showEventTypeDialog(it as EventType)
- }
- adapter.setupDragListener(true)
- manage_event_types_list.adapter = adapter
+ eventsHelper.getEventTypes(this, false) {
+ val adapter = ManageEventTypesAdapter(this, it, this, manage_event_types_list) {
+ showEventTypeDialog(it as EventType)
}
+ manage_event_types_list.adapter = adapter
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_event_types, menu)
+ updateMenuItemColors(menu)
return true
}
@@ -54,15 +53,17 @@ class ManageEventTypesActivity : SimpleActivity(), DeleteEventTypesListener {
return true
}
- override fun deleteEventTypes(eventTypes: ArrayList, deleteEvents: Boolean) {
+ override fun deleteEventTypes(eventTypes: ArrayList, deleteEvents: Boolean): Boolean {
if (eventTypes.any { it.caldavCalendarId != 0 }) {
toast(R.string.unsync_caldav_calendar)
- }
-
- dbHelper.deleteEventTypes(eventTypes, deleteEvents) {
- if (it == 0) {
- toast(R.string.unknown_error_occurred)
+ if (eventTypes.size == 1) {
+ return false
}
}
+
+ ensureBackgroundThread {
+ eventsHelper.deleteEventTypes(eventTypes, deleteEvents)
+ }
+ return true
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SelectTimeZoneActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SelectTimeZoneActivity.kt
new file mode 100644
index 000000000..11746941a
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SelectTimeZoneActivity.kt
@@ -0,0 +1,90 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.app.SearchManager
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import androidx.appcompat.widget.SearchView
+import androidx.core.view.MenuItemCompat
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.adapters.SelectTimeZoneAdapter
+import com.simplemobiletools.calendar.pro.helpers.CURRENT_TIME_ZONE
+import com.simplemobiletools.calendar.pro.helpers.TIME_ZONE
+import com.simplemobiletools.calendar.pro.helpers.getAllTimeZones
+import com.simplemobiletools.calendar.pro.models.MyTimeZone
+import kotlinx.android.synthetic.main.activity_select_time_zone.*
+import java.util.*
+
+class SelectTimeZoneActivity : SimpleActivity() {
+ private var mSearchMenuItem: MenuItem? = null
+ private val allTimeZones = getAllTimeZones()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_select_time_zone)
+ title = ""
+
+ SelectTimeZoneAdapter(this, allTimeZones) {
+ val data = Intent()
+ data.putExtra(TIME_ZONE, it as MyTimeZone)
+ setResult(RESULT_OK, data)
+ finish()
+ }.apply {
+ select_time_zone_list.adapter = this
+ }
+
+ val currentTimeZone = intent.getStringExtra(CURRENT_TIME_ZONE) ?: TimeZone.getDefault().id
+ val pos = allTimeZones.indexOfFirst { it.zoneName.equals(currentTimeZone, true) }
+ if (pos != -1) {
+ select_time_zone_list.scrollToPosition(pos)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_select_time_zone, menu)
+ setupSearch(menu)
+ updateMenuItemColors(menu)
+ return true
+ }
+
+ private fun setupSearch(menu: Menu) {
+ val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
+ mSearchMenuItem = menu.findItem(R.id.search)
+ (mSearchMenuItem!!.actionView as SearchView).apply {
+ queryHint = getString(R.string.enter_a_country)
+ setSearchableInfo(searchManager.getSearchableInfo(componentName))
+ isSubmitButtonEnabled = false
+ isIconified = false
+ setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String) = false
+
+ override fun onQueryTextChange(newText: String): Boolean {
+ searchQueryChanged(newText)
+ return true
+ }
+ })
+ }
+
+ mSearchMenuItem!!.expandActionView()
+ MenuItemCompat.setOnActionExpandListener(mSearchMenuItem, object : MenuItemCompat.OnActionExpandListener {
+ override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
+ searchQueryChanged("")
+ return true
+ }
+
+ override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
+ finish()
+ return true
+ }
+ })
+ }
+
+ private fun searchQueryChanged(text: String) {
+ val timeZones = allTimeZones.filter {
+ it.zoneName.toLowerCase(Locale.getDefault()).contains(text.toLowerCase(Locale.getDefault()))
+ }.toMutableList() as ArrayList
+ (select_time_zone_list.adapter as? SelectTimeZoneAdapter)?.updateTimeZones(timeZones)
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt
new file mode 100644
index 000000000..699e10d1a
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt
@@ -0,0 +1,782 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.app.Activity
+import android.app.TimePickerDialog
+import android.content.Intent
+import android.media.AudioManager
+import android.os.Bundle
+import android.view.Menu
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.dialogs.SelectCalendarsDialog
+import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypeDialog
+import com.simplemobiletools.calendar.pro.extensions.*
+import com.simplemobiletools.calendar.pro.helpers.*
+import com.simplemobiletools.calendar.pro.models.EventType
+import com.simplemobiletools.commons.dialogs.*
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.models.AlarmSound
+import com.simplemobiletools.commons.models.RadioItem
+import kotlinx.android.synthetic.main.activity_settings.*
+import org.joda.time.DateTime
+import java.io.File
+import java.io.InputStream
+import java.util.*
+
+class SettingsActivity : SimpleActivity() {
+ private val GET_RINGTONE_URI = 1
+ private val PICK_IMPORT_SOURCE_INTENT = 2
+
+ private var mStoredPrimaryColor = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_settings)
+ mStoredPrimaryColor = config.primaryColor
+ }
+
+ override fun onResume() {
+ super.onResume()
+ setupSettingItems()
+ }
+
+ private fun setupSettingItems() {
+ setupCustomizeColors()
+ setupUseEnglish()
+ setupManageEventTypes()
+ setupHourFormat()
+ setupSundayFirst()
+ setupDeleteAllEvents()
+ setupReplaceDescription()
+ setupWeekNumbers()
+ setupShowGrid()
+ setupWeeklyStart()
+ setupVibrate()
+ setupReminderSound()
+ setupReminderAudioStream()
+ setupUseSameSnooze()
+ setupLoopReminders()
+ setupSnoozeTime()
+ setupCaldavSync()
+ setupManageSyncedCalendars()
+ setupDefaultStartTime()
+ setupDefaultDuration()
+ setupDefaultEventType()
+ setupPullToRefresh()
+ setupDefaultReminder()
+ setupDefaultReminder1()
+ setupDefaultReminder2()
+ setupDefaultReminder3()
+ setupDisplayPastEvents()
+ setupFontSize()
+ setupCustomizeWidgetColors()
+ setupViewToOpenFromListWidget()
+ setupDimEvents()
+ setupAllowChangingTimeZones()
+ updateTextColors(settings_holder)
+ checkPrimaryColor()
+ setupSectionColors()
+ setupExportSettings()
+ setupImportSettings()
+ invalidateOptionsMenu()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ mStoredPrimaryColor = config.primaryColor
+ }
+
+ override fun onStop() {
+ super.onStop()
+ val reminders = sortedSetOf(config.defaultReminder1, config.defaultReminder2, config.defaultReminder3).filter { it != REMINDER_OFF }
+ config.defaultReminder1 = reminders.getOrElse(0) { REMINDER_OFF }
+ config.defaultReminder2 = reminders.getOrElse(1) { REMINDER_OFF }
+ config.defaultReminder3 = reminders.getOrElse(2) { REMINDER_OFF }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ updateMenuItemColors(menu)
+ return super.onCreateOptionsMenu(menu)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+ super.onActivityResult(requestCode, resultCode, resultData)
+ if (requestCode == GET_RINGTONE_URI && resultCode == RESULT_OK && resultData != null) {
+ val newAlarmSound = storeNewYourAlarmSound(resultData)
+ updateReminderSound(newAlarmSound)
+ } else if (requestCode == PICK_IMPORT_SOURCE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
+ val inputStream = contentResolver.openInputStream(resultData.data!!)
+ parseFile(inputStream)
+ }
+ }
+
+ private fun checkPrimaryColor() {
+ if (config.primaryColor != mStoredPrimaryColor) {
+ ensureBackgroundThread {
+ val eventTypes = eventsHelper.getEventTypesSync()
+ if (eventTypes.filter { it.caldavCalendarId == 0 }.size == 1) {
+ val eventType = eventTypes.first { it.caldavCalendarId == 0 }
+ eventType.color = config.primaryColor
+ eventsHelper.insertOrUpdateEventTypeSync(eventType)
+ }
+ }
+ }
+ }
+
+ private fun setupSectionColors() {
+ val adjustedPrimaryColor = getAdjustedPrimaryColor()
+ arrayListOf(reminders_label, caldav_label, weekly_view_label, monthly_view_label, simple_event_list_label, widgets_label, events_label,
+ new_events_label, migrating_label).forEach {
+ it.setTextColor(adjustedPrimaryColor)
+ }
+ }
+
+ private fun setupCustomizeColors() {
+ settings_customize_colors_holder.setOnClickListener {
+ startCustomizationActivity()
+ }
+ }
+
+ private fun setupUseEnglish() {
+ settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
+ settings_use_english.isChecked = config.useEnglish
+ settings_use_english_holder.setOnClickListener {
+ settings_use_english.toggle()
+ config.useEnglish = settings_use_english.isChecked
+ System.exit(0)
+ }
+ }
+
+ private fun setupManageEventTypes() {
+ settings_manage_event_types_holder.setOnClickListener {
+ startActivity(Intent(this, ManageEventTypesActivity::class.java))
+ }
+ }
+
+ private fun setupHourFormat() {
+ settings_hour_format.isChecked = config.use24HourFormat
+ settings_hour_format_holder.setOnClickListener {
+ settings_hour_format.toggle()
+ config.use24HourFormat = settings_hour_format.isChecked
+ }
+ }
+
+ private fun setupCaldavSync() {
+ settings_caldav_sync.isChecked = config.caldavSync
+ settings_caldav_sync_holder.setOnClickListener {
+ if (config.caldavSync) {
+ toggleCaldavSync(false)
+ } else {
+ handlePermission(PERMISSION_WRITE_CALENDAR) {
+ if (it) {
+ handlePermission(PERMISSION_READ_CALENDAR) {
+ if (it) {
+ toggleCaldavSync(true)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun setupPullToRefresh() {
+ settings_caldav_pull_to_refresh_holder.beVisibleIf(config.caldavSync)
+ settings_caldav_pull_to_refresh.isChecked = config.pullToRefresh
+ settings_caldav_pull_to_refresh_holder.setOnClickListener {
+ settings_caldav_pull_to_refresh.toggle()
+ config.pullToRefresh = settings_caldav_pull_to_refresh.isChecked
+ }
+ }
+
+ private fun setupManageSyncedCalendars() {
+ settings_manage_synced_calendars_holder.beVisibleIf(config.caldavSync)
+ settings_manage_synced_calendars_holder.setOnClickListener {
+ showCalendarPicker()
+ }
+ }
+
+ private fun toggleCaldavSync(enable: Boolean) {
+ if (enable) {
+ showCalendarPicker()
+ } else {
+ settings_caldav_sync.isChecked = false
+ config.caldavSync = false
+ settings_manage_synced_calendars_holder.beGone()
+ settings_caldav_pull_to_refresh_holder.beGone()
+
+ ensureBackgroundThread {
+ config.getSyncedCalendarIdsAsList().forEach {
+ calDAVHelper.deleteCalDAVCalendarEvents(it.toLong())
+ }
+ eventTypesDB.deleteEventTypesWithCalendarId(config.getSyncedCalendarIdsAsList())
+ updateDefaultEventTypeText()
+ }
+ }
+ }
+
+ private fun showCalendarPicker() {
+ val oldCalendarIds = config.getSyncedCalendarIdsAsList()
+
+ SelectCalendarsDialog(this) {
+ val newCalendarIds = config.getSyncedCalendarIdsAsList()
+ if (newCalendarIds.isEmpty() && !config.caldavSync) {
+ return@SelectCalendarsDialog
+ }
+
+ settings_manage_synced_calendars_holder.beVisibleIf(newCalendarIds.isNotEmpty())
+ settings_caldav_pull_to_refresh_holder.beVisibleIf(newCalendarIds.isNotEmpty())
+ settings_caldav_sync.isChecked = newCalendarIds.isNotEmpty()
+ config.caldavSync = newCalendarIds.isNotEmpty()
+ if (settings_caldav_sync.isChecked) {
+ toast(R.string.syncing)
+ }
+
+ ensureBackgroundThread {
+ if (newCalendarIds.isNotEmpty()) {
+ val existingEventTypeNames = eventsHelper.getEventTypesSync().map { it.getDisplayTitle().toLowerCase() } as ArrayList
+ getSyncedCalDAVCalendars().forEach {
+ val calendarTitle = it.getFullTitle()
+ if (!existingEventTypeNames.contains(calendarTitle.toLowerCase())) {
+ val eventType = EventType(null, it.displayName, it.color, it.id, it.displayName, it.accountName)
+ existingEventTypeNames.add(calendarTitle.toLowerCase())
+ eventsHelper.insertOrUpdateEventType(this, eventType)
+ }
+ }
+
+ syncCalDAVCalendars {
+ calDAVHelper.refreshCalendars(true) {
+ if (settings_caldav_sync.isChecked) {
+ toast(R.string.synchronization_completed)
+ }
+ }
+ }
+ }
+
+ val removedCalendarIds = oldCalendarIds.filter { !newCalendarIds.contains(it) }
+ removedCalendarIds.forEach {
+ calDAVHelper.deleteCalDAVCalendarEvents(it.toLong())
+ eventsHelper.getEventTypeWithCalDAVCalendarId(it)?.apply {
+ eventsHelper.deleteEventTypes(arrayListOf(this), true)
+ }
+ }
+
+ eventTypesDB.deleteEventTypesWithCalendarId(removedCalendarIds)
+ updateDefaultEventTypeText()
+ }
+ }
+ }
+
+ private fun setupSundayFirst() {
+ settings_sunday_first.isChecked = config.isSundayFirst
+ settings_sunday_first_holder.setOnClickListener {
+ settings_sunday_first.toggle()
+ config.isSundayFirst = settings_sunday_first.isChecked
+ }
+ }
+
+ private fun setupDeleteAllEvents() {
+ settings_delete_all_events_holder.setOnClickListener {
+ ConfirmationDialog(this, messageId = R.string.delete_all_events_confirmation) {
+ eventsHelper.deleteAllEvents()
+ }
+ }
+ }
+
+ private fun setupReplaceDescription() {
+ settings_replace_description.isChecked = config.replaceDescription
+ settings_replace_description_holder.setOnClickListener {
+ settings_replace_description.toggle()
+ config.replaceDescription = settings_replace_description.isChecked
+ }
+ }
+
+ private fun setupWeeklyStart() {
+ settings_start_weekly_at.text = getHoursString(config.startWeeklyAt)
+ settings_start_weekly_at_holder.setOnClickListener {
+ val items = ArrayList()
+ (0..16).mapTo(items) { RadioItem(it, getHoursString(it)) }
+
+ RadioGroupDialog(this@SettingsActivity, items, config.startWeeklyAt) {
+ config.startWeeklyAt = it as Int
+ settings_start_weekly_at.text = getHoursString(it)
+ }
+ }
+ }
+
+ private fun setupWeekNumbers() {
+ settings_week_numbers.isChecked = config.showWeekNumbers
+ settings_week_numbers_holder.setOnClickListener {
+ settings_week_numbers.toggle()
+ config.showWeekNumbers = settings_week_numbers.isChecked
+ }
+ }
+
+ private fun setupShowGrid() {
+ settings_show_grid.isChecked = config.showGrid
+ settings_show_grid_holder.setOnClickListener {
+ settings_show_grid.toggle()
+ config.showGrid = settings_show_grid.isChecked
+ }
+ }
+
+ private fun setupReminderSound() {
+ settings_reminder_sound.text = config.reminderSoundTitle
+
+ settings_reminder_sound_holder.setOnClickListener {
+ SelectAlarmSoundDialog(this, config.reminderSoundUri, config.reminderAudioStream, GET_RINGTONE_URI, ALARM_SOUND_TYPE_NOTIFICATION, false,
+ onAlarmPicked = {
+ if (it != null) {
+ updateReminderSound(it)
+ }
+ }, onAlarmSoundDeleted = {
+ if (it.uri == config.reminderSoundUri) {
+ val defaultAlarm = getDefaultAlarmSound(ALARM_SOUND_TYPE_NOTIFICATION)
+ updateReminderSound(defaultAlarm)
+ }
+ })
+ }
+ }
+
+ private fun updateReminderSound(alarmSound: AlarmSound) {
+ config.reminderSoundTitle = alarmSound.title
+ config.reminderSoundUri = alarmSound.uri
+ settings_reminder_sound.text = alarmSound.title
+ }
+
+ private fun setupReminderAudioStream() {
+ settings_reminder_audio_stream.text = getAudioStreamText()
+ settings_reminder_audio_stream_holder.setOnClickListener {
+ val items = arrayListOf(
+ RadioItem(AudioManager.STREAM_ALARM, getString(R.string.alarm_stream)),
+ RadioItem(AudioManager.STREAM_SYSTEM, getString(R.string.system_stream)),
+ RadioItem(AudioManager.STREAM_NOTIFICATION, getString(R.string.notification_stream)),
+ RadioItem(AudioManager.STREAM_RING, getString(R.string.ring_stream)))
+
+ RadioGroupDialog(this@SettingsActivity, items, config.reminderAudioStream) {
+ config.reminderAudioStream = it as Int
+ settings_reminder_audio_stream.text = getAudioStreamText()
+ }
+ }
+ }
+
+ private fun getAudioStreamText() = getString(when (config.reminderAudioStream) {
+ AudioManager.STREAM_ALARM -> R.string.alarm_stream
+ AudioManager.STREAM_SYSTEM -> R.string.system_stream
+ AudioManager.STREAM_NOTIFICATION -> R.string.notification_stream
+ else -> R.string.ring_stream
+ })
+
+ private fun setupVibrate() {
+ settings_vibrate.isChecked = config.vibrateOnReminder
+ settings_vibrate_holder.setOnClickListener {
+ settings_vibrate.toggle()
+ config.vibrateOnReminder = settings_vibrate.isChecked
+ }
+ }
+
+ private fun setupLoopReminders() {
+ settings_loop_reminders.isChecked = config.loopReminders
+ settings_loop_reminders_holder.setOnClickListener {
+ settings_loop_reminders.toggle()
+ config.loopReminders = settings_loop_reminders.isChecked
+ }
+ }
+
+ private fun setupUseSameSnooze() {
+ settings_snooze_time_holder.beVisibleIf(config.useSameSnooze)
+ settings_use_same_snooze.isChecked = config.useSameSnooze
+ settings_use_same_snooze_holder.setOnClickListener {
+ settings_use_same_snooze.toggle()
+ config.useSameSnooze = settings_use_same_snooze.isChecked
+ settings_snooze_time_holder.beVisibleIf(config.useSameSnooze)
+ }
+ }
+
+ private fun setupSnoozeTime() {
+ updateSnoozeTime()
+ settings_snooze_time_holder.setOnClickListener {
+ showPickSecondsDialogHelper(config.snoozeTime, true) {
+ config.snoozeTime = it / 60
+ updateSnoozeTime()
+ }
+ }
+ }
+
+ private fun updateSnoozeTime() {
+ settings_snooze_time.text = formatMinutesToTimeString(config.snoozeTime)
+ }
+
+ private fun setupDefaultReminder() {
+ settings_use_last_event_reminders.isChecked = config.usePreviousEventReminders
+ toggleDefaultRemindersVisibility(!config.usePreviousEventReminders)
+ settings_use_last_event_reminders_holder.setOnClickListener {
+ settings_use_last_event_reminders.toggle()
+ config.usePreviousEventReminders = settings_use_last_event_reminders.isChecked
+ toggleDefaultRemindersVisibility(!settings_use_last_event_reminders.isChecked)
+ }
+ }
+
+ private fun setupDefaultReminder1() {
+ settings_default_reminder_1.text = getFormattedMinutes(config.defaultReminder1)
+ settings_default_reminder_1_holder.setOnClickListener {
+ showPickSecondsDialogHelper(config.defaultReminder1) {
+ config.defaultReminder1 = if (it <= 0) it else it / 60
+ settings_default_reminder_1.text = getFormattedMinutes(config.defaultReminder1)
+ }
+ }
+ }
+
+ private fun setupDefaultReminder2() {
+ settings_default_reminder_2.text = getFormattedMinutes(config.defaultReminder2)
+ settings_default_reminder_2_holder.setOnClickListener {
+ showPickSecondsDialogHelper(config.defaultReminder2) {
+ config.defaultReminder2 = if (it <= 0) it else it / 60
+ settings_default_reminder_2.text = getFormattedMinutes(config.defaultReminder2)
+ }
+ }
+ }
+
+ private fun setupDefaultReminder3() {
+ settings_default_reminder_3.text = getFormattedMinutes(config.defaultReminder3)
+ settings_default_reminder_3_holder.setOnClickListener {
+ showPickSecondsDialogHelper(config.defaultReminder3) {
+ config.defaultReminder3 = if (it <= 0) it else it / 60
+ settings_default_reminder_3.text = getFormattedMinutes(config.defaultReminder3)
+ }
+ }
+ }
+
+ private fun toggleDefaultRemindersVisibility(show: Boolean) {
+ arrayOf(settings_default_reminder_1_holder, settings_default_reminder_2_holder, settings_default_reminder_3_holder).forEach {
+ it.beVisibleIf(show)
+ }
+ }
+
+ private fun getHoursString(hours: Int) = String.format("%02d:00", hours)
+
+ private fun setupDisplayPastEvents() {
+ var displayPastEvents = config.displayPastEvents
+ updatePastEventsText(displayPastEvents)
+ settings_display_past_events_holder.setOnClickListener {
+ CustomIntervalPickerDialog(this, displayPastEvents * 60) {
+ val result = it / 60
+ displayPastEvents = result
+ config.displayPastEvents = result
+ updatePastEventsText(result)
+ }
+ }
+ }
+
+ private fun updatePastEventsText(displayPastEvents: Int) {
+ settings_display_past_events.text = getDisplayPastEventsText(displayPastEvents)
+ }
+
+ private fun getDisplayPastEventsText(displayPastEvents: Int): String {
+ return if (displayPastEvents == 0) {
+ getString(R.string.never)
+ } else {
+ getFormattedMinutes(displayPastEvents, false)
+ }
+ }
+
+ private fun setupFontSize() {
+ settings_font_size.text = getFontSizeText()
+ settings_font_size_holder.setOnClickListener {
+ val items = arrayListOf(
+ RadioItem(FONT_SIZE_SMALL, getString(R.string.small)),
+ RadioItem(FONT_SIZE_MEDIUM, getString(R.string.medium)),
+ RadioItem(FONT_SIZE_LARGE, getString(R.string.large)),
+ RadioItem(FONT_SIZE_EXTRA_LARGE, getString(R.string.extra_large)))
+
+ RadioGroupDialog(this@SettingsActivity, items, config.fontSize) {
+ config.fontSize = it as Int
+ settings_font_size.text = getFontSizeText()
+ updateWidgets()
+ }
+ }
+ }
+
+ private fun setupCustomizeWidgetColors() {
+ settings_customize_widget_colors_holder.setOnClickListener {
+ Intent(this, WidgetListConfigureActivity::class.java).apply {
+ putExtra(IS_CUSTOMIZING_COLORS, true)
+ startActivity(this)
+ }
+ }
+ }
+
+ private fun setupViewToOpenFromListWidget() {
+ settings_list_widget_view_to_open.text = getDefaultViewText()
+ settings_list_widget_view_to_open_holder.setOnClickListener {
+ val items = arrayListOf(
+ RadioItem(DAILY_VIEW, getString(R.string.daily_view)),
+ RadioItem(WEEKLY_VIEW, getString(R.string.weekly_view)),
+ RadioItem(MONTHLY_VIEW, getString(R.string.monthly_view)),
+ RadioItem(YEARLY_VIEW, getString(R.string.yearly_view)),
+ RadioItem(EVENTS_LIST_VIEW, getString(R.string.simple_event_list)),
+ RadioItem(LAST_VIEW, getString(R.string.last_view)))
+
+ RadioGroupDialog(this@SettingsActivity, items, config.listWidgetViewToOpen) {
+ config.listWidgetViewToOpen = it as Int
+ settings_list_widget_view_to_open.text = getDefaultViewText()
+ updateWidgets()
+ }
+ }
+ }
+
+ private fun getDefaultViewText() = getString(when (config.listWidgetViewToOpen) {
+ DAILY_VIEW -> R.string.daily_view
+ WEEKLY_VIEW -> R.string.weekly_view
+ MONTHLY_VIEW -> R.string.monthly_view
+ YEARLY_VIEW -> R.string.yearly_view
+ EVENTS_LIST_VIEW -> R.string.simple_event_list
+ else -> R.string.last_view
+ })
+
+ private fun setupDimEvents() {
+ settings_dim_past_events.isChecked = config.dimPastEvents
+ settings_dim_past_events_holder.setOnClickListener {
+ settings_dim_past_events.toggle()
+ config.dimPastEvents = settings_dim_past_events.isChecked
+ }
+ }
+
+ private fun setupAllowChangingTimeZones() {
+ settings_allow_changing_time_zones.isChecked = config.allowChangingTimeZones
+ settings_allow_changing_time_zones_holder.setOnClickListener {
+ settings_allow_changing_time_zones.toggle()
+ config.allowChangingTimeZones = settings_allow_changing_time_zones.isChecked
+ }
+ }
+
+ private fun setupDefaultStartTime() {
+ updateDefaultStartTimeText()
+ settings_default_start_time_holder.setOnClickListener {
+ val currentDefaultTime = if (config.defaultStartTime == -1) -1 else 0
+ val items = ArrayList()
+ items.add(RadioItem(-1, getString(R.string.next_full_hour)))
+ items.add(RadioItem(0, getString(R.string.other_time)))
+
+ RadioGroupDialog(this@SettingsActivity, items, currentDefaultTime) {
+ if (it as Int == -1) {
+ config.defaultStartTime = it
+ updateDefaultStartTimeText()
+ } else {
+ val timeListener = TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute ->
+ config.defaultStartTime = hourOfDay * 60 + minute
+ updateDefaultStartTimeText()
+ }
+
+ val currentDateTime = DateTime.now()
+ TimePickerDialog(this, getDialogTheme(), timeListener, currentDateTime.hourOfDay, currentDateTime.minuteOfHour, config.use24HourFormat).show()
+ }
+ }
+ }
+ }
+
+ private fun updateDefaultStartTimeText() {
+ if (config.defaultStartTime == -1) {
+ settings_default_start_time.text = getString(R.string.next_full_hour)
+ } else {
+ val hours = config.defaultStartTime / 60
+ val minutes = config.defaultStartTime % 60
+ settings_default_start_time.text = String.format("%02d:%02d", hours, minutes)
+ }
+ }
+
+ private fun setupDefaultDuration() {
+ updateDefaultDurationText()
+ settings_default_duration_holder.setOnClickListener {
+ CustomIntervalPickerDialog(this, config.defaultDuration * 60) {
+ val result = it / 60
+ config.defaultDuration = result
+ updateDefaultDurationText()
+ }
+ }
+ }
+
+ private fun updateDefaultDurationText() {
+ val duration = config.defaultDuration
+ settings_default_duration.text = if (duration == 0) {
+ "0 ${getString(R.string.minutes_raw)}"
+ } else {
+ getFormattedMinutes(duration, false)
+ }
+ }
+
+ private fun setupDefaultEventType() {
+ updateDefaultEventTypeText()
+ settings_default_event_type.text = getString(R.string.last_used_one)
+ settings_default_event_type_holder.setOnClickListener {
+ SelectEventTypeDialog(this, config.defaultEventTypeId, true, false, true, true) {
+ config.defaultEventTypeId = it.id!!
+ updateDefaultEventTypeText()
+ }
+ }
+ }
+
+ private fun updateDefaultEventTypeText() {
+ if (config.defaultEventTypeId == -1L) {
+ runOnUiThread {
+ settings_default_event_type.text = getString(R.string.last_used_one)
+ }
+ } else {
+ ensureBackgroundThread {
+ val eventType = eventTypesDB.getEventTypeWithId(config.defaultEventTypeId)
+ if (eventType != null) {
+ config.lastUsedCaldavCalendarId = eventType.caldavCalendarId
+ runOnUiThread {
+ settings_default_event_type.text = eventType.title
+ }
+ } else {
+ config.defaultEventTypeId = -1
+ updateDefaultEventTypeText()
+ }
+ }
+ }
+ }
+
+ private fun setupExportSettings() {
+ settings_export_holder.setOnClickListener {
+ val configItems = LinkedHashMap().apply {
+ put(IS_USING_SHARED_THEME, config.isUsingSharedTheme)
+ put(TEXT_COLOR, config.textColor)
+ put(BACKGROUND_COLOR, config.backgroundColor)
+ put(PRIMARY_COLOR, config.primaryColor)
+ put(APP_ICON_COLOR, config.appIconColor)
+ put(USE_ENGLISH, config.useEnglish)
+ put(WAS_USE_ENGLISH_TOGGLED, config.wasUseEnglishToggled)
+ put(WIDGET_BG_COLOR, config.widgetBgColor)
+ put(WIDGET_TEXT_COLOR, config.widgetTextColor)
+ put(WEEK_NUMBERS, config.showWeekNumbers)
+ put(START_WEEKLY_AT, config.startWeeklyAt)
+ put(VIBRATE, config.vibrateOnReminder)
+ put(LAST_EVENT_REMINDER_MINUTES, config.lastEventReminderMinutes1)
+ put(LAST_EVENT_REMINDER_MINUTES_2, config.lastEventReminderMinutes2)
+ put(LAST_EVENT_REMINDER_MINUTES_3, config.lastEventReminderMinutes3)
+ put(DISPLAY_PAST_EVENTS, config.displayPastEvents)
+ put(FONT_SIZE, config.fontSize)
+ put(LIST_WIDGET_VIEW_TO_OPEN, config.listWidgetViewToOpen)
+ put(REMINDER_AUDIO_STREAM, config.reminderAudioStream)
+ put(REPLACE_DESCRIPTION, config.replaceDescription)
+ put(SHOW_GRID, config.showGrid)
+ put(LOOP_REMINDERS, config.loopReminders)
+ put(DIM_PAST_EVENTS, config.dimPastEvents)
+ put(ALLOW_CHANGING_TIME_ZONES, config.allowChangingTimeZones)
+ put(USE_PREVIOUS_EVENT_REMINDERS, config.usePreviousEventReminders)
+ put(DEFAULT_REMINDER_1, config.defaultReminder1)
+ put(DEFAULT_REMINDER_2, config.defaultReminder2)
+ put(DEFAULT_REMINDER_3, config.defaultReminder3)
+ put(PULL_TO_REFRESH, config.pullToRefresh)
+ put(DEFAULT_START_TIME, config.defaultStartTime)
+ put(DEFAULT_DURATION, config.defaultDuration)
+ put(USE_SAME_SNOOZE, config.useSameSnooze)
+ put(SNOOZE_TIME, config.snoozeTime)
+ put(USE_24_HOUR_FORMAT, config.use24HourFormat)
+ put(SUNDAY_FIRST, config.isSundayFirst)
+ }
+
+ exportSettings(configItems)
+ }
+ }
+
+ private fun setupImportSettings() {
+ settings_import_holder.setOnClickListener {
+ if (isQPlus()) {
+ Intent(Intent.ACTION_GET_CONTENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "text/plain"
+ startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT)
+ }
+ } else {
+ handlePermission(PERMISSION_READ_STORAGE) {
+ if (it) {
+ FilePickerDialog(this) {
+ ensureBackgroundThread {
+ parseFile(File(it).inputStream())
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun parseFile(inputStream: InputStream?) {
+ if (inputStream == null) {
+ toast(R.string.unknown_error_occurred)
+ return
+ }
+
+ var importedItems = 0
+ val configValues = LinkedHashMap()
+ inputStream.bufferedReader().use {
+ while (true) {
+ try {
+ val line = it.readLine() ?: break
+ val split = line.split("=".toRegex(), 2)
+ if (split.size == 2) {
+ configValues[split[0]] = split[1]
+ }
+ importedItems++
+ } catch (e: Exception) {
+ showErrorToast(e)
+ }
+ }
+ }
+
+ for ((key, value) in configValues) {
+ when (key) {
+ IS_USING_SHARED_THEME -> config.isUsingSharedTheme = value.toBoolean()
+ TEXT_COLOR -> config.textColor = value.toInt()
+ BACKGROUND_COLOR -> config.backgroundColor = value.toInt()
+ PRIMARY_COLOR -> config.primaryColor = value.toInt()
+ APP_ICON_COLOR -> {
+ if (getAppIconColors().contains(value.toInt())) {
+ config.appIconColor = value.toInt()
+ checkAppIconColor()
+ }
+ }
+ USE_ENGLISH -> config.useEnglish = value.toBoolean()
+ WAS_USE_ENGLISH_TOGGLED -> config.wasUseEnglishToggled = value.toBoolean()
+ WIDGET_BG_COLOR -> config.widgetBgColor = value.toInt()
+ WIDGET_TEXT_COLOR -> config.widgetTextColor = value.toInt()
+ WEEK_NUMBERS -> config.showWeekNumbers = value.toBoolean()
+ START_WEEKLY_AT -> config.startWeeklyAt = value.toInt()
+ VIBRATE -> config.vibrateOnReminder = value.toBoolean()
+ LAST_EVENT_REMINDER_MINUTES -> config.lastEventReminderMinutes1 = value.toInt()
+ LAST_EVENT_REMINDER_MINUTES_2 -> config.lastEventReminderMinutes2 = value.toInt()
+ LAST_EVENT_REMINDER_MINUTES_3 -> config.lastEventReminderMinutes3 = value.toInt()
+ DISPLAY_PAST_EVENTS -> config.displayPastEvents = value.toInt()
+ FONT_SIZE -> config.fontSize = value.toInt()
+ LIST_WIDGET_VIEW_TO_OPEN -> config.listWidgetViewToOpen = value.toInt()
+ REMINDER_AUDIO_STREAM -> config.reminderAudioStream = value.toInt()
+ REPLACE_DESCRIPTION -> config.replaceDescription = value.toBoolean()
+ SHOW_GRID -> config.showGrid = value.toBoolean()
+ LOOP_REMINDERS -> config.loopReminders = value.toBoolean()
+ DIM_PAST_EVENTS -> config.dimPastEvents = value.toBoolean()
+ ALLOW_CHANGING_TIME_ZONES -> config.allowChangingTimeZones = value.toBoolean()
+ USE_PREVIOUS_EVENT_REMINDERS -> config.usePreviousEventReminders = value.toBoolean()
+ DEFAULT_REMINDER_1 -> config.defaultReminder1 = value.toInt()
+ DEFAULT_REMINDER_2 -> config.defaultReminder2 = value.toInt()
+ DEFAULT_REMINDER_3 -> config.defaultReminder3 = value.toInt()
+ PULL_TO_REFRESH -> config.pullToRefresh = value.toBoolean()
+ DEFAULT_START_TIME -> config.defaultStartTime = value.toInt()
+ DEFAULT_DURATION -> config.defaultDuration = value.toInt()
+ USE_SAME_SNOOZE -> config.useSameSnooze = value.toBoolean()
+ SNOOZE_TIME -> config.snoozeTime = value.toInt()
+ USE_24_HOUR_FORMAT -> config.use24HourFormat = value.toBoolean()
+ SUNDAY_FIRST -> config.isSundayFirst = value.toBoolean()
+ }
+ }
+
+ runOnUiThread {
+ val msg = if (configValues.size > 0) R.string.settings_imported_successfully else R.string.no_entries_for_importing
+ toast(msg)
+
+ setupSettingItems()
+ updateWidgets()
+ }
+ }
+}
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
new file mode 100644
index 000000000..ed185a195
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SimpleActivity.kt
@@ -0,0 +1,72 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.content.Context
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.CalendarContract
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.extensions.refreshCalDAVCalendars
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.helpers.ensureBackgroundThread
+
+open class SimpleActivity : BaseSimpleActivity() {
+ val CALDAV_REFRESH_DELAY = 3000L
+ 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_green,
+ 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,
+ 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 getAppLauncherName() = getString(R.string.app_launcher_name)
+
+ fun Context.syncCalDAVCalendars(callback: () -> Unit) {
+ calDAVRefreshCallback = callback
+ ensureBackgroundThread {
+ val uri = CalendarContract.Calendars.CONTENT_URI
+ contentResolver.unregisterContentObserver(calDAVSyncObserver)
+ contentResolver.registerContentObserver(uri, false, calDAVSyncObserver)
+ refreshCalDAVCalendars(config.caldavSyncedCalendarIds, true)
+ }
+ }
+
+ // 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()) {
+ override fun onChange(selfChange: Boolean) {
+ super.onChange(selfChange)
+ if (!selfChange) {
+ calDAVRefreshHandler.removeCallbacksAndMessages(null)
+ calDAVRefreshHandler.postDelayed({
+ ensureBackgroundThread {
+ unregisterObserver()
+ calDAVRefreshCallback?.invoke()
+ calDAVRefreshCallback = null
+ }
+ }, CALDAV_REFRESH_DELAY)
+ }
+ }
+ }
+
+ private fun unregisterObserver() {
+ contentResolver.unregisterContentObserver(calDAVSyncObserver)
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SnoozeReminderActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SnoozeReminderActivity.kt
new file mode 100644
index 000000000..0caf7acb1
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SnoozeReminderActivity.kt
@@ -0,0 +1,37 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.extensions.eventsDB
+import com.simplemobiletools.calendar.pro.extensions.rescheduleReminder
+import com.simplemobiletools.calendar.pro.helpers.EVENT_ID
+import com.simplemobiletools.commons.extensions.showPickSecondsDialogHelper
+import com.simplemobiletools.commons.helpers.ensureBackgroundThread
+
+class SnoozeReminderActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ showPickSecondsDialogHelper(config.snoozeTime, true, cancelCallback = { dialogCancelled() }) {
+ ensureBackgroundThread {
+ val eventId = intent.getLongExtra(EVENT_ID, 0L)
+ val event = eventsDB.getEventWithId(eventId)
+ config.snoozeTime = it / 60
+ rescheduleReminder(event, it / 60)
+ runOnUiThread {
+ finishActivity()
+ }
+ }
+ }
+ }
+
+ private fun dialogCancelled() {
+ finishActivity()
+ }
+
+ private fun finishActivity() {
+ finish()
+ overridePendingTransition(0, 0)
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SplashActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SplashActivity.kt
new file mode 100644
index 000000000..3cb6b6ad4
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SplashActivity.kt
@@ -0,0 +1,33 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.content.Intent
+import com.simplemobiletools.calendar.pro.extensions.getNewEventTimestampFromCode
+import com.simplemobiletools.calendar.pro.helpers.*
+import com.simplemobiletools.commons.activities.BaseSplashActivity
+import org.joda.time.DateTime
+
+class SplashActivity : BaseSplashActivity() {
+ override fun initActivity() {
+ when {
+ intent.extras?.containsKey(DAY_CODE) == true -> Intent(this, MainActivity::class.java).apply {
+ putExtra(DAY_CODE, intent.getStringExtra(DAY_CODE))
+ putExtra(VIEW_TO_OPEN, intent.getIntExtra(VIEW_TO_OPEN, LAST_VIEW))
+ startActivity(this)
+ }
+ intent.extras?.containsKey(EVENT_ID) == true -> Intent(this, MainActivity::class.java).apply {
+ putExtra(EVENT_ID, intent.getLongExtra(EVENT_ID, 0L))
+ putExtra(EVENT_OCCURRENCE_TS, intent.getLongExtra(EVENT_OCCURRENCE_TS, 0L))
+ startActivity(this)
+ }
+ intent.action == SHORTCUT_NEW_EVENT -> {
+ val dayCode = Formatter.getDayCodeFromDateTime(DateTime())
+ Intent(this, EventActivity::class.java).apply {
+ putExtra(NEW_EVENT_START_TS, getNewEventTimestampFromCode(dayCode))
+ startActivity(this)
+ }
+ }
+ else -> startActivity(Intent(this, MainActivity::class.java))
+ }
+ finish()
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetDateConfigureActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetDateConfigureActivity.kt
new file mode 100644
index 000000000..8852bb13d
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetDateConfigureActivity.kt
@@ -0,0 +1,141 @@
+package com.simplemobiletools.calendar.pro.activities
+
+import android.app.Activity
+import android.appwidget.AppWidgetManager
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.widget.SeekBar
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.helpers.Formatter
+import com.simplemobiletools.calendar.pro.helpers.LOW_ALPHA
+import com.simplemobiletools.calendar.pro.helpers.MyWidgetDateProvider
+import com.simplemobiletools.commons.dialogs.ColorPickerDialog
+import com.simplemobiletools.commons.extensions.adjustAlpha
+import com.simplemobiletools.commons.extensions.applyColorFilter
+import com.simplemobiletools.commons.extensions.setFillWithStroke
+import kotlinx.android.synthetic.main.widget_config_date.*
+
+class WidgetDateConfigureActivity : SimpleActivity() {
+ private var mBgAlpha = 0f
+ private var mWidgetId = 0
+ private var mBgColorWithoutTransparency = 0
+ private var mBgColor = 0
+ private var mTextColorWithoutTransparency = 0
+ private var mTextColor = 0
+ private var mWeakTextColor = 0
+ private var mPrimaryColor = 0
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ useDynamicTheme = false
+ super.onCreate(savedInstanceState)
+ setResult(Activity.RESULT_CANCELED)
+ setContentView(R.layout.widget_config_date)
+ initVariables()
+
+ val extras = intent.extras
+ if (extras != null)
+ mWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
+
+ if (mWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)
+ finish()
+
+ config_save.setOnClickListener { saveConfig() }
+ config_bg_color.setOnClickListener { pickBackgroundColor() }
+ config_text_color.setOnClickListener { pickTextColor() }
+ config_bg_seekbar.setColors(mTextColor, mPrimaryColor, mPrimaryColor)
+ widget_date_label.text = Formatter.getTodayDayNumber()
+ widget_month_label.text = Formatter.getCurrentMonthShort()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ window.decorView.setBackgroundColor(0)
+ }
+
+ private fun initVariables() {
+ mTextColorWithoutTransparency = config.widgetTextColor
+ updateColors()
+
+ mBgColor = config.widgetBgColor
+ mBgAlpha = Color.alpha(mBgColor) / 255.toFloat()
+
+ mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor))
+ config_bg_seekbar.setOnSeekBarChangeListener(bgSeekbarChangeListener)
+ config_bg_seekbar.progress = (mBgAlpha * 100).toInt()
+ updateBgColor()
+ }
+
+ private fun saveConfig() {
+ storeWidgetColors()
+ requestWidgetUpdate()
+
+ Intent().apply {
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+ setResult(Activity.RESULT_OK, this)
+ }
+ finish()
+ }
+
+ private fun storeWidgetColors() {
+ config.apply {
+ widgetBgColor = mBgColor
+ widgetTextColor = mTextColorWithoutTransparency
+ }
+ }
+
+ private fun pickBackgroundColor() {
+ ColorPickerDialog(this, mBgColorWithoutTransparency) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ mBgColorWithoutTransparency = color
+ updateBgColor()
+ }
+ }
+ }
+
+ private fun pickTextColor() {
+ ColorPickerDialog(this, mTextColor) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ mTextColorWithoutTransparency = color
+ updateColors()
+ }
+ }
+ }
+
+ private fun requestWidgetUpdate() {
+ Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, MyWidgetDateProvider::class.java).apply {
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(mWidgetId))
+ sendBroadcast(this)
+ }
+ }
+
+ private fun updateColors() {
+ mTextColor = mTextColorWithoutTransparency
+ mWeakTextColor = mTextColorWithoutTransparency.adjustAlpha(LOW_ALPHA)
+ mPrimaryColor = config.primaryColor
+
+ config_text_color.setFillWithStroke(mTextColor, Color.BLACK)
+ config_save.setTextColor(mTextColor)
+ widget_date_label.setTextColor(mTextColor)
+ widget_month_label.setTextColor(mTextColor)
+ }
+
+ private fun updateBgColor() {
+ mBgColor = mBgColorWithoutTransparency.adjustAlpha(mBgAlpha)
+ config_date_time_wrapper.background.applyColorFilter(mBgColor)
+ config_bg_color.setFillWithStroke(mBgColor, Color.BLACK)
+ config_save.setBackgroundColor(mBgColor)
+ }
+
+ private val bgSeekbarChangeListener = object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ mBgAlpha = progress.toFloat() / 100.toFloat()
+ updateBgColor()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/WidgetListConfigureActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetListConfigureActivity.kt
similarity index 63%
rename from app/src/main/kotlin/com/simplemobiletools/calendar/activities/WidgetListConfigureActivity.kt
rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetListConfigureActivity.kt
index cb1e1e0ae..29f2c8f50 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/WidgetListConfigureActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetListConfigureActivity.kt
@@ -1,31 +1,30 @@
-package com.simplemobiletools.calendar.activities
+package com.simplemobiletools.calendar.pro.activities
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
-import android.content.res.Resources
import android.graphics.Color
import android.os.Bundle
import android.widget.SeekBar
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.adapters.EventListAdapter
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.extensions.seconds
-import com.simplemobiletools.calendar.helpers.Formatter
-import com.simplemobiletools.calendar.helpers.MyWidgetListProvider
-import com.simplemobiletools.calendar.models.ListEvent
-import com.simplemobiletools.calendar.models.ListItem
-import com.simplemobiletools.calendar.models.ListSection
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.adapters.EventListAdapter
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.extensions.seconds
+import com.simplemobiletools.calendar.pro.helpers.Formatter
+import com.simplemobiletools.calendar.pro.helpers.MyWidgetListProvider
+import com.simplemobiletools.calendar.pro.models.ListEvent
+import com.simplemobiletools.calendar.pro.models.ListItem
+import com.simplemobiletools.calendar.pro.models.ListSection
import com.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.extensions.adjustAlpha
+import com.simplemobiletools.commons.extensions.applyColorFilter
+import com.simplemobiletools.commons.extensions.setFillWithStroke
+import com.simplemobiletools.commons.helpers.IS_CUSTOMIZING_COLORS
import kotlinx.android.synthetic.main.widget_config_list.*
import org.joda.time.DateTime
import java.util.*
class WidgetListConfigureActivity : SimpleActivity() {
- lateinit var mRes: Resources
- private var mPackageName = ""
-
private var mBgAlpha = 0f
private var mWidgetId = 0
private var mBgColorWithoutTransparency = 0
@@ -33,26 +32,24 @@ class WidgetListConfigureActivity : SimpleActivity() {
private var mTextColorWithoutTransparency = 0
private var mTextColor = 0
- private var mEventsAdapter: EventListAdapter? = null
-
public override fun onCreate(savedInstanceState: Bundle?) {
useDynamicTheme = false
super.onCreate(savedInstanceState)
setResult(Activity.RESULT_CANCELED)
setContentView(R.layout.widget_config_list)
- mPackageName = packageName
initVariables()
- val extras = intent.extras
- if (extras != null)
- mWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
+ val isCustomizingColors = intent.extras?.getBoolean(IS_CUSTOMIZING_COLORS) ?: false
+ mWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID
- if (mWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID)
+ if (mWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID && !isCustomizingColors) {
finish()
+ }
- mEventsAdapter = EventListAdapter(this, getListItems(), false, null, config_events_list) {}
- mEventsAdapter!!.updateTextColor(mTextColor)
- config_events_list.adapter = mEventsAdapter
+ EventListAdapter(this, getListItems(), false, null, config_events_list) {}.apply {
+ updateTextColor(mTextColor)
+ config_events_list.adapter = this
+ }
config_save.setOnClickListener { saveConfig() }
config_bg_color.setOnClickListener { pickBackgroundColor() }
@@ -68,18 +65,11 @@ class WidgetListConfigureActivity : SimpleActivity() {
}
private fun initVariables() {
- mRes = resources
-
mTextColorWithoutTransparency = config.widgetTextColor
updateColors()
mBgColor = config.widgetBgColor
- if (mBgColor == 1) {
- mBgColor = Color.BLACK
- mBgAlpha = .2f
- } else {
- mBgAlpha = Color.alpha(mBgColor) / 255.toFloat()
- }
+ mBgAlpha = Color.alpha(mBgColor) / 255.toFloat()
mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor))
config_bg_seekbar.setOnSeekBarChangeListener(bgSeekbarChangeListener)
@@ -106,16 +96,20 @@ class WidgetListConfigureActivity : SimpleActivity() {
}
private fun pickBackgroundColor() {
- ColorPickerDialog(this, mBgColorWithoutTransparency) {
- mBgColorWithoutTransparency = it
- updateBgColor()
+ ColorPickerDialog(this, mBgColorWithoutTransparency) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ mBgColorWithoutTransparency = color
+ updateBgColor()
+ }
}
}
private fun pickTextColor() {
- ColorPickerDialog(this, mTextColor) {
- mTextColorWithoutTransparency = it
- updateColors()
+ ColorPickerDialog(this, mTextColor) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ mTextColorWithoutTransparency = color
+ updateColors()
+ }
}
}
@@ -128,15 +122,15 @@ class WidgetListConfigureActivity : SimpleActivity() {
private fun updateColors() {
mTextColor = mTextColorWithoutTransparency
- mEventsAdapter?.updateTextColor(mTextColor)
- config_text_color.setBackgroundColor(mTextColor)
+ (config_events_list.adapter as? EventListAdapter)?.updateTextColor(mTextColor)
+ config_text_color.setFillWithStroke(mTextColor, Color.BLACK)
config_save.setTextColor(mTextColor)
}
private fun updateBgColor() {
mBgColor = mBgColorWithoutTransparency.adjustAlpha(mBgAlpha)
- config_events_list.setBackgroundColor(mBgColor)
- config_bg_color.setBackgroundColor(mBgColor)
+ config_events_list.background.applyColorFilter(mBgColor)
+ config_bg_color.setFillWithStroke(mBgColor, Color.BLACK)
config_save.setBackgroundColor(mBgColor)
}
@@ -145,24 +139,24 @@ class WidgetListConfigureActivity : SimpleActivity() {
var dateTime = DateTime.now().withTime(0, 0, 0, 0).plusDays(1)
var code = Formatter.getDayCodeFromTS(dateTime.seconds())
var day = Formatter.getDayTitle(this, code)
- listItems.add(ListSection(day))
+ listItems.add(ListSection(day, code, false, false))
var time = dateTime.withHourOfDay(7)
- listItems.add(ListEvent(1, time.seconds(), time.plusMinutes(30).seconds(), getString(R.string.sample_title_1), getString(R.string.sample_description_1), false, config.primaryColor))
+ listItems.add(ListEvent(1, time.seconds(), time.plusMinutes(30).seconds(), getString(R.string.sample_title_1), getString(R.string.sample_description_1), false, config.primaryColor, "", false, false))
time = dateTime.withHourOfDay(8)
- listItems.add(ListEvent(2, time.seconds(), time.plusHours(1).seconds(), getString(R.string.sample_title_2), getString(R.string.sample_description_2), false, config.primaryColor))
+ listItems.add(ListEvent(2, time.seconds(), time.plusHours(1).seconds(), getString(R.string.sample_title_2), getString(R.string.sample_description_2), false, config.primaryColor, "", false, false))
dateTime = dateTime.plusDays(1)
code = Formatter.getDayCodeFromTS(dateTime.seconds())
day = Formatter.getDayTitle(this, code)
- listItems.add(ListSection(day))
+ listItems.add(ListSection(day, code, false, false))
time = dateTime.withHourOfDay(8)
- listItems.add(ListEvent(3, time.seconds(), time.plusHours(1).seconds(), getString(R.string.sample_title_3), "", false, config.primaryColor))
+ listItems.add(ListEvent(3, time.seconds(), time.plusHours(1).seconds(), getString(R.string.sample_title_3), "", false, config.primaryColor, "", false, false))
time = dateTime.withHourOfDay(13)
- listItems.add(ListEvent(4, time.seconds(), time.plusHours(1).seconds(), getString(R.string.sample_title_4), getString(R.string.sample_description_4), false, config.primaryColor))
+ listItems.add(ListEvent(4, time.seconds(), time.plusHours(1).seconds(), getString(R.string.sample_title_4), getString(R.string.sample_description_4), false, config.primaryColor, "", false, false))
time = dateTime.withHourOfDay(18)
- listItems.add(ListEvent(5, time.seconds(), time.plusMinutes(10).seconds(), getString(R.string.sample_title_5), "", false, config.primaryColor))
+ listItems.add(ListEvent(5, time.seconds(), time.plusMinutes(10).seconds(), getString(R.string.sample_title_5), "", false, config.primaryColor, "", false, false))
return listItems
}
@@ -173,12 +167,8 @@ class WidgetListConfigureActivity : SimpleActivity() {
updateBgColor()
}
- override fun onStartTrackingTouch(seekBar: SeekBar) {
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
- }
-
- override fun onStopTrackingTouch(seekBar: SeekBar) {
-
- }
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/WidgetMonthlyConfigureActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetMonthlyConfigureActivity.kt
similarity index 70%
rename from app/src/main/kotlin/com/simplemobiletools/calendar/activities/WidgetMonthlyConfigureActivity.kt
rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetMonthlyConfigureActivity.kt
index 311976787..1b0302585 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/activities/WidgetMonthlyConfigureActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetMonthlyConfigureActivity.kt
@@ -1,37 +1,35 @@
-package com.simplemobiletools.calendar.activities
+package com.simplemobiletools.calendar.pro.activities
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
-import android.content.res.Resources
import android.graphics.Color
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.SeekBar
import android.widget.TextView
-import com.simplemobiletools.calendar.R
-import com.simplemobiletools.calendar.extensions.addDayEvents
-import com.simplemobiletools.calendar.extensions.addDayNumber
-import com.simplemobiletools.calendar.extensions.config
-import com.simplemobiletools.calendar.helpers.LOW_ALPHA
-import com.simplemobiletools.calendar.helpers.MonthlyCalendarImpl
-import com.simplemobiletools.calendar.helpers.MyWidgetMonthlyProvider
-import com.simplemobiletools.calendar.interfaces.MonthlyCalendar
-import com.simplemobiletools.calendar.models.DayMonthly
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.extensions.addDayEvents
+import com.simplemobiletools.calendar.pro.extensions.addDayNumber
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.helpers.LOW_ALPHA
+import com.simplemobiletools.calendar.pro.helpers.MonthlyCalendarImpl
+import com.simplemobiletools.calendar.pro.helpers.MyWidgetMonthlyProvider
+import com.simplemobiletools.calendar.pro.interfaces.MonthlyCalendar
+import com.simplemobiletools.calendar.pro.models.DayMonthly
import com.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.extensions.adjustAlpha
import com.simplemobiletools.commons.extensions.applyColorFilter
import com.simplemobiletools.commons.extensions.beVisible
+import com.simplemobiletools.commons.extensions.setFillWithStroke
import kotlinx.android.synthetic.main.first_row.*
import kotlinx.android.synthetic.main.top_navigation.*
import kotlinx.android.synthetic.main.widget_config_monthly.*
import org.joda.time.DateTime
class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar {
- lateinit var mRes: Resources
private var mDays: List? = null
- private var mPackageName = ""
private var dayLabelHeight = 0
private var mBgAlpha = 0f
@@ -69,26 +67,18 @@ class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar {
}
private fun initVariables() {
- mPackageName = packageName
- mRes = resources
-
mTextColorWithoutTransparency = config.widgetTextColor
updateColors()
mBgColor = config.widgetBgColor
- if (mBgColor == 1) {
- mBgColor = Color.BLACK
- mBgAlpha = .2f
- } else {
- mBgAlpha = Color.alpha(mBgColor) / 255.toFloat()
- }
+ mBgAlpha = Color.alpha(mBgColor) / 255.toFloat()
mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor))
config_bg_seekbar.setOnSeekBarChangeListener(bgSeekbarChangeListener)
config_bg_seekbar.progress = (mBgAlpha * 100).toInt()
updateBgColor()
- MonthlyCalendarImpl(this, applicationContext).updateMonthlyCalendar(DateTime(), false)
+ MonthlyCalendarImpl(this, applicationContext).updateMonthlyCalendar(DateTime().withDayOfMonth(1))
}
private fun saveConfig() {
@@ -110,17 +100,21 @@ class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar {
}
private fun pickBackgroundColor() {
- ColorPickerDialog(this, mBgColorWithoutTransparency) {
- mBgColorWithoutTransparency = it
- updateBgColor()
+ ColorPickerDialog(this, mBgColorWithoutTransparency) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ mBgColorWithoutTransparency = color
+ updateBgColor()
+ }
}
}
private fun pickTextColor() {
- ColorPickerDialog(this, mTextColor) {
- mTextColorWithoutTransparency = it
- updateColors()
- updateDays()
+ ColorPickerDialog(this, mTextColor) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ mTextColorWithoutTransparency = color
+ updateColors()
+ updateDays()
+ }
}
}
@@ -139,27 +133,27 @@ class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar {
top_left_arrow.applyColorFilter(mTextColor)
top_right_arrow.applyColorFilter(mTextColor)
top_value.setTextColor(mTextColor)
- config_text_color.setBackgroundColor(mTextColor)
+ config_text_color.setFillWithStroke(mTextColor, Color.BLACK)
config_save.setTextColor(mTextColor)
updateLabels()
}
private fun updateBgColor() {
mBgColor = mBgColorWithoutTransparency.adjustAlpha(mBgAlpha)
- config_calendar.setBackgroundColor(mBgColor)
- config_bg_color.setBackgroundColor(mBgColor)
+ config_calendar.background.applyColorFilter(mBgColor)
+ config_bg_color.setFillWithStroke(mBgColor, Color.BLACK)
config_save.setBackgroundColor(mBgColor)
}
private fun updateDays() {
val len = mDays!!.size
- if (applicationContext.config.displayWeekNumbers) {
+ if (applicationContext.config.showWeekNumbers) {
week_num.setTextColor(mTextColor)
week_num.beVisible()
for (i in 0..5) {
- findViewById(mRes.getIdentifier("week_num_$i", "id", mPackageName)).apply {
+ findViewById(resources.getIdentifier("week_num_$i", "id", packageName)).apply {
text = "${mDays!![i * 7 + 3].weekOfYear}:"
setTextColor(mTextColor)
beVisible()
@@ -167,14 +161,14 @@ class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar {
}
}
- val dividerMargin = mRes.displayMetrics.density.toInt()
+ val dividerMargin = resources.displayMetrics.density.toInt()
for (i in 0 until len) {
- findViewById(mRes.getIdentifier("day_$i", "id", mPackageName)).apply {
+ findViewById(resources.getIdentifier("day_$i", "id", packageName)).apply {
val day = mDays!![i]
removeAllViews()
context.addDayNumber(mTextColor, day, this, dayLabelHeight) { dayLabelHeight = it }
- context.addDayEvents(day, this, mRes, dividerMargin)
+ context.addDayEvents(day, this, resources, dividerMargin)
}
}
}
@@ -185,16 +179,12 @@ class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar {
updateBgColor()
}
- override fun onStartTrackingTouch(seekBar: SeekBar) {
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
- }
-
- override fun onStopTrackingTouch(seekBar: SeekBar) {
-
- }
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
}
- override fun updateMonthlyCalendar(context: Context, month: String, days: List, checkedEvents: Boolean) {
+ override fun updateMonthlyCalendar(context: Context, month: String, days: ArrayList, checkedEvents: Boolean, currTargetDate: DateTime) {
runOnUiThread {
mDays = days
top_value.text = month
@@ -204,7 +194,7 @@ class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar {
private fun updateLabels() {
for (i in 0..6) {
- findViewById(mRes.getIdentifier("label_$i", "id", mPackageName)).apply {
+ findViewById(resources.getIdentifier("label_$i", "id", packageName)).apply {
setTextColor(mTextColor)
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/AutoCompleteTextViewAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/AutoCompleteTextViewAdapter.kt
new file mode 100644
index 000000000..21321a699
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/AutoCompleteTextViewAdapter.kt
@@ -0,0 +1,84 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.graphics.drawable.BitmapDrawable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.Filter
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.activities.SimpleActivity
+import com.simplemobiletools.calendar.pro.models.Attendee
+import com.simplemobiletools.commons.extensions.normalizeString
+import com.simplemobiletools.commons.helpers.SimpleContactsHelper
+import kotlinx.android.synthetic.main.item_autocomplete_email_name.view.*
+
+class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList) : ArrayAdapter(activity, 0, contacts) {
+ var resultList = ArrayList()
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val contact = resultList[position]
+ var listItem = convertView
+ if (listItem == null || listItem.tag != contact.name.isNotEmpty()) {
+ val layout = if (contact.name.isNotEmpty()) R.layout.item_autocomplete_email_name else R.layout.item_autocomplete_email
+ listItem = LayoutInflater.from(activity).inflate(layout, parent, false)
+ }
+
+ val nameToUse = when {
+ contact.name.isNotEmpty() -> contact.name
+ contact.email.isNotEmpty() -> contact.email
+ else -> "A"
+ }
+
+ val placeholder = BitmapDrawable(activity.resources, SimpleContactsHelper(context).getContactLetterIcon(nameToUse))
+ listItem!!.apply {
+ tag = contact.name.isNotEmpty()
+ item_autocomplete_name?.text = contact.name
+ item_autocomplete_email?.text = contact.email
+
+ contact.updateImage(context, item_autocomplete_image, placeholder)
+ }
+
+ return listItem
+ }
+
+ override fun getFilter() = object : Filter() {
+ override fun performFiltering(constraint: CharSequence?): FilterResults {
+ val filterResults = Filter.FilterResults()
+ if (constraint != null) {
+ resultList.clear()
+ val searchString = constraint.toString().normalizeString()
+ contacts.forEach {
+ if (it.email.contains(searchString, true) || it.name.contains(searchString, true)) {
+ resultList.add(it)
+ }
+ }
+
+ resultList.sortWith(compareBy
+ { it.name.startsWith(searchString, true) }.thenBy
+ { it.email.startsWith(searchString, true) }.thenBy
+ { it.name.contains(searchString, true) }.thenBy
+ { it.email.contains(searchString, true) })
+ resultList.reverse()
+
+ filterResults.values = resultList
+ filterResults.count = resultList.size
+ }
+ return filterResults
+ }
+
+ override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
+ if (results?.count ?: -1 > 0) {
+ notifyDataSetChanged()
+ } else {
+ notifyDataSetInvalidated()
+ }
+ }
+
+ override fun convertResultToString(resultValue: Any?) = (resultValue as? Attendee)?.getPublicName()
+ }
+
+ override fun getItem(index: Int) = resultList[index]
+
+ override fun getCount() = resultList.size
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/DayEventsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/DayEventsAdapter.kt
new file mode 100644
index 000000000..0fbab41de
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/DayEventsAdapter.kt
@@ -0,0 +1,162 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.view.Menu
+import android.view.View
+import android.view.ViewGroup
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.activities.SimpleActivity
+import com.simplemobiletools.calendar.pro.dialogs.DeleteEventDialog
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.extensions.eventsHelper
+import com.simplemobiletools.calendar.pro.extensions.handleEventDeleting
+import com.simplemobiletools.calendar.pro.extensions.shareEvents
+import com.simplemobiletools.calendar.pro.helpers.Formatter
+import com.simplemobiletools.calendar.pro.helpers.ITEM_EVENT
+import com.simplemobiletools.calendar.pro.helpers.ITEM_EVENT_SIMPLE
+import com.simplemobiletools.calendar.pro.helpers.LOW_ALPHA
+import com.simplemobiletools.calendar.pro.models.Event
+import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
+import com.simplemobiletools.commons.extensions.adjustAlpha
+import com.simplemobiletools.commons.extensions.applyColorFilter
+import com.simplemobiletools.commons.extensions.beInvisible
+import com.simplemobiletools.commons.extensions.beInvisibleIf
+import com.simplemobiletools.commons.helpers.ensureBackgroundThread
+import com.simplemobiletools.commons.views.MyRecyclerView
+import kotlinx.android.synthetic.main.event_item_day_view.view.*
+
+class DayEventsAdapter(activity: SimpleActivity, val events: ArrayList, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit)
+ : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) {
+
+ private val allDayString = resources.getString(R.string.all_day)
+ private val replaceDescriptionWithLocation = activity.config.replaceDescription
+ private val dimPastEvents = activity.config.dimPastEvents
+
+ init {
+ setupDragListener(true)
+ }
+
+ override fun getActionMenuId() = R.menu.cab_day
+
+ override fun prepareActionMode(menu: Menu) {}
+
+ override fun actionItemPressed(id: Int) {
+ when (id) {
+ R.id.cab_share -> shareEvents()
+ R.id.cab_delete -> askConfirmDelete()
+ }
+ }
+
+ override fun getSelectableItemCount() = events.size
+
+ override fun getIsItemSelectable(position: Int) = true
+
+ override fun getItemSelectionKey(position: Int) = events.getOrNull(position)?.id?.toInt()
+
+ override fun getItemKeyPosition(key: Int) = events.indexOfFirst { it.id?.toInt() == key }
+
+ override fun onActionModeCreated() {}
+
+ override fun onActionModeDestroyed() {}
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val layoutId = when (viewType) {
+ ITEM_EVENT -> R.layout.event_item_day_view
+ else -> R.layout.event_item_day_view_simple
+ }
+ return createViewHolder(layoutId, parent)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val event = events[position]
+ holder.bindView(event, true, true) { itemView, layoutPosition ->
+ setupView(itemView, event)
+ }
+ bindViewHolder(holder)
+ }
+
+ override fun getItemCount() = events.size
+
+ override fun getItemViewType(position: Int): Int {
+ val event = events[position]
+ val detailField = if (replaceDescriptionWithLocation) event.location else event.description
+ return if (detailField.isNotEmpty()) {
+ ITEM_EVENT
+ } else if (event.startTS == event.endTS) {
+ ITEM_EVENT_SIMPLE
+ } else if (event.getIsAllDay()) {
+ val startCode = Formatter.getDayCodeFromTS(event.startTS)
+ val endCode = Formatter.getDayCodeFromTS(event.endTS)
+ if (startCode == endCode) {
+ ITEM_EVENT_SIMPLE
+ } else {
+ ITEM_EVENT
+ }
+ } else {
+ ITEM_EVENT
+ }
+ }
+
+ private fun setupView(view: View, event: Event) {
+ view.apply {
+ event_item_frame.isSelected = selectedKeys.contains(event.id?.toInt())
+ event_item_title.text = event.title
+ event_item_description?.text = if (replaceDescriptionWithLocation) event.location else event.description
+ event_item_start.text = if (event.getIsAllDay()) allDayString else Formatter.getTimeFromTS(context, event.startTS)
+ event_item_end?.beInvisibleIf(event.startTS == event.endTS)
+ event_item_color_bar.background.applyColorFilter(event.color)
+
+ if (event.startTS != event.endTS) {
+ val startCode = Formatter.getDayCodeFromTS(event.startTS)
+ val endCode = Formatter.getDayCodeFromTS(event.endTS)
+
+ event_item_end?.apply {
+ text = Formatter.getTimeFromTS(context, event.endTS)
+ if (startCode != endCode) {
+ if (event.getIsAllDay()) {
+ text = Formatter.getDateFromCode(context, endCode, true)
+ } else {
+ append(" (${Formatter.getDateFromCode(context, endCode, true)})")
+ }
+ } else if (event.getIsAllDay()) {
+ beInvisible()
+ }
+ }
+ }
+
+ var newTextColor = textColor
+ if (dimPastEvents && event.isPastEvent) {
+ newTextColor = newTextColor.adjustAlpha(LOW_ALPHA)
+ }
+
+ event_item_start.setTextColor(newTextColor)
+ event_item_end?.setTextColor(newTextColor)
+ event_item_title.setTextColor(newTextColor)
+ event_item_description?.setTextColor(newTextColor)
+ }
+ }
+
+ private fun shareEvents() = activity.shareEvents(selectedKeys.distinct().map { it.toLong() })
+
+ private fun askConfirmDelete() {
+ val eventIds = selectedKeys.map { it.toLong() }.toMutableList()
+ val eventsToDelete = events.filter { selectedKeys.contains(it.id?.toInt()) }
+ val timestamps = eventsToDelete.map { it.startTS }
+ val positions = getSelectedItemPositions()
+
+ val hasRepeatableEvent = eventsToDelete.any { it.repeatInterval > 0 }
+ DeleteEventDialog(activity, eventIds, hasRepeatableEvent) { it ->
+ events.removeAll(eventsToDelete)
+
+ ensureBackgroundThread {
+ val nonRepeatingEventIDs = eventsToDelete.asSequence().filter { it.repeatInterval == 0 }.mapNotNull { it.id }.toMutableList()
+ activity.eventsHelper.deleteEvents(nonRepeatingEventIDs, true)
+
+ val repeatingEventIDs = eventsToDelete.asSequence().filter { it.repeatInterval != 0 }.mapNotNull { it.id }.toList()
+ activity.handleEventDeleting(repeatingEventIDs, timestamps, it)
+ activity.runOnUiThread {
+ removeSelectedItems(positions)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListAdapter.kt
new file mode 100644
index 000000000..f75132b73
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListAdapter.kt
@@ -0,0 +1,221 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.view.Menu
+import android.view.View
+import android.view.ViewGroup
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.activities.SimpleActivity
+import com.simplemobiletools.calendar.pro.dialogs.DeleteEventDialog
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.extensions.eventsHelper
+import com.simplemobiletools.calendar.pro.extensions.handleEventDeleting
+import com.simplemobiletools.calendar.pro.extensions.shareEvents
+import com.simplemobiletools.calendar.pro.helpers.*
+import com.simplemobiletools.calendar.pro.helpers.Formatter
+import com.simplemobiletools.calendar.pro.models.ListEvent
+import com.simplemobiletools.calendar.pro.models.ListItem
+import com.simplemobiletools.calendar.pro.models.ListSection
+import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
+import com.simplemobiletools.commons.extensions.adjustAlpha
+import com.simplemobiletools.commons.extensions.applyColorFilter
+import com.simplemobiletools.commons.extensions.beInvisible
+import com.simplemobiletools.commons.extensions.beInvisibleIf
+import com.simplemobiletools.commons.helpers.ensureBackgroundThread
+import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
+import com.simplemobiletools.commons.views.MyRecyclerView
+import kotlinx.android.synthetic.main.event_list_item.view.*
+import kotlinx.android.synthetic.main.event_list_section.view.*
+import java.util.*
+
+class EventListAdapter(activity: SimpleActivity, var listItems: ArrayList, val allowLongClick: Boolean, val listener: RefreshRecyclerViewListener?,
+ recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) {
+
+ private val topDivider = resources.getDrawable(R.drawable.divider_width)
+ private val allDayString = resources.getString(R.string.all_day)
+ private val replaceDescription = activity.config.replaceDescription
+ private val dimPastEvents = activity.config.dimPastEvents
+ private val now = getNowSeconds()
+ private var use24HourFormat = activity.config.use24HourFormat
+ private var currentItemsHash = listItems.hashCode()
+
+ init {
+ setupDragListener(true)
+ val firstNonPastSectionIndex = listItems.indexOfFirst { it is ListSection && !it.isPastSection }
+ if (firstNonPastSectionIndex != -1) {
+ activity.runOnUiThread {
+ recyclerView.scrollToPosition(firstNonPastSectionIndex)
+ }
+ }
+ }
+
+ override fun getActionMenuId() = R.menu.cab_event_list
+
+ override fun prepareActionMode(menu: Menu) {}
+
+ override fun actionItemPressed(id: Int) {
+ when (id) {
+ R.id.cab_share -> shareEvents()
+ R.id.cab_delete -> askConfirmDelete()
+ }
+ }
+
+ override fun getSelectableItemCount() = listItems.filter { it is ListEvent }.size
+
+ override fun getIsItemSelectable(position: Int) = listItems[position] is ListEvent
+
+ override fun getItemSelectionKey(position: Int) = (listItems.getOrNull(position) as? ListEvent)?.hashCode()
+
+ override fun getItemKeyPosition(key: Int) = listItems.indexOfFirst { (it as? ListEvent)?.hashCode() == key }
+
+ override fun onActionModeCreated() {}
+
+ override fun onActionModeDestroyed() {}
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyRecyclerViewAdapter.ViewHolder {
+ val layoutId = when (viewType) {
+ ITEM_EVENT -> R.layout.event_list_item
+ ITEM_EVENT_SIMPLE -> R.layout.event_list_item_simple
+ else -> R.layout.event_list_section
+ }
+ return createViewHolder(layoutId, parent)
+ }
+
+ override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
+ val listItem = listItems[position]
+ holder.bindView(listItem, true, allowLongClick && listItem is ListEvent) { itemView, layoutPosition ->
+ if (listItem is ListSection) {
+ setupListSection(itemView, listItem, position)
+ } else if (listItem is ListEvent) {
+ setupListEvent(itemView, listItem)
+ }
+ }
+ bindViewHolder(holder)
+ }
+
+ override fun getItemCount() = listItems.size
+
+ override fun getItemViewType(position: Int) = if (listItems[position] is ListEvent) {
+ val event = listItems[position] as ListEvent
+ val detailField = if (replaceDescription) event.location else event.description
+ if (detailField.isNotEmpty()) {
+ ITEM_EVENT
+ } else if (event.startTS == event.endTS) {
+ ITEM_EVENT_SIMPLE
+ } else if (event.isAllDay) {
+ val startCode = Formatter.getDayCodeFromTS(event.startTS)
+ val endCode = Formatter.getDayCodeFromTS(event.endTS)
+ if (startCode == endCode) {
+ ITEM_EVENT_SIMPLE
+ } else {
+ ITEM_EVENT
+ }
+ } else {
+ ITEM_EVENT
+ }
+ } else {
+ ITEM_HEADER
+ }
+
+ fun toggle24HourFormat(use24HourFormat: Boolean) {
+ this.use24HourFormat = use24HourFormat
+ notifyDataSetChanged()
+ }
+
+ fun updateListItems(newListItems: ArrayList) {
+ if (newListItems.hashCode() != currentItemsHash) {
+ currentItemsHash = newListItems.hashCode()
+ listItems = newListItems.clone() as ArrayList
+ recyclerView.resetItemCount()
+ notifyDataSetChanged()
+ finishActMode()
+ }
+ }
+
+ private fun setupListEvent(view: View, listEvent: ListEvent) {
+ view.apply {
+ event_item_frame.isSelected = selectedKeys.contains(listEvent.hashCode())
+ event_item_title.text = listEvent.title
+ event_item_description?.text = if (replaceDescription) listEvent.location else listEvent.description
+ event_item_start.text = if (listEvent.isAllDay) allDayString else Formatter.getTimeFromTS(context, listEvent.startTS)
+ event_item_end?.beInvisibleIf(listEvent.startTS == listEvent.endTS)
+ event_item_color_bar.background.applyColorFilter(listEvent.color)
+
+ if (listEvent.startTS != listEvent.endTS) {
+ event_item_end?.apply {
+ val startCode = Formatter.getDayCodeFromTS(listEvent.startTS)
+ val endCode = Formatter.getDayCodeFromTS(listEvent.endTS)
+
+ text = Formatter.getTimeFromTS(context, listEvent.endTS)
+ if (startCode != endCode) {
+ if (listEvent.isAllDay) {
+ text = Formatter.getDateFromCode(context, endCode, true)
+ } else {
+ append(" (${Formatter.getDateFromCode(context, endCode, true)})")
+ }
+ } else if (listEvent.isAllDay) {
+ beInvisible()
+ }
+ }
+ }
+
+ var startTextColor = textColor
+ var endTextColor = textColor
+ if (listEvent.isAllDay || listEvent.startTS <= now && listEvent.endTS <= now) {
+ if (listEvent.isAllDay && Formatter.getDayCodeFromTS(listEvent.startTS) == Formatter.getDayCodeFromTS(now)) {
+ startTextColor = primaryColor
+ }
+
+ if (dimPastEvents && listEvent.isPastEvent) {
+ startTextColor = startTextColor.adjustAlpha(LOW_ALPHA)
+ endTextColor = endTextColor.adjustAlpha(LOW_ALPHA)
+ }
+ } else if (listEvent.startTS <= now && listEvent.endTS >= now) {
+ startTextColor = primaryColor
+ }
+
+ event_item_start.setTextColor(startTextColor)
+ event_item_end?.setTextColor(endTextColor)
+ event_item_title.setTextColor(startTextColor)
+ event_item_description?.setTextColor(startTextColor)
+ }
+ }
+
+ private fun setupListSection(view: View, listSection: ListSection, position: Int) {
+ view.event_section_title.apply {
+ text = listSection.title
+ setCompoundDrawablesWithIntrinsicBounds(null, if (position == 0) null else topDivider, null, null)
+ var color = if (listSection.isToday) primaryColor else textColor
+ if (dimPastEvents && listSection.isPastSection) {
+ color = color.adjustAlpha(LOW_ALPHA)
+ }
+ setTextColor(color)
+ }
+ }
+
+ private fun shareEvents() = activity.shareEvents(getSelectedEventIds())
+
+ private fun getSelectedEventIds() = listItems.filter { it is ListEvent && selectedKeys.contains(it.hashCode()) }.map { (it as ListEvent).id }.toMutableList() as ArrayList
+
+ private fun askConfirmDelete() {
+ val eventIds = getSelectedEventIds()
+ val eventsToDelete = listItems.filter { selectedKeys.contains((it as? ListEvent)?.hashCode()) } as List
+ val timestamps = eventsToDelete.mapNotNull { (it as? ListEvent)?.startTS }
+
+ val hasRepeatableEvent = eventsToDelete.any { it.isRepeatable }
+ DeleteEventDialog(activity, eventIds, hasRepeatableEvent) {
+ listItems.removeAll(eventsToDelete)
+
+ ensureBackgroundThread {
+ val nonRepeatingEventIDs = eventsToDelete.filter { !it.isRepeatable }.mapNotNull { it.id }.toMutableList()
+ activity.eventsHelper.deleteEvents(nonRepeatingEventIDs, true)
+
+ val repeatingEventIDs = eventsToDelete.filter { it.isRepeatable }.map { it.id }
+ activity.handleEventDeleting(repeatingEventIDs, timestamps, it)
+ activity.runOnUiThread {
+ listener?.refreshItems()
+ finishActMode()
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapter.kt
new file mode 100644
index 000000000..50ff6beed
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapter.kt
@@ -0,0 +1,208 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.content.Context
+import android.content.Intent
+import android.view.View
+import android.widget.RemoteViews
+import android.widget.RemoteViewsService
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.R.id.event_item_holder
+import com.simplemobiletools.calendar.pro.R.id.event_section_title
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.extensions.eventsHelper
+import com.simplemobiletools.calendar.pro.extensions.getWidgetFontSize
+import com.simplemobiletools.calendar.pro.extensions.seconds
+import com.simplemobiletools.calendar.pro.helpers.*
+import com.simplemobiletools.calendar.pro.helpers.Formatter
+import com.simplemobiletools.calendar.pro.models.Event
+import com.simplemobiletools.calendar.pro.models.ListEvent
+import com.simplemobiletools.calendar.pro.models.ListItem
+import com.simplemobiletools.calendar.pro.models.ListSection
+import com.simplemobiletools.commons.extensions.adjustAlpha
+import com.simplemobiletools.commons.extensions.setBackgroundColor
+import com.simplemobiletools.commons.extensions.setText
+import com.simplemobiletools.commons.extensions.setTextSize
+import org.joda.time.DateTime
+import java.util.*
+
+class EventListWidgetAdapter(val context: Context) : RemoteViewsService.RemoteViewsFactory {
+ private val ITEM_EVENT = 0
+ private val ITEM_HEADER = 1
+
+ private val allDayString = context.resources.getString(R.string.all_day)
+ private var events = ArrayList()
+ private var textColor = context.config.widgetTextColor
+ private var weakTextColor = textColor.adjustAlpha(LOW_ALPHA)
+ private val replaceDescription = context.config.replaceDescription
+ private val dimPastEvents = context.config.dimPastEvents
+ private var mediumFontSize = context.getWidgetFontSize()
+
+ override fun getViewAt(position: Int): RemoteViews? {
+ val type = getItemViewType(position)
+ val remoteView: RemoteViews
+
+ if (type == ITEM_EVENT) {
+ val event = events[position] as ListEvent
+ val layout = getItemViewLayout(event)
+ remoteView = RemoteViews(context.packageName, layout)
+ setupListEvent(remoteView, event)
+ } else {
+ remoteView = RemoteViews(context.packageName, R.layout.event_list_section_widget)
+ val section = events.getOrNull(position) as? ListSection
+ if (section != null) {
+ setupListSection(remoteView, section)
+ }
+ }
+
+ return remoteView
+ }
+
+ private fun getItemViewLayout(event: ListEvent): Int {
+ val detailField = if (replaceDescription) event.location else event.description
+ return if (detailField.isNotEmpty()) {
+ R.layout.event_list_item_widget
+ } else if (event.startTS == event.endTS) {
+ R.layout.event_list_item_widget_simple
+ } else if (event.isAllDay) {
+ val startCode = Formatter.getDayCodeFromTS(event.startTS)
+ val endCode = Formatter.getDayCodeFromTS(event.endTS)
+ if (startCode == endCode) {
+ R.layout.event_list_item_widget_simple
+ } else {
+ R.layout.event_list_item_widget
+ }
+ } else {
+ R.layout.event_list_item_widget
+ }
+ }
+
+ private fun setupListEvent(remoteView: RemoteViews, item: ListEvent) {
+ var curTextColor = textColor
+ remoteView.apply {
+ setText(R.id.event_item_title, item.title)
+ setText(R.id.event_item_description, if (replaceDescription) item.location else item.description)
+ setText(R.id.event_item_start, if (item.isAllDay) allDayString else Formatter.getTimeFromTS(context, item.startTS))
+ setBackgroundColor(R.id.event_item_color_bar, item.color)
+
+ if (item.startTS == item.endTS) {
+ setViewVisibility(R.id.event_item_end, View.INVISIBLE)
+ } else {
+ setViewVisibility(R.id.event_item_end, View.VISIBLE)
+ var endString = Formatter.getTimeFromTS(context, item.endTS)
+ val startCode = Formatter.getDayCodeFromTS(item.startTS)
+ val endCode = Formatter.getDayCodeFromTS(item.endTS)
+
+ if (startCode != endCode) {
+ if (item.isAllDay) {
+ endString = Formatter.getDateFromCode(context, endCode, true)
+ } else {
+ endString += " (${Formatter.getDateFromCode(context, endCode, true)})"
+ }
+ } else if (item.isAllDay) {
+ setViewVisibility(R.id.event_item_end, View.INVISIBLE)
+ }
+ setText(R.id.event_item_end, endString)
+ }
+
+ if (dimPastEvents && item.isPastEvent) {
+ curTextColor = weakTextColor
+ }
+
+ setTextColor(R.id.event_item_title, curTextColor)
+ setTextColor(R.id.event_item_description, curTextColor)
+ setTextColor(R.id.event_item_start, curTextColor)
+ setTextColor(R.id.event_item_end, curTextColor)
+
+ setTextSize(R.id.event_item_title, mediumFontSize)
+ setTextSize(R.id.event_item_description, mediumFontSize)
+ setTextSize(R.id.event_item_start, mediumFontSize)
+ setTextSize(R.id.event_item_end, mediumFontSize)
+
+ Intent().apply {
+ putExtra(EVENT_ID, item.id)
+ putExtra(EVENT_OCCURRENCE_TS, item.startTS)
+ setOnClickFillInIntent(event_item_holder, this)
+ }
+ }
+ }
+
+ private fun setupListSection(remoteView: RemoteViews, item: ListSection) {
+ var curTextColor = textColor
+ if (dimPastEvents && item.isPastSection) {
+ curTextColor = weakTextColor
+ }
+
+ remoteView.apply {
+ setTextColor(event_section_title, curTextColor)
+ setTextSize(event_section_title, mediumFontSize)
+ setText(event_section_title, item.title)
+
+ Intent().apply {
+ putExtra(DAY_CODE, item.code)
+ putExtra(VIEW_TO_OPEN, context.config.listWidgetViewToOpen)
+ setOnClickFillInIntent(event_section_title, this)
+ }
+ }
+ }
+
+ private fun getItemViewType(position: Int) = if (events.getOrNull(position) is ListEvent) ITEM_EVENT else ITEM_HEADER
+
+ override fun getLoadingView() = null
+
+ override fun getViewTypeCount() = 3
+
+ override fun onCreate() {}
+
+ override fun getItemId(position: Int) = position.toLong()
+
+ override fun onDataSetChanged() {
+ textColor = context.config.widgetTextColor
+ weakTextColor = textColor.adjustAlpha(LOW_ALPHA)
+ mediumFontSize = context.getWidgetFontSize()
+ val fromTS = DateTime().seconds() - context.config.displayPastEvents * 60
+ val toTS = DateTime().plusYears(1).seconds()
+ context.eventsHelper.getEventsSync(fromTS, toTS, applyTypeFilter = true) {
+ val listItems = ArrayList(it.size)
+ val replaceDescription = context.config.replaceDescription
+ val sorted = it.sortedWith(compareBy {
+ if (it.getIsAllDay()) {
+ Formatter.getDayStartTS(Formatter.getDayCodeFromTS(it.startTS)) - 1
+ } else {
+ it.startTS
+ }
+ }.thenBy {
+ if (it.getIsAllDay()) {
+ Formatter.getDayEndTS(Formatter.getDayCodeFromTS(it.endTS))
+ } else {
+ it.endTS
+ }
+ }.thenBy { it.title }.thenBy { if (replaceDescription) it.location else it.description })
+
+ var prevCode = ""
+ val now = getNowSeconds()
+ val today = Formatter.getDayTitle(context, Formatter.getDayCodeFromTS(now))
+
+ sorted.forEach {
+ val code = Formatter.getDayCodeFromTS(it.startTS)
+ if (code != prevCode) {
+ val day = Formatter.getDayTitle(context, code)
+ val isToday = day == today
+ val listSection = ListSection(day, code, isToday, !isToday && it.startTS < now)
+ listItems.add(listSection)
+ prevCode = code
+ }
+
+ val listEvent = ListEvent(it.id!!, it.startTS, it.endTS, it.title, it.description, it.getIsAllDay(), it.color, it.location, it.isPastEvent, it.repeatInterval > 0)
+ listItems.add(listEvent)
+ }
+
+ this@EventListWidgetAdapter.events = listItems
+ }
+ }
+
+ override fun hasStableIds() = true
+
+ override fun getCount() = events.size
+
+ override fun onDestroy() {}
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapterEmpty.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapterEmpty.kt
new file mode 100644
index 000000000..09564fdba
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapterEmpty.kt
@@ -0,0 +1,26 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.content.Context
+import android.widget.RemoteViews
+import android.widget.RemoteViewsService
+import com.simplemobiletools.calendar.pro.R
+
+class EventListWidgetAdapterEmpty(val context: Context) : RemoteViewsService.RemoteViewsFactory {
+ override fun getViewAt(position: Int) = RemoteViews(context.packageName, R.layout.event_list_section_widget)
+
+ override fun getLoadingView() = null
+
+ override fun getViewTypeCount() = 1
+
+ override fun onCreate() {}
+
+ override fun getItemId(position: Int) = 0L
+
+ override fun onDataSetChanged() {}
+
+ override fun hasStableIds() = true
+
+ override fun getCount() = 0
+
+ override fun onDestroy() {}
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/FilterEventTypeAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/FilterEventTypeAdapter.kt
new file mode 100644
index 000000000..2586b6520
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/FilterEventTypeAdapter.kt
@@ -0,0 +1,69 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.activities.SimpleActivity
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.models.EventType
+import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor
+import com.simplemobiletools.commons.extensions.setFillWithStroke
+import kotlinx.android.synthetic.main.filter_event_type_view.view.*
+import java.util.*
+
+class FilterEventTypeAdapter(val activity: SimpleActivity, val eventTypes: List, val displayEventTypes: Set) :
+ RecyclerView.Adapter() {
+ private val selectedKeys = HashSet()
+
+ init {
+ eventTypes.forEachIndexed { index, eventType ->
+ if (displayEventTypes.contains(eventType.id.toString())) {
+ selectedKeys.add(eventType.id!!)
+ }
+ }
+ }
+
+ private fun toggleItemSelection(select: Boolean, eventType: EventType, pos: Int) {
+ if (select) {
+ selectedKeys.add(eventType.id!!)
+ } else {
+ selectedKeys.remove(eventType.id)
+ }
+
+ notifyItemChanged(pos)
+ }
+
+ fun getSelectedItemsList() = selectedKeys.asSequence().map { it }.toMutableList() as ArrayList
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = activity.layoutInflater.inflate(R.layout.filter_event_type_view, parent, false)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val eventType = eventTypes[position]
+ holder.bindView(eventType)
+ }
+
+ override fun getItemCount() = eventTypes.size
+
+ inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ fun bindView(eventType: EventType): View {
+ val isSelected = selectedKeys.contains(eventType.id)
+ itemView.apply {
+ filter_event_type_checkbox.isChecked = isSelected
+ filter_event_type_checkbox.setColors(activity.config.textColor, activity.getAdjustedPrimaryColor(), activity.config.backgroundColor)
+ filter_event_type_checkbox.text = eventType.getDisplayTitle()
+ filter_event_type_color.setFillWithStroke(eventType.color, activity.config.backgroundColor)
+ filter_event_type_holder.setOnClickListener { viewClicked(!isSelected, eventType) }
+ }
+
+ return itemView
+ }
+
+ private fun viewClicked(select: Boolean, eventType: EventType) {
+ toggleItemSelection(select, eventType, adapterPosition)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/ManageEventTypesAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/ManageEventTypesAdapter.kt
new file mode 100644
index 000000000..26b18c063
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/ManageEventTypesAdapter.kt
@@ -0,0 +1,121 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.view.Menu
+import android.view.View
+import android.view.ViewGroup
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.activities.SimpleActivity
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.extensions.eventsHelper
+import com.simplemobiletools.calendar.pro.helpers.REGULAR_EVENT_TYPE_ID
+import com.simplemobiletools.calendar.pro.interfaces.DeleteEventTypesListener
+import com.simplemobiletools.calendar.pro.models.EventType
+import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
+import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.dialogs.RadioGroupDialog
+import com.simplemobiletools.commons.extensions.setFillWithStroke
+import com.simplemobiletools.commons.extensions.toast
+import com.simplemobiletools.commons.models.RadioItem
+import com.simplemobiletools.commons.views.MyRecyclerView
+import kotlinx.android.synthetic.main.item_event_type.view.*
+import java.util.*
+
+class ManageEventTypesAdapter(activity: SimpleActivity, val eventTypes: ArrayList, val listener: DeleteEventTypesListener?, recyclerView: MyRecyclerView,
+ itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) {
+
+ init {
+ setupDragListener(true)
+ }
+
+ override fun getActionMenuId() = R.menu.cab_event_type
+
+ override fun prepareActionMode(menu: Menu) {}
+
+ override fun actionItemPressed(id: Int) {
+ when (id) {
+ R.id.cab_delete -> askConfirmDelete()
+ }
+ }
+
+ override fun getSelectableItemCount() = eventTypes.size
+
+ override fun getIsItemSelectable(position: Int) = true
+
+ override fun getItemSelectionKey(position: Int) = eventTypes.getOrNull(position)?.id?.toInt()
+
+ override fun getItemKeyPosition(key: Int) = eventTypes.indexOfFirst { it.id?.toInt() == key }
+
+ override fun onActionModeCreated() {}
+
+ override fun onActionModeDestroyed() {}
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_event_type, parent)
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val eventType = eventTypes[position]
+ holder.bindView(eventType, true, true) { itemView, layoutPosition ->
+ setupView(itemView, eventType)
+ }
+ bindViewHolder(holder)
+ }
+
+ override fun getItemCount() = eventTypes.size
+
+ private fun getItemWithKey(key: Int): EventType? = eventTypes.firstOrNull { it.id?.toInt() == key }
+
+ private fun getSelectedItems() = eventTypes.filter { selectedKeys.contains(it.id?.toInt()) } as ArrayList
+
+ private fun setupView(view: View, eventType: EventType) {
+ view.apply {
+ event_item_frame.isSelected = selectedKeys.contains(eventType.id?.toInt())
+ event_type_title.text = eventType.getDisplayTitle()
+ event_type_color.setFillWithStroke(eventType.color, activity.config.backgroundColor)
+ event_type_title.setTextColor(textColor)
+ }
+ }
+
+ private fun askConfirmDelete() {
+ val eventTypes = eventTypes.filter { selectedKeys.contains(it.id?.toInt()) }.map { it.id } as ArrayList
+
+ activity.eventsHelper.doEventTypesContainEvents(eventTypes) {
+ activity.runOnUiThread {
+ if (it) {
+ val MOVE_EVENTS = 0
+ val DELETE_EVENTS = 1
+ val res = activity.resources
+ val items = ArrayList().apply {
+ add(RadioItem(MOVE_EVENTS, res.getString(R.string.move_events_into_default)))
+ add(RadioItem(DELETE_EVENTS, res.getString(R.string.remove_affected_events)))
+ }
+ RadioGroupDialog(activity, items) {
+ deleteEventTypes(it == DELETE_EVENTS)
+ }
+ } else {
+ ConfirmationDialog(activity) {
+ deleteEventTypes(true)
+ }
+ }
+ }
+ }
+ }
+
+ private fun deleteEventTypes(deleteEvents: Boolean) {
+ val eventTypesToDelete = getSelectedItems()
+
+ for (key in selectedKeys) {
+ val type = getItemWithKey(key) ?: continue
+ if (type.id == REGULAR_EVENT_TYPE_ID) {
+ activity.toast(R.string.cannot_delete_default_type)
+ eventTypesToDelete.remove(type)
+ toggleItemSelection(false, getItemKeyPosition(type.id!!.toInt()))
+ break
+ }
+ }
+
+ if (listener?.deleteEventTypes(eventTypesToDelete, deleteEvents) == true) {
+ val positions = getSelectedItemPositions()
+ eventTypes.removeAll(eventTypesToDelete)
+ removeSelectedItems(positions)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyDayPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyDayPagerAdapter.kt
similarity index 53%
rename from app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyDayPagerAdapter.kt
rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyDayPagerAdapter.kt
index ae18ebf1a..0982c5ef4 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyDayPagerAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyDayPagerAdapter.kt
@@ -1,13 +1,13 @@
-package com.simplemobiletools.calendar.adapters
+package com.simplemobiletools.calendar.pro.adapters
import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.support.v4.app.FragmentManager
-import android.support.v4.app.FragmentStatePagerAdapter
import android.util.SparseArray
-import com.simplemobiletools.calendar.fragments.DayFragment
-import com.simplemobiletools.calendar.helpers.DAY_CODE
-import com.simplemobiletools.calendar.interfaces.NavigationListener
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+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) {
@@ -28,15 +28,9 @@ class MyDayPagerAdapter(fm: FragmentManager, private val mCodes: List, p
return fragment
}
- fun checkDayEvents(pos: Int) {
+ fun updateCalendars(pos: Int) {
for (i in -1..1) {
- mFragments[pos + i]?.checkEvents()
- }
- }
-
- fun destroyMultiselector(pos: Int) {
- for (i in -1..1) {
- mFragments[pos + i]?.getDayEventsAdapter()?.finishActMode()
+ mFragments[pos + i]?.updateCalendar()
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyMonthPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyMonthPagerAdapter.kt
similarity index 64%
rename from app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyMonthPagerAdapter.kt
rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyMonthPagerAdapter.kt
index 3f9a1ab8a..744d576a2 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyMonthPagerAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyMonthPagerAdapter.kt
@@ -1,13 +1,13 @@
-package com.simplemobiletools.calendar.adapters
+package com.simplemobiletools.calendar.pro.adapters
import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.support.v4.app.FragmentManager
-import android.support.v4.app.FragmentStatePagerAdapter
import android.util.SparseArray
-import com.simplemobiletools.calendar.fragments.MonthFragment
-import com.simplemobiletools.calendar.helpers.DAY_CODE
-import com.simplemobiletools.calendar.interfaces.NavigationListener
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import com.simplemobiletools.calendar.pro.fragments.MonthFragment
+import com.simplemobiletools.calendar.pro.helpers.DAY_CODE
+import com.simplemobiletools.calendar.pro.interfaces.NavigationListener
class MyMonthPagerAdapter(fm: FragmentManager, private val mCodes: List, private val mListener: NavigationListener) : FragmentStatePagerAdapter(fm) {
private val mFragments = SparseArray()
@@ -27,7 +27,7 @@ class MyMonthPagerAdapter(fm: FragmentManager, private val mCodes: List,
return fragment
}
- fun refreshEvents(pos: Int) {
+ fun updateCalendars(pos: Int) {
for (i in -1..1) {
mFragments[pos + i]?.updateCalendar()
}
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
new file mode 100644
index 000000000..1f5379cba
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyWeekPagerAdapter.kt
@@ -0,0 +1,45 @@
+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 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()
+
+ override fun getCount() = mWeekTimestamps.size
+
+ override fun getItem(position: Int): Fragment {
+ val bundle = Bundle()
+ val weekTimestamp = mWeekTimestamps[position]
+ bundle.putLong(WEEK_START_TIMESTAMP, weekTimestamp)
+
+ 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 updateCalendars(pos: Int) {
+ for (i in -1..1) {
+ mFragments[pos + i]?.updateCalendar()
+ }
+ }
+
+ fun updateNotVisibleScaleLevel(pos: Int) {
+ mFragments[pos - 1]?.updateNotVisibleViewScaleLevel()
+ mFragments[pos + 1]?.updateNotVisibleViewScaleLevel()
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyYearPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyYearPagerAdapter.kt
similarity index 50%
rename from app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyYearPagerAdapter.kt
rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyYearPagerAdapter.kt
index 0590bc7e2..ed8528cb5 100644
--- a/app/src/main/kotlin/com/simplemobiletools/calendar/adapters/MyYearPagerAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/MyYearPagerAdapter.kt
@@ -1,15 +1,14 @@
-package com.simplemobiletools.calendar.adapters
+package com.simplemobiletools.calendar.pro.adapters
import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.support.v4.app.FragmentManager
-import android.support.v4.app.FragmentStatePagerAdapter
import android.util.SparseArray
-import com.simplemobiletools.calendar.fragments.YearFragment
-import com.simplemobiletools.calendar.helpers.YEAR_LABEL
-import com.simplemobiletools.calendar.interfaces.NavigationListener
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import com.simplemobiletools.calendar.pro.fragments.YearFragment
+import com.simplemobiletools.calendar.pro.helpers.YEAR_LABEL
-class MyYearPagerAdapter(fm: FragmentManager, val mYears: List, val mListener: NavigationListener) : FragmentStatePagerAdapter(fm) {
+class MyYearPagerAdapter(fm: FragmentManager, val mYears: List) : FragmentStatePagerAdapter(fm) {
private val mFragments = SparseArray()
override fun getCount() = mYears.size
@@ -21,15 +20,14 @@ class MyYearPagerAdapter(fm: FragmentManager, val mYears: List, val mListen
val fragment = YearFragment()
fragment.arguments = bundle
- fragment.mListener = mListener
mFragments.put(position, fragment)
return fragment
}
- fun refreshEvents(pos: Int) {
+ fun updateCalendars(pos: Int) {
for (i in -1..1) {
- mFragments[pos + i]?.updateEvents()
+ mFragments[pos + i]?.updateCalendar()
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/SelectTimeZoneAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/SelectTimeZoneAdapter.kt
new file mode 100644
index 000000000..263468053
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/SelectTimeZoneAdapter.kt
@@ -0,0 +1,51 @@
+package com.simplemobiletools.calendar.pro.adapters
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.simplemobiletools.calendar.pro.R
+import com.simplemobiletools.calendar.pro.activities.SimpleActivity
+import com.simplemobiletools.calendar.pro.extensions.config
+import com.simplemobiletools.calendar.pro.models.MyTimeZone
+import kotlinx.android.synthetic.main.item_select_time_zone.view.*
+import java.util.*
+
+class SelectTimeZoneAdapter(val activity: SimpleActivity, var timeZones: ArrayList, val itemClick: (Any) -> Unit) :
+ RecyclerView.Adapter() {
+ val textColor = activity.config.textColor
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = activity.layoutInflater.inflate(R.layout.item_select_time_zone, parent, false)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val timeZone = timeZones[position]
+ holder.bindView(timeZone)
+ }
+
+ override fun getItemCount() = timeZones.size
+
+ fun updateTimeZones(newTimeZones: ArrayList) {
+ timeZones = newTimeZones.clone() as ArrayList