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 -Logo +Logo 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. -Get it on Google Play -Get it on F-Droid +This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com -App image -App image -App image +Get it on Google Play +Get it on F-Droid -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. +
+App image +App image +App image +
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 + notifyDataSetChanged() + } + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + fun bindView(timeZone: MyTimeZone): View { + itemView.apply { + item_time_zone_title.text = timeZone.zoneName + item_time_zone_shift.text = timeZone.title + + item_time_zone_title.setTextColor(textColor) + item_time_zone_shift.setTextColor(textColor) + + item_select_time_zone_holder.setOnClickListener { + itemClick(timeZone) + } + } + + return itemView + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/databases/EventsDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/databases/EventsDatabase.kt new file mode 100644 index 000000000..7673b0d77 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/databases/EventsDatabase.kt @@ -0,0 +1,84 @@ +package com.simplemobiletools.calendar.pro.databases + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.helpers.Converters +import com.simplemobiletools.calendar.pro.helpers.REGULAR_EVENT_TYPE_ID +import com.simplemobiletools.calendar.pro.interfaces.EventTypesDao +import com.simplemobiletools.calendar.pro.interfaces.EventsDao +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.calendar.pro.models.EventType +import java.util.concurrent.Executors + +@Database(entities = [Event::class, EventType::class], version = 3) +@TypeConverters(Converters::class) +abstract class EventsDatabase : RoomDatabase() { + + abstract fun EventsDao(): EventsDao + + abstract fun EventTypesDao(): EventTypesDao + + companion object { + private var db: EventsDatabase? = null + + fun getInstance(context: Context): EventsDatabase { + if (db == null) { + synchronized(EventsDatabase::class) { + if (db == null) { + db = Room.databaseBuilder(context.applicationContext, EventsDatabase::class.java, "events.db") + .addCallback(object : Callback() { + override fun onCreate(db: SupportSQLiteDatabase) { + super.onCreate(db) + insertRegularEventType(context) + } + }) + .addMigrations(MIGRATION_1_2) + .addMigrations(MIGRATION_2_3) + .build() + db!!.openHelper.setWriteAheadLoggingEnabled(true) + } + } + } + return db!! + } + + fun destroyInstance() { + db = null + } + + private fun insertRegularEventType(context: Context) { + Executors.newSingleThreadScheduledExecutor().execute { + val regularEvent = context.resources.getString(R.string.regular_event) + val eventType = EventType(REGULAR_EVENT_TYPE_ID, regularEvent, context.config.primaryColor) + db!!.EventTypesDao().insertOrUpdate(eventType) + context.config.addDisplayEventType(REGULAR_EVENT_TYPE_ID.toString()) + } + } + + private val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.apply { + execSQL("ALTER TABLE events ADD COLUMN reminder_1_type INTEGER NOT NULL DEFAULT 0") + execSQL("ALTER TABLE events ADD COLUMN reminder_2_type INTEGER NOT NULL DEFAULT 0") + execSQL("ALTER TABLE events ADD COLUMN reminder_3_type INTEGER NOT NULL DEFAULT 0") + execSQL("ALTER TABLE events ADD COLUMN attendees TEXT NOT NULL DEFAULT ''") + } + } + } + + private val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + database.apply { + execSQL("ALTER TABLE events ADD COLUMN time_zone TEXT NOT NULL DEFAULT ''") + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/CustomEventRepeatIntervalDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/CustomEventRepeatIntervalDialog.kt similarity index 66% rename from app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/CustomEventRepeatIntervalDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/CustomEventRepeatIntervalDialog.kt index 6791c92c6..9c9cb919a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/CustomEventRepeatIntervalDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/CustomEventRepeatIntervalDialog.kt @@ -1,16 +1,16 @@ -package com.simplemobiletools.calendar.dialogs +package com.simplemobiletools.calendar.pro.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.calendar.helpers.DAY -import com.simplemobiletools.calendar.helpers.MONTH -import com.simplemobiletools.calendar.helpers.WEEK -import com.simplemobiletools.calendar.helpers.YEAR +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.helpers.DAY +import com.simplemobiletools.calendar.pro.helpers.MONTH +import com.simplemobiletools.calendar.pro.helpers.WEEK +import com.simplemobiletools.calendar.pro.helpers.YEAR import com.simplemobiletools.commons.extensions.hideKeyboard import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.showKeyboard import com.simplemobiletools.commons.extensions.value import kotlinx.android.synthetic.main.dialog_custom_event_repeat_interval.view.* @@ -22,12 +22,13 @@ class CustomEventRepeatIntervalDialog(val activity: Activity, val callback: (sec view.dialog_radio_view.check(R.id.dialog_radio_days) dialog = AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, { dialogInterface, i -> confirmRepeatInterval() }) + .setPositiveButton(R.string.ok) { dialogInterface, i -> confirmRepeatInterval() } .setNegativeButton(R.string.cancel, null) .create().apply { - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - activity.setupDialogStuff(view, this) - } + activity.setupDialogStuff(view, this) { + showKeyboard(view.dialog_custom_repeat_interval_value) + } + } } private fun confirmRepeatInterval() { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DeleteEventDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DeleteEventDialog.kt new file mode 100644 index 000000000..a9b5954cd --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DeleteEventDialog.kt @@ -0,0 +1,47 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import android.app.Activity +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.helpers.DELETE_ALL_OCCURRENCES +import com.simplemobiletools.calendar.pro.helpers.DELETE_FUTURE_OCCURRENCES +import com.simplemobiletools.calendar.pro.helpers.DELETE_SELECTED_OCCURRENCE +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, hasRepeatableEvent: Boolean, val callback: (deleteRule: Int) -> Unit) { + val dialog: AlertDialog? + + init { + 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 (!hasRepeatableEvent) { + delete_event_radio_view.check(R.id.delete_event_all) + } + + 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) } + .setNegativeButton(R.string.no, null) + .create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun dialogConfirmed(view: ViewGroup) { + val deleteRule = when (view.delete_event_radio_view.checkedRadioButtonId) { + R.id.delete_event_all -> DELETE_ALL_OCCURRENCES + R.id.delete_event_future -> DELETE_FUTURE_OCCURRENCES + else -> DELETE_SELECTED_OCCURRENCE + } + dialog?.dismiss() + callback(deleteRule) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditEventTypeDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditEventTypeDialog.kt new file mode 100644 index 000000000..418d2b330 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditEventTypeDialog.kt @@ -0,0 +1,92 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import android.app.Activity +import android.widget.ImageView +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.models.EventType +import com.simplemobiletools.commons.dialogs.ColorPickerDialog +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import kotlinx.android.synthetic.main.dialog_event_type.view.* + +class EditEventTypeDialog(val activity: Activity, var eventType: EventType? = null, val callback: (eventType: EventType) -> Unit) { + var isNewEvent = eventType == null + + init { + if (eventType == null) + eventType = EventType(null, "", 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) { wasPositivePressed, color -> + if (wasPositivePressed) { + eventType!!.color = color + 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 { + activity.setupDialogStuff(view, this, if (isNewEvent) R.string.add_new_type else R.string.edit_type) { + showKeyboard(view.type_title) + getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + ensureBackgroundThread { + eventTypeConfirmed(view.type_title.value, this) + } + } + } + } + } + + private fun setupColor(view: ImageView) { + view.setFillWithStroke(eventType!!.color, activity.config.backgroundColor) + } + + private fun eventTypeConfirmed(title: String, dialog: AlertDialog) { + val eventIdWithTitle = activity.eventsHelper.getEventTypeIdWithTitle(title) + var isEventTypeTitleTaken = isNewEvent && eventIdWithTitle != -1L + if (!isEventTypeTitleTaken) { + isEventTypeTitleTaken = !isNewEvent && eventType!!.id != eventIdWithTitle && eventIdWithTitle != -1L + } + + if (title.isEmpty()) { + activity.toast(R.string.title_empty) + return + } else if (isEventTypeTitleTaken) { + activity.toast(R.string.type_already_exists) + return + } + + eventType!!.title = title + if (eventType!!.caldavCalendarId != 0) { + eventType!!.caldavDisplayName = title + } + + eventType!!.id = activity.eventsHelper.insertOrUpdateEventTypeSync(eventType!!) + + if (eventType!!.id != -1L) { + activity.runOnUiThread { + dialog.dismiss() + callback(eventType!!) + } + } else { + activity.toast(R.string.editing_calendar_failed) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/EditRepeatingEventDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditRepeatingEventDialog.kt similarity index 67% rename from app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/EditRepeatingEventDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditRepeatingEventDialog.kt index 31a9ba25c..f9530aaea 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/EditRepeatingEventDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditRepeatingEventDialog.kt @@ -1,10 +1,10 @@ -package com.simplemobiletools.calendar.dialogs +package com.simplemobiletools.calendar.pro.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 androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.commons.extensions.hideKeyboard import com.simplemobiletools.commons.extensions.setupDialogStuff import kotlinx.android.synthetic.main.dialog_edit_repeating_event.view.* @@ -19,10 +19,10 @@ class EditRepeatingEventDialog(val activity: SimpleActivity, val callback: (allO dialog = AlertDialog.Builder(activity) .create().apply { - activity.setupDialogStuff(view, this) { - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) - } - } + activity.setupDialogStuff(view, this) { + hideKeyboard() + } + } } private fun sendResult(allOccurrences: Boolean) { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ExportEventsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ExportEventsDialog.kt new file mode 100644 index 000000000..d5e598b08 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ExportEventsDialog.kt @@ -0,0 +1,87 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.calendar.pro.adapters.FilterEventTypeAdapter +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.commons.dialogs.FilePickerDialog +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import kotlinx.android.synthetic.main.dialog_export_events.view.* +import java.io.File +import java.util.* + +class ExportEventsDialog(val activity: SimpleActivity, val path: String, val hidePath: Boolean, + val callback: (file: File, eventTypes: ArrayList) -> Unit) { + private var realPath = if (path.isEmpty()) activity.internalStoragePath else path + val config = activity.config + + init { + val view = (activity.layoutInflater.inflate(R.layout.dialog_export_events, null) as ViewGroup).apply { + export_events_folder.text = activity.humanizePath(realPath) + export_events_filename.setText("${activity.getString(R.string.events)}_${activity.getCurrentFormattedDateTime()}") + export_events_checkbox.isChecked = config.exportPastEvents + + if (hidePath) { + export_events_folder_label.beGone() + export_events_folder.beGone() + } else { + export_events_folder.setOnClickListener { + activity.hideKeyboard(export_events_filename) + FilePickerDialog(activity, realPath, false, showFAB = true) { + export_events_folder.text = activity.humanizePath(it) + realPath = it + } + } + } + + activity.eventsHelper.getEventTypes(activity, false) { + val eventTypes = HashSet() + it.mapTo(eventTypes) { it.id.toString() } + + 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(realPath, "$filename.ics") + if (!hidePath && file.exists()) { + activity.toast(R.string.name_taken) + return@setOnClickListener + } + + ensureBackgroundThread { + config.lastExportPath = file.absolutePath.getParentPath() + config.exportPastEvents = view.export_events_checkbox.isChecked + + val eventTypes = (view.export_events_types_list.adapter as FilterEventTypeAdapter).getSelectedItemsList() + callback(file, eventTypes) + dismiss() + } + } + else -> activity.toast(R.string.invalid_name) + } + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt new file mode 100644 index 000000000..63ba012fc --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/FilterEventTypesDialog.kt @@ -0,0 +1,38 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.calendar.pro.adapters.FilterEventTypeAdapter +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.commons.extensions.setupDialogStuff +import kotlinx.android.synthetic.main.dialog_filter_event_types.view.* + +class FilterEventTypesDialog(val activity: SimpleActivity, val callback: () -> Unit) { + private lateinit var dialog: AlertDialog + private val view = activity.layoutInflater.inflate(R.layout.dialog_filter_event_types, null) + + init { + activity.eventsHelper.getEventTypes(activity, false) { + val displayEventTypes = activity.config.displayEventTypes + view.filter_event_types_list.adapter = FilterEventTypeAdapter(activity, it, 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).getSelectedItemsList().map { it.toString() }.toHashSet() + if (activity.config.displayEventTypes != selectedItems) { + activity.config.displayEventTypes = selectedItems + callback() + } + dialog.dismiss() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ImportEventsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ImportEventsDialog.kt new file mode 100644 index 000000000..ce8811dc9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/ImportEventsDialog.kt @@ -0,0 +1,103 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +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.eventTypesDB +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.helpers.IcsImporter +import com.simplemobiletools.calendar.pro.helpers.IcsImporter.ImportResult.* +import com.simplemobiletools.calendar.pro.helpers.REGULAR_EVENT_TYPE_ID +import com.simplemobiletools.commons.extensions.setFillWithStroke +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.toast +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import kotlinx.android.synthetic.main.dialog_import_events.view.* + +class ImportEventsDialog(val activity: SimpleActivity, val path: String, val callback: (refreshView: Boolean) -> Unit) { + var currEventTypeId = REGULAR_EVENT_TYPE_ID + var currEventTypeCalDAVCalendarId = 0 + val config = activity.config + + init { + ensureBackgroundThread { + if (activity.eventTypesDB.getEventTypeWithId(config.lastUsedLocalEventTypeId) == null) { + config.lastUsedLocalEventTypeId = REGULAR_EVENT_TYPE_ID + } + + val isLastCaldavCalendarOK = config.caldavSync && config.getSyncedCalendarIdsAsList().contains(config.lastUsedCaldavCalendarId) + currEventTypeId = if (isLastCaldavCalendarOK) { + val lastUsedCalDAVCalendar = activity.eventsHelper.getEventTypeWithCalDAVCalendarId(config.lastUsedCaldavCalendarId) + if (lastUsedCalDAVCalendar != null) { + currEventTypeCalDAVCalendarId = config.lastUsedCaldavCalendarId + lastUsedCalDAVCalendar.id!! + } else { + REGULAR_EVENT_TYPE_ID + } + } else { + config.lastUsedLocalEventTypeId + } + + activity.runOnUiThread { + initDialog() + } + } + } + + private fun initDialog() { + val view = (activity.layoutInflater.inflate(R.layout.dialog_import_events, null) as ViewGroup).apply { + updateEventType(this) + import_event_type_holder.setOnClickListener { + SelectEventTypeDialog(activity, currEventTypeId, true, true, false, true) { + currEventTypeId = it.id!! + currEventTypeCalDAVCalendarId = it.caldavCalendarId + + config.lastUsedLocalEventTypeId = it.id!! + config.lastUsedCaldavCalendarId = it.caldavCalendarId + + 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 { + getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(null) + activity.toast(R.string.importing) + ensureBackgroundThread { + val overrideFileEventTypes = view.import_events_checkbox.isChecked + val result = IcsImporter(activity).importEvents(path, currEventTypeId, currEventTypeCalDAVCalendarId, overrideFileEventTypes) + handleParseResult(result) + dismiss() + } + } + } + } + } + + private fun updateEventType(view: ViewGroup) { + ensureBackgroundThread { + val eventType = activity.eventTypesDB.getEventTypeWithId(currEventTypeId) + activity.runOnUiThread { + view.import_event_type_title.text = eventType!!.getDisplayTitle() + view.import_event_type_color.setFillWithStroke(eventType.color, activity.config.backgroundColor) + } + } + } + + private fun handleParseResult(result: IcsImporter.ImportResult) { + activity.toast(when (result) { + IMPORT_NOTHING_NEW -> R.string.no_new_items + IMPORT_OK -> R.string.importing_successful + IMPORT_PARTIAL -> R.string.importing_some_entries_failed + else -> R.string.importing_failed + }) + callback(result != IMPORT_FAIL) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/RepeatLimitTypePickerDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/RepeatLimitTypePickerDialog.kt similarity index 67% rename from app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/RepeatLimitTypePickerDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/RepeatLimitTypePickerDialog.kt index 1057c38f6..91d73d5bc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/RepeatLimitTypePickerDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/RepeatLimitTypePickerDialog.kt @@ -1,48 +1,51 @@ -package com.simplemobiletools.calendar.dialogs +package com.simplemobiletools.calendar.pro.dialogs -import android.annotation.SuppressLint import android.app.Activity import android.app.DatePickerDialog -import android.support.v7.app.AlertDialog import android.view.View -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.extensions.seconds -import com.simplemobiletools.calendar.helpers.Formatter +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +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.getNowSeconds import com.simplemobiletools.commons.extensions.getDialogTheme -import com.simplemobiletools.commons.extensions.isLollipopPlus import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.value import kotlinx.android.synthetic.main.dialog_repeat_limit_type_picker.view.* import org.joda.time.DateTime import java.util.* -class RepeatLimitTypePickerDialog(val activity: Activity, var repeatLimit: Int, val startTS: Int, val callback: (repeatLimit: Int) -> Unit) { +class RepeatLimitTypePickerDialog(val activity: Activity, var repeatLimit: Long, val startTS: Long, val callback: (repeatLimit: Long) -> Unit) { lateinit var dialog: AlertDialog var view: View init { view = activity.layoutInflater.inflate(R.layout.dialog_repeat_limit_type_picker, null).apply { repeat_type_date.setOnClickListener { showRepetitionLimitDialog() } - repeat_type_forever.setOnClickListener { callback(0); dialog.dismiss() } repeat_type_count.setOnClickListener { dialog_radio_view.check(R.id.repeat_type_x_times) } + repeat_type_forever.setOnClickListener { + callback(0) + dialog.dismiss() + } } view.dialog_radio_view.check(getCheckedItem()) - if (repeatLimit in 1..startTS) + if (repeatLimit in 1..startTS) { repeatLimit = startTS + } updateRepeatLimitText() dialog = AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, { dialogInterface, i -> confirmRepetition() }) + .setPositiveButton(R.string.ok) { dialogInterface, i -> confirmRepetition() } .setNegativeButton(R.string.cancel, null) .create().apply { - activity.setupDialogStuff(view, this) { - activity.currentFocus?.clearFocus() - } - } + activity.setupDialogStuff(view, this) { + activity.currentFocus?.clearFocus() + } + } } private fun getCheckedItem() = when { @@ -56,7 +59,7 @@ class RepeatLimitTypePickerDialog(val activity: Activity, var repeatLimit: Int, private fun updateRepeatLimitText() { if (repeatLimit <= 0) - repeatLimit = (System.currentTimeMillis() / 1000).toInt() + repeatLimit = getNowSeconds() val repeatLimitDateTime = Formatter.getDateTimeFromTS(repeatLimit) view.repeat_type_date.text = Formatter.getFullDate(activity, repeatLimitDateTime) @@ -68,27 +71,23 @@ class RepeatLimitTypePickerDialog(val activity: Activity, var repeatLimit: Int, R.id.repeat_type_forever -> callback(0) else -> { var count = view.repeat_type_count.value - if (count.isEmpty()) - count = "0" - else - count = "-$count" - callback(count.toInt()) + count = if (count.isEmpty()) { + "0" + } else { + "-$count" + } + callback(count.toLong()) } } dialog.dismiss() } - @SuppressLint("NewApi") private fun showRepetitionLimitDialog() { - val now = (System.currentTimeMillis() / 1000).toInt() - val repeatLimitDateTime = Formatter.getDateTimeFromTS(if (repeatLimit != 0) repeatLimit else now) + val repeatLimitDateTime = Formatter.getDateTimeFromTS(if (repeatLimit != 0L) repeatLimit else getNowSeconds()) val datepicker = DatePickerDialog(activity, activity.getDialogTheme(), repetitionLimitDateSetListener, repeatLimitDateTime.year, repeatLimitDateTime.monthOfYear - 1, repeatLimitDateTime.dayOfMonth) - if (activity.isLollipopPlus()) { - datepicker.datePicker.firstDayOfWeek = if (activity.config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY - } - + datepicker.datePicker.firstDayOfWeek = if (activity.config.isSundayFirst) Calendar.SUNDAY else Calendar.MONDAY datepicker.show() } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/RepeatRuleWeeklyDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/RepeatRuleWeeklyDialog.kt similarity index 78% rename from app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/RepeatRuleWeeklyDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/RepeatRuleWeeklyDialog.kt index 18e694065..ded9e3be9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/RepeatRuleWeeklyDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/RepeatRuleWeeklyDialog.kt @@ -1,26 +1,26 @@ -package com.simplemobiletools.calendar.dialogs +package com.simplemobiletools.calendar.pro.dialogs import android.app.Activity -import android.support.v7.app.AlertDialog -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.extensions.config +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.views.MyAppCompatCheckbox import kotlinx.android.synthetic.main.dialog_vertical_linear_layout.view.* +import java.util.* class RepeatRuleWeeklyDialog(val activity: Activity, val curRepeatRule: Int, val callback: (repeatRule: Int) -> Unit) { val dialog: AlertDialog val view = activity.layoutInflater.inflate(R.layout.dialog_vertical_linear_layout, null) init { - val days = arrayOf(R.string.monday, R.string.tuesday, R.string.wednesday, R.string.thursday, R.string.friday, R.string.saturday, R.string.sunday) - val res = activity.resources + val days = activity.resources.getStringArray(R.array.week_days) val checkboxes = ArrayList(7) for (i in 0..6) { val pow = Math.pow(2.0, i.toDouble()).toInt() (activity.layoutInflater.inflate(R.layout.my_checkbox, null) as MyAppCompatCheckbox).apply { isChecked = curRepeatRule and pow != 0 - text = res.getString(days[i]) + text = days[i] id = pow checkboxes.add(this) } @@ -38,8 +38,8 @@ class RepeatRuleWeeklyDialog(val activity: Activity, val curRepeatRule: Int, val .setPositiveButton(R.string.ok, { dialog, which -> callback(getRepeatRuleSum()) }) .setNegativeButton(R.string.cancel, null) .create().apply { - activity.setupDialogStuff(view, this) - } + activity.setupDialogStuff(view, this) + } } private fun getRepeatRuleSum(): Int { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectCalendarsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectCalendarsDialog.kt similarity index 55% rename from app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectCalendarsDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectCalendarsDialog.kt index cf0fda542..b8bcebb61 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectCalendarsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectCalendarsDialog.kt @@ -1,27 +1,33 @@ -package com.simplemobiletools.calendar.dialogs +package com.simplemobiletools.calendar.pro.dialogs -import android.app.Activity -import android.support.v7.app.AlertDialog -import android.support.v7.widget.SwitchCompat import android.text.TextUtils import android.view.ViewGroup import android.widget.RelativeLayout -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.helpers.CalDAVHandler +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.SwitchCompat +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.calendar.pro.extensions.calDAVHelper +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.setupDialogStuff import kotlinx.android.synthetic.main.calendar_item_account.view.* import kotlinx.android.synthetic.main.calendar_item_calendar.view.* import kotlinx.android.synthetic.main.dialog_select_calendars.view.* -class SelectCalendarsDialog(val activity: Activity, val callback: () -> Unit) { - var prevAccount = "" - var dialog: AlertDialog - var view = (activity.layoutInflater.inflate(R.layout.dialog_select_calendars, null) as ViewGroup) +class SelectCalendarsDialog(val activity: SimpleActivity, val callback: () -> Unit) { + private var prevAccount = "" + private var dialog: AlertDialog + private var view = (activity.layoutInflater.inflate(R.layout.dialog_select_calendars, null) as ViewGroup) init { val ids = activity.config.getSyncedCalendarIdsAsList() - val calendars = CalDAVHandler(activity).getCalDAVCalendars() + val calendars = activity.calDAVHelper.getCalDAVCalendars("", true) + view.apply { + dialog_select_calendars_placeholder.beVisibleIf(calendars.isEmpty()) + dialog_select_calendars_holder.beVisibleIf(calendars.isNotEmpty()) + } + val sorted = calendars.sortedWith(compareBy({ it.accountName }, { it.displayName })) sorted.forEach { if (prevAccount != it.accountName) { @@ -29,20 +35,20 @@ class SelectCalendarsDialog(val activity: Activity, val callback: () -> Unit) { addCalendarItem(false, it.accountName) } - addCalendarItem(true, it.displayName, it.id, ids.contains(it.id.toString())) + addCalendarItem(true, it.displayName, it.id, ids.contains(it.id)) } dialog = AlertDialog.Builder(activity) - .setPositiveButton(R.string.ok, { dialogInterface, i -> confirmSelection() }) + .setPositiveButton(R.string.ok) { dialogInterface, i -> confirmSelection() } .setNegativeButton(R.string.cancel, null) .create().apply { - activity.setupDialogStuff(view, this, R.string.select_caldav_calendars) - } + activity.setupDialogStuff(view, this, R.string.select_caldav_calendars) + } } private fun addCalendarItem(isEvent: Boolean, text: String, tag: Int = 0, shouldCheck: Boolean = false) { - val calendarItem = activity.layoutInflater.inflate(if (isEvent) R.layout.calendar_item_calendar else R.layout.calendar_item_account, - view.dialog_select_calendars_holder, false) + val layout = if (isEvent) R.layout.calendar_item_calendar else R.layout.calendar_item_account + val calendarItem = activity.layoutInflater.inflate(layout, view.dialog_select_calendars_holder, false) if (isEvent) { calendarItem.calendar_item_calendar_switch.apply { @@ -61,19 +67,19 @@ class SelectCalendarsDialog(val activity: Activity, val callback: () -> Unit) { } private fun confirmSelection() { - val calendarIDs = ArrayList() + val calendarIds = ArrayList() val childCnt = view.dialog_select_calendars_holder.childCount for (i in 0..childCnt) { val child = view.dialog_select_calendars_holder.getChildAt(i) if (child is RelativeLayout) { val check = child.getChildAt(0) if (check is SwitchCompat && check.isChecked) { - calendarIDs.add(check.tag as Int) + calendarIds.add(check.tag as Int) } } } - activity.config.caldavSyncedCalendarIDs = TextUtils.join(",", calendarIDs) + activity.config.caldavSyncedCalendarIds = TextUtils.join(",", calendarIds) callback() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventCalendarDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventCalendarDialog.kt similarity index 66% rename from app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventCalendarDialog.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventCalendarDialog.kt index 3a6ecc790..f01100d6d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/dialogs/SelectEventCalendarDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventCalendarDialog.kt @@ -1,19 +1,20 @@ -package com.simplemobiletools.calendar.dialogs +package com.simplemobiletools.calendar.pro.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.helpers.STORED_LOCALLY_ONLY -import com.simplemobiletools.calendar.models.CalDAVCalendar -import com.simplemobiletools.commons.extensions.setBackgroundWithStroke +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.helpers.STORED_LOCALLY_ONLY +import com.simplemobiletools.calendar.pro.models.CalDAVCalendar +import com.simplemobiletools.commons.extensions.setFillWithStroke import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.updateTextColors +import com.simplemobiletools.commons.helpers.ensureBackgroundThread import kotlinx.android.synthetic.main.dialog_select_radio_group.view.* import kotlinx.android.synthetic.main.radio_button_with_color.view.* @@ -26,7 +27,14 @@ class SelectEventCalendarDialog(val activity: Activity, val calendars: List Unit) { + private val dialog: AlertDialog? + private val radioGroup: RadioGroup + private var wasInit = false + private val colors = activity.calDAVHelper.getAvailableCalDAVCalendarColors(eventType) + + init { + val view = activity.layoutInflater.inflate(R.layout.dialog_select_event_type_color, null) as ViewGroup + radioGroup = view.dialog_select_event_type_color_radio + view.dialog_select_event_type_other_value.setOnClickListener { + showCustomColorPicker() + } + + colors.forEachIndexed { index, value -> + addRadioButton(index, value) + } + + wasInit = true + dialog = AlertDialog.Builder(activity) + .create().apply { + activity.setupDialogStuff(view, this) + + if (colors.isEmpty()) { + showCustomColorPicker() + } + } + } + + 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 = if (color == 0) activity.getString(R.string.transparent) else String.format("#%06X", 0xFFFFFF and color) + isChecked = color == eventType.color + id = colorKey + } + + view.dialog_radio_color.setFillWithStroke(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() + } + + private fun showCustomColorPicker() { + ColorPickerDialog(activity, activity.config.primaryColor) { wasPositivePressed, color -> + if (wasPositivePressed) { + callback(color) + } + dialog?.dismiss() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypeDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypeDialog.kt new file mode 100644 index 000000000..3af5d5462 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SelectEventTypeDialog.kt @@ -0,0 +1,92 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import android.app.Activity +import android.graphics.Color +import android.view.ViewGroup +import android.widget.RadioGroup +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.models.EventType +import com.simplemobiletools.commons.extensions.hideKeyboard +import com.simplemobiletools.commons.extensions.setFillWithStroke +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.updateTextColors +import com.simplemobiletools.commons.views.MyCompatRadioButton +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: Long, val showCalDAVCalendars: Boolean, val showNewEventTypeOption: Boolean, + val addLastUsedOneAsFirstOption: Boolean, val showOnlyWritable: Boolean, val callback: (eventType: EventType) -> Unit) { + private val NEW_EVENT_TYPE_ID = -2L + private val LAST_USED_EVENT_TYPE_ID = -1L + + 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.eventsHelper.getEventTypes(activity, showOnlyWritable) { + eventTypes = it + activity.runOnUiThread { + if (addLastUsedOneAsFirstOption) { + val lastUsedEventType = EventType(LAST_USED_EVENT_TYPE_ID, activity.getString(R.string.last_used_one), Color.TRANSPARENT, 0) + addRadioButton(lastUsedEventType) + } + eventTypes.filter { showCalDAVCalendars || it.caldavCalendarId == 0 }.forEach { + addRadioButton(it) + } + if (showNewEventTypeOption) { + val newEventType = EventType(NEW_EVENT_TYPE_ID, activity.getString(R.string.add_new_type), Color.TRANSPARENT, 0) + addRadioButton(newEventType) + } + wasInit = true + activity.updateTextColors(view.dialog_radio_holder) + } + } + + dialog = AlertDialog.Builder(activity) + .create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun addRadioButton(eventType: EventType) { + val view = activity.layoutInflater.inflate(R.layout.radio_button_with_color, null) + (view.dialog_radio_button as MyCompatRadioButton).apply { + text = eventType.getDisplayTitle() + isChecked = eventType.id == currEventType + id = eventType.id!!.toInt() + } + + if (eventType.color != Color.TRANSPARENT) { + view.dialog_radio_color.setFillWithStroke(eventType.color, activity.config.backgroundColor) + } + + view.setOnClickListener { viewClicked(eventType) } + radioGroup.addView(view, RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) + } + + private fun viewClicked(eventType: EventType) { + if (!wasInit) { + return + } + + if (eventType.id == NEW_EVENT_TYPE_ID) { + EditEventTypeDialog(activity) { + callback(it) + activity.hideKeyboard() + dialog?.dismiss() + } + } else { + callback(eventType) + dialog?.dismiss() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SetRemindersDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SetRemindersDialog.kt new file mode 100644 index 000000000..14a87b9ea --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SetRemindersDialog.kt @@ -0,0 +1,69 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.helpers.REMINDER_OFF +import com.simplemobiletools.commons.extensions.* +import kotlinx.android.synthetic.main.dialog_set_reminders.view.* + +class SetRemindersDialog(val activity: Activity, val callback: (reminders: ArrayList) -> Unit) { + private var mReminder1Minutes = -1 + private var mReminder2Minutes = -1 + private var mReminder3Minutes = -1 + + init { + val view = activity.layoutInflater.inflate(R.layout.dialog_set_reminders, null).apply { + set_reminders_image.applyColorFilter(context.config.textColor) + set_reminders_1.text = activity.getFormattedMinutes(mReminder1Minutes) + set_reminders_2.text = activity.getFormattedMinutes(mReminder1Minutes) + set_reminders_3.text = activity.getFormattedMinutes(mReminder1Minutes) + + set_reminders_1.setOnClickListener { + activity.showPickSecondsDialogHelper(mReminder1Minutes) { + mReminder1Minutes = if (it <= 0) it else it / 60 + set_reminders_1.text = activity.getFormattedMinutes(mReminder1Minutes) + if (mReminder1Minutes != -1) { + set_reminders_2.beVisible() + } + } + } + + set_reminders_2.setOnClickListener { + activity.showPickSecondsDialogHelper(mReminder2Minutes) { + mReminder2Minutes = if (it <= 0) it else it / 60 + set_reminders_2.text = activity.getFormattedMinutes(mReminder2Minutes) + if (mReminder2Minutes != -1) { + set_reminders_3.beVisible() + } + } + } + + set_reminders_3.setOnClickListener { + activity.showPickSecondsDialogHelper(mReminder3Minutes) { + mReminder3Minutes = if (it <= 0) it else it / 60 + set_reminders_3.text = activity.getFormattedMinutes(mReminder3Minutes) + } + } + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this, R.string.event_reminders) + } + } + + private fun dialogConfirmed() { + val tempReminders = arrayListOf(mReminder1Minutes, mReminder2Minutes, mReminder3Minutes).filter { it != REMINDER_OFF }.sorted() + val reminders = arrayListOf( + tempReminders.getOrNull(0) ?: REMINDER_OFF, + tempReminders.getOrNull(1) ?: REMINDER_OFF, + tempReminders.getOrNull(2) ?: REMINDER_OFF + ) + + callback(reminders) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Activity.kt new file mode 100644 index 000000000..16192b5ce --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Activity.kt @@ -0,0 +1,83 @@ +package com.simplemobiletools.calendar.pro.extensions + +import android.app.Activity +import com.simplemobiletools.calendar.pro.BuildConfig +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.dialogs.CustomEventRepeatIntervalDialog +import com.simplemobiletools.calendar.pro.helpers.* +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.dialogs.RadioGroupDialog +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.models.RadioItem +import java.io.File +import java.util.* +import kotlin.collections.ArrayList + +fun BaseSimpleActivity.shareEvents(ids: List) { + ensureBackgroundThread { + val file = getTempFile() + if (file == null) { + toast(R.string.unknown_error_occurred) + return@ensureBackgroundThread + } + + val events = eventsDB.getEventsWithIds(ids) as ArrayList + getFileOutputStream(file.toFileDirItem(this), true) { + IcsExporter().exportEvents(this, it, events, false) { + if (it == IcsExporter.ExportResult.EXPORT_OK) { + sharePathIntent(file.absolutePath, 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") +} + +fun Activity.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/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt new file mode 100644 index 000000000..2b49865db --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -0,0 +1,544 @@ +package com.simplemobiletools.calendar.pro.extensions + +import android.accounts.Account +import android.annotation.SuppressLint +import android.app.* +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.media.AudioAttributes +import android.net.Uri +import android.os.Bundle +import android.provider.CalendarContract +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.app.AlarmManagerCompat +import androidx.core.app.NotificationCompat +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.EventActivity +import com.simplemobiletools.calendar.pro.activities.SnoozeReminderActivity +import com.simplemobiletools.calendar.pro.databases.EventsDatabase +import com.simplemobiletools.calendar.pro.helpers.* +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.interfaces.EventTypesDao +import com.simplemobiletools.calendar.pro.interfaces.EventsDao +import com.simplemobiletools.calendar.pro.models.* +import com.simplemobiletools.calendar.pro.receivers.CalDAVSyncReceiver +import com.simplemobiletools.calendar.pro.receivers.NotificationReceiver +import com.simplemobiletools.calendar.pro.services.SnoozeService +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.* +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import org.joda.time.LocalDate +import java.util.* + +val Context.config: Config get() = Config.newInstance(applicationContext) +val Context.eventsDB: EventsDao get() = EventsDatabase.getInstance(applicationContext).EventsDao() +val Context.eventTypesDB: EventTypesDao get() = EventsDatabase.getInstance(applicationContext).EventTypesDao() +val Context.eventsHelper: EventsHelper get() = EventsHelper(this) +val Context.calDAVHelper: CalDAVHelper get() = CalDAVHelper(this) + +fun Context.updateWidgets() { + val widgetIDs = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(ComponentName(applicationContext, MyWidgetMonthlyProvider::class.java)) + if (widgetIDs.isNotEmpty()) { + Intent(applicationContext, MyWidgetMonthlyProvider::class.java).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIDs) + sendBroadcast(this) + } + } + + updateListWidget() + updateDateWidget() +} + +fun Context.updateListWidget() { + val widgetIDs = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(ComponentName(applicationContext, MyWidgetListProvider::class.java)) + if (widgetIDs.isNotEmpty()) { + Intent(applicationContext, MyWidgetListProvider::class.java).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIDs) + sendBroadcast(this) + } + } +} + +fun Context.updateDateWidget() { + val widgetIDs = AppWidgetManager.getInstance(applicationContext).getAppWidgetIds(ComponentName(applicationContext, MyWidgetDateProvider::class.java)) + if (widgetIDs.isNotEmpty()) { + Intent(applicationContext, MyWidgetDateProvider::class.java).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIDs) + sendBroadcast(this) + } + } +} + +fun Context.scheduleAllEvents() { + val events = eventsDB.getEventsAtReboot(getNowSeconds()) + events.forEach { + scheduleNextEventReminder(it, false) + } +} + +fun Context.scheduleNextEventReminder(event: Event, showToasts: Boolean) { + val validReminders = event.getReminders().filter { it.type == REMINDER_NOTIFICATION } + if (validReminders.isEmpty()) { + if (showToasts) { + toast(R.string.saving) + } + return + } + + val now = getNowSeconds() + val reminderSeconds = validReminders.reversed().map { it.minutes * 60 } + eventsHelper.getEvents(now, now + YEAR, event.id!!, false) { + if (it.isNotEmpty()) { + for (curEvent in it) { + for (curReminder in reminderSeconds) { + if (curEvent.getEventStartTS() - curReminder > now) { + scheduleEventIn((curEvent.getEventStartTS() - curReminder) * 1000L, curEvent, showToasts) + return@getEvents + } + } + } + } + + if (showToasts) { + toast(R.string.saving) + } + } +} + +fun Context.scheduleEventIn(notifTS: Long, event: Event, showToasts: Boolean) { + if (notifTS < System.currentTimeMillis()) { + if (showToasts) { + toast(R.string.saving) + } + return + } + + val newNotifTS = notifTS + 1000 + if (showToasts) { + val secondsTillNotification = (newNotifTS - System.currentTimeMillis()) / 1000 + val msg = String.format(getString(R.string.reminder_triggers_in), formatSecondsToTimeString(secondsTillNotification.toInt())) + toast(msg) + } + + val pendingIntent = getNotificationIntent(applicationContext, event) + val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager + try { + AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, newNotifTS, pendingIntent) + } catch (e: Exception) { + showErrorToast(e) + } +} + +fun Context.cancelNotification(id: Long) { + (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(id.toInt()) +} + +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!!.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) +} + +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.notifyRunningEvents() { + eventsHelper.getRunningEvents().filter { it.getReminders().any { it.type == REMINDER_NOTIFICATION } }.forEach { + notifyEvent(it) + } +} + +fun Context.notifyEvent(originalEvent: Event) { + var event = originalEvent.copy() + val currentSeconds = getNowSeconds() + + var eventStartTS = if (event.getIsAllDay()) Formatter.getDayStartTS(Formatter.getDayCodeFromTS(event.startTS)) else event.startTS + // make sure refer to the proper repeatable event instance with "Tomorrow", or the specific date + if (event.repeatInterval != 0 && eventStartTS - event.reminder1Minutes * 60 < currentSeconds) { + val events = eventsHelper.getRepeatableEventsFor(currentSeconds - WEEK_SECONDS, currentSeconds + YEAR_SECONDS, event.id!!) + for (currEvent in events) { + eventStartTS = if (currEvent.getIsAllDay()) Formatter.getDayStartTS(Formatter.getDayCodeFromTS(currEvent.startTS)) else currEvent.startTS + if (eventStartTS - currEvent.reminder1Minutes * 60 > currentSeconds) { + break + } + + event = currEvent + } + } + + val pendingIntent = getPendingIntent(applicationContext, event) + val startTime = Formatter.getTimeFromTS(applicationContext, event.startTS) + val endTime = Formatter.getTimeFromTS(applicationContext, event.endTS) + val startDate = Formatter.getDateFromTS(event.startTS) + + val displayedStartDate = when (startDate) { + LocalDate.now() -> "" + LocalDate.now().plusDays(1) -> getString(R.string.tomorrow) + else -> "${Formatter.getDateFromCode(this, Formatter.getDayCodeFromTS(event.startTS))}," + } + + 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 content = "$displayedStartDate $timeRange $descriptionOrLocation".trim() + ensureBackgroundThread { + val notification = getNotification(pendingIntent, event, content) + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + try { + if (notification != null) { + notificationManager.notify(event.id!!.toInt(), notification) + } + } catch (e: Exception) { + showErrorToast(e) + } + } +} + +@SuppressLint("NewApi") +fun Context.getNotification(pendingIntent: PendingIntent, event: Event, content: String, publicVersion: Boolean = false): Notification? { + var soundUri = config.reminderSoundUri + if (soundUri == SILENT) { + soundUri = "" + } else { + grantReadUriPermission(soundUri) + } + + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + // create a new channel for every new sound uri as the new Android Oreo notification system is fundamentally broken + if (soundUri != config.lastSoundUri || config.lastVibrateOnReminder != config.vibrateOnReminder) { + if (!publicVersion) { + if (isOreoPlus()) { + val oldChannelId = "simple_calendar_${config.lastReminderChannel}_${config.reminderAudioStream}_${event.eventType}" + notificationManager.deleteNotificationChannel(oldChannelId) + } + } + + config.lastVibrateOnReminder = config.vibrateOnReminder + config.lastReminderChannel = System.currentTimeMillis() + config.lastSoundUri = soundUri + } + + val channelId = "simple_calendar_${config.lastReminderChannel}_${config.reminderAudioStream}_${event.eventType}" + if (isOreoPlus()) { + val audioAttributes = AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setLegacyStreamType(config.reminderAudioStream) + .build() + + val name = eventTypesDB.getEventTypeWithId(event.eventType)?.getDisplayTitle() + val importance = NotificationManager.IMPORTANCE_HIGH + NotificationChannel(channelId, name, importance).apply { + setBypassDnd(true) + enableLights(true) + lightColor = event.color + enableVibration(config.vibrateOnReminder) + setSound(Uri.parse(soundUri), audioAttributes) + try { + notificationManager.createNotificationChannel(this) + } catch (e: Exception) { + showErrorToast(e) + return null + } + } + } + + val contentTitle = if (publicVersion) resources.getString(R.string.app_name) else event.title + val contentText = if (publicVersion) resources.getString(R.string.public_event_notification_text) else content + + val builder = NotificationCompat.Builder(this, channelId) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setSmallIcon(R.drawable.ic_calendar_vector) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setDefaults(Notification.DEFAULT_LIGHTS) + .setCategory(Notification.CATEGORY_EVENT) + .setAutoCancel(true) + .setSound(Uri.parse(soundUri), config.reminderAudioStream) + .setChannelId(channelId) + .addAction(R.drawable.ic_snooze_vector, getString(R.string.snooze), getSnoozePendingIntent(this, event)) + + if (config.vibrateOnReminder) { + val vibrateArray = LongArray(2) { 500 } + builder.setVibrate(vibrateArray) + } + + if (!publicVersion) { + val notification = getNotification(pendingIntent, event, content, true) + if (notification != null) { + builder.setPublicVersion(notification) + } + } + + val notification = builder.build() + if (config.loopReminders) { + notification.flags = notification.flags or Notification.FLAG_INSISTENT + } + return notification +} + +private fun getFormattedEventTime(startTime: String, endTime: String) = if (startTime == endTime) startTime else "$startTime \u2013 $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!!.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) +} + +private fun getSnoozePendingIntent(context: Context, event: Event): PendingIntent { + val snoozeClass = if (context.config.useSameSnooze) SnoozeService::class.java else SnoozeReminderActivity::class.java + val intent = Intent(context, snoozeClass).setAction("Snooze") + intent.putExtra(EVENT_ID, event.id) + return if (context.config.useSameSnooze) { + PendingIntent.getService(context, event.id!!.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) + } else { + PendingIntent.getActivity(context, event.id!!.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) + } +} + +fun Context.rescheduleReminder(event: Event?, minutes: Int) { + if (event != null) { + applicationContext.scheduleEventIn(System.currentTimeMillis() + minutes * 60000, event, false) + cancelNotification(event.id!!) + } +} + +fun Context.launchNewEventIntent(dayCode: String = Formatter.getTodayCode()) { + Intent(applicationContext, EventActivity::class.java).apply { + putExtra(NEW_EVENT_START_TS, getNewEventTimestampFromCode(dayCode)) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(this) + } +} + +fun Context.getNewEventTimestampFromCode(dayCode: String): Long { + val defaultStartTime = config.defaultStartTime + val currHour = DateTime(System.currentTimeMillis(), DateTimeZone.getDefault()).hourOfDay + var dateTime = Formatter.getLocalDateTimeFromCode(dayCode).withHourOfDay(currHour) + var newDateTime = dateTime.plusHours(1).withMinuteOfHour(0).withSecondOfMinute(0).withMillisOfSecond(0) + + if (defaultStartTime != -1) { + val hours = defaultStartTime / 60 + val minutes = defaultStartTime % 60 + dateTime = Formatter.getLocalDateTimeFromCode(dayCode).withHourOfDay(hours).withMinuteOfHour(minutes) + newDateTime = dateTime + } + + // make sure the date doesn't change + return newDateTime.withDate(dateTime.year, dateTime.monthOfYear, dateTime.dayOfMonth).seconds() +} + +fun Context.getSyncedCalDAVCalendars() = calDAVHelper.getCalDAVCalendars(config.caldavSyncedCalendarIds, false) + +fun Context.recheckCalDAVCalendars(callback: () -> Unit) { + if (config.caldavSync) { + ensureBackgroundThread { + calDAVHelper.refreshCalendars(false, callback) + updateWidgets() + } + } +} + +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 = 2 * AlarmManager.INTERVAL_HOUR + try { + alarm.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + syncCheckInterval, syncCheckInterval, pendingIntent) + } catch (ignored: Exception) { + } + } 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 = getAdjustedPrimaryColor() + setTextColor(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, primaryColor: Int) = + textView.addResizedBackgroundDrawable(res, dayLabelHeight, primaryColor, R.drawable.ic_circle_filled) + +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 { + 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 }).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 + contentDescription = it.title + linearLayout.addView(this) + } + } +} + +fun Context.getEventListItems(events: List): ArrayList { + val listItems = ArrayList(events.size) + val replaceDescription = config.replaceDescription + + // move all-day events in front of others + val sorted = events.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(this, Formatter.getDayCodeFromTS(now)) + + sorted.forEach { + val code = Formatter.getDayCodeFromTS(it.startTS) + if (code != prevCode) { + val day = Formatter.getDayTitle(this, 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) + } + return listItems +} + +fun Context.handleEventDeleting(eventIds: List, timestamps: List, action: Int) { + when (action) { + DELETE_SELECTED_OCCURRENCE -> { + eventIds.forEachIndexed { index, value -> + eventsHelper.addEventRepetitionException(value, timestamps[index], true) + } + } + DELETE_FUTURE_OCCURRENCES -> { + eventIds.forEachIndexed { index, value -> + eventsHelper.addEventRepeatLimit(value, timestamps[index]) + } + } + DELETE_ALL_OCCURRENCES -> { + eventsHelper.deleteEvents(eventIds.toMutableList(), true) + } + } +} + +fun Context.refreshCalDAVCalendars(ids: String, showToasts: Boolean) { + val uri = CalendarContract.Calendars.CONTENT_URI + val accounts = HashSet() + val calendars = calDAVHelper.getCalDAVCalendars(ids, showToasts) + calendars.forEach { + accounts.add(Account(it.accountName, it.accountType)) + } + + Bundle().apply { + putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) + putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) + accounts.forEach { + ContentResolver.requestSync(it, uri.authority, this) + } + } +} + +fun Context.getWidgetFontSize() = when (config.fontSize) { + FONT_SIZE_SMALL -> getWidgetSmallFontSize() + FONT_SIZE_MEDIUM -> getWidgetMediumFontSize() + FONT_SIZE_LARGE -> getWidgetLargeFontSize() + else -> getWidgetExtraLargeFontSize() +} + +fun Context.getWidgetSmallFontSize() = getWidgetMediumFontSize() - 3f +fun Context.getWidgetMediumFontSize() = resources.getDimension(R.dimen.day_text_size) / resources.displayMetrics.density +fun Context.getWidgetLargeFontSize() = getWidgetMediumFontSize() + 3f +fun Context.getWidgetExtraLargeFontSize() = getWidgetMediumFontSize() + 6f + +fun Context.getWeeklyViewItemHeight(): Float { + val defaultHeight = resources.getDimension(R.dimen.weekly_view_row_height) + val multiplier = config.weeklyViewItemHeightMultiplier + return defaultHeight * multiplier +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/DateTime.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/DateTime.kt new file mode 100644 index 000000000..c538b3a30 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/DateTime.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.calendar.pro.extensions + +import org.joda.time.DateTime + +fun DateTime.seconds() = millis / 1000L diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Int.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Int.kt new file mode 100644 index 000000000..16bf1faf4 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Int.kt @@ -0,0 +1,11 @@ +package com.simplemobiletools.calendar.pro.extensions + +import com.simplemobiletools.calendar.pro.helpers.MONTH +import com.simplemobiletools.calendar.pro.helpers.WEEK +import com.simplemobiletools.calendar.pro.helpers.YEAR + +fun Int.isXWeeklyRepetition() = this != 0 && this % WEEK == 0 + +fun Int.isXMonthlyRepetition() = this != 0 && this % MONTH == 0 + +fun Int.isXYearlyRepetition() = this != 0 && this % YEAR == 0 diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Long.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Long.kt new file mode 100644 index 000000000..f49a24281 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Long.kt @@ -0,0 +1,10 @@ +package com.simplemobiletools.calendar.pro.extensions + +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.models.Event + +fun Long.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 +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Range.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Range.kt new file mode 100644 index 000000000..8638fd446 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Range.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.calendar.pro.extensions + +import android.util.Range + +fun Range.touch(other: Range) = (upper > other.lower && lower < other.upper) || (other.upper > lower && other.lower < upper) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/String.kt new file mode 100644 index 000000000..71cd1be33 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/String.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.calendar.pro.extensions + +fun String.getMonthCode() = if (length == 8) substring(0, 6) else "" diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/TextView.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/TextView.kt similarity index 92% rename from app/src/main/kotlin/com/simplemobiletools/calendar/extensions/TextView.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/TextView.kt index 41f9a9a52..a0adf4c8e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/extensions/TextView.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/TextView.kt @@ -1,4 +1,4 @@ -package com.simplemobiletools.calendar.extensions +package com.simplemobiletools.calendar.pro.extensions import android.content.res.Resources import android.graphics.Bitmap diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragment.kt new file mode 100644 index 000000000..78493ebc9 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragment.kt @@ -0,0 +1,131 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout +import androidx.fragment.app.Fragment +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.EventActivity +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.calendar.pro.adapters.DayEventsAdapter +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.helpers.DAY_CODE +import com.simplemobiletools.calendar.pro.helpers.EVENT_ID +import com.simplemobiletools.calendar.pro.helpers.EVENT_OCCURRENCE_TS +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.interfaces.NavigationListener +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.commons.extensions.applyColorFilter +import kotlinx.android.synthetic.main.fragment_day.view.* +import kotlinx.android.synthetic.main.top_navigation.view.* +import java.util.* + +class DayFragment : Fragment() { + var mListener: NavigationListener? = null + private var mTextColor = 0 + private var mDayCode = "" + private var lastHash = 0 + + private lateinit var mHolder: RelativeLayout + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_day, container, false) + mHolder = view.day_holder + + mDayCode = arguments!!.getString(DAY_CODE)!! + setupButtons() + return view + } + + override fun onResume() { + super.onResume() + updateCalendar() + } + + private fun setupButtons() { + mTextColor = context!!.config.textColor + + mHolder.top_left_arrow.apply { + applyColorFilter(mTextColor) + background = null + setOnClickListener { + mListener?.goLeft() + } + + val pointerLeft = context!!.getDrawable(R.drawable.ic_chevron_left_vector) + pointerLeft?.isAutoMirrored = true + setImageDrawable(pointerLeft) + } + + mHolder.top_right_arrow.apply { + applyColorFilter(mTextColor) + background = null + setOnClickListener { + mListener?.goRight() + } + + val pointerRight = context!!.getDrawable(R.drawable.ic_chevron_right_vector) + pointerRight?.isAutoMirrored = true + setImageDrawable(pointerRight) + } + + val day = Formatter.getDayTitle(context!!, mDayCode) + mHolder.top_value.apply { + text = day + contentDescription = text + setOnClickListener { + (activity as MainActivity).showGoToDateDialog() + } + setTextColor(context.config.textColor) + } + } + + fun updateCalendar() { + val startTS = Formatter.getDayStartTS(mDayCode) + val endTS = Formatter.getDayEndTS(mDayCode) + context?.eventsHelper?.getEvents(startTS, endTS) { + receivedEvents(it) + } + } + + private fun receivedEvents(events: List) { + val newHash = events.hashCode() + if (newHash == lastHash || !isAdded) { + return + } + lastHash = newHash + + val replaceDescription = context!!.config.replaceDescription + val sorted = ArrayList(events.sortedWith(compareBy({ !it.getIsAllDay() }, { 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, mHolder.day_events) { + editEvent(it as Event) + }.apply { + 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) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt new file mode 100644 index 000000000..17c9d508a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt @@ -0,0 +1,141 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.DatePicker +import androidx.appcompat.app.AlertDialog +import androidx.viewpager.widget.ViewPager +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.adapters.MyDayPagerAdapter +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.helpers.DAY_CODE +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.interfaces.NavigationListener +import com.simplemobiletools.commons.extensions.getDialogTheme +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.updateActionBarTitle +import com.simplemobiletools.commons.views.MyViewPager +import kotlinx.android.synthetic.main.fragment_days_holder.view.* +import org.joda.time.DateTime +import java.util.* + +class DayFragmentsHolder : MyFragmentHolder(), NavigationListener { + private val PREFILLED_DAYS = 251 + + private var viewPager: MyViewPager? = null + private var defaultDailyPage = 0 + private var todayDayCode = "" + private var currentDayCode = "" + private var isGoToTodayVisible = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + currentDayCode = arguments?.getString(DAY_CODE) ?: "" + todayDayCode = Formatter.getTodayCode() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_days_holder, container, false) + view.background = ColorDrawable(context!!.config.backgroundColor) + viewPager = view.fragment_days_viewpager + viewPager!!.id = (System.currentTimeMillis() % 100000).toInt() + setupFragment() + return view + } + + private fun setupFragment() { + val codes = getDays(currentDayCode) + val dailyAdapter = MyDayPagerAdapter(activity!!.supportFragmentManager, codes, this) + defaultDailyPage = codes.size / 2 + + + viewPager!!.apply { + adapter = dailyAdapter + addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + } + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + } + + override fun onPageSelected(position: Int) { + currentDayCode = codes[position] + val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() + if (isGoToTodayVisible != shouldGoToTodayBeVisible) { + (activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible) + isGoToTodayVisible = shouldGoToTodayBeVisible + } + } + }) + currentItem = defaultDailyPage + } + updateActionBarTitle() + } + + private fun getDays(code: String): List { + val days = ArrayList(PREFILLED_DAYS) + val today = Formatter.getDateTimeFromCode(code) + for (i in -PREFILLED_DAYS / 2..PREFILLED_DAYS / 2) { + days.add(Formatter.getDayCodeFromDateTime(today.plusDays(i))) + } + return days + } + + override fun goLeft() { + viewPager!!.currentItem = viewPager!!.currentItem - 1 + } + + override fun goRight() { + viewPager!!.currentItem = viewPager!!.currentItem + 1 + } + + override fun goToDateTime(dateTime: DateTime) { + currentDayCode = Formatter.getDayCodeFromDateTime(dateTime) + setupFragment() + } + + override fun goToToday() { + currentDayCode = todayDayCode + setupFragment() + } + + override fun showGoToDateDialog() { + 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(currentDayCode) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> dateSelected(dateTime, datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun dateSelected(dateTime: DateTime, datePicker: DatePicker) { + val month = datePicker.month + 1 + val year = datePicker.year + val day = datePicker.dayOfMonth + val newDateTime = dateTime.withDate(year, month, day) + goToDateTime(newDateTime) + } + + override fun refreshEvents() { + (viewPager?.adapter as? MyDayPagerAdapter)?.updateCalendars(viewPager?.currentItem ?: 0) + } + + override fun shouldGoToTodayBeVisible() = currentDayCode != todayDayCode + + override fun updateActionBarTitle() { + (activity as MainActivity).updateActionBarTitle(getString(R.string.app_launcher_name)) + } + + override fun getNewEventDayCode() = currentDayCode +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/EventListFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/EventListFragment.kt new file mode 100644 index 000000000..da553b923 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/EventListFragment.kt @@ -0,0 +1,213 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.content.Intent +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.EventActivity +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.calendar.pro.adapters.EventListAdapter +import com.simplemobiletools.calendar.pro.extensions.* +import com.simplemobiletools.calendar.pro.helpers.EVENT_ID +import com.simplemobiletools.calendar.pro.helpers.EVENT_OCCURRENCE_TS +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.* +import com.simplemobiletools.commons.helpers.MONTH_SECONDS +import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener +import com.simplemobiletools.commons.views.MyLinearLayoutManager +import com.simplemobiletools.commons.views.MyRecyclerView +import kotlinx.android.synthetic.main.fragment_event_list.view.* +import org.joda.time.DateTime +import java.util.* + +class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener { + private val NOT_UPDATING = 0 + private val UPDATE_TOP = 1 + private val UPDATE_BOTTOM = 2 + + private var FETCH_INTERVAL = 3 * MONTH_SECONDS + private var MIN_EVENTS_TRESHOLD = 30 + + private var mEvents = ArrayList() + private var minFetchedTS = 0L + private var maxFetchedTS = 0L + private var wereInitialEventsAdded = false + private var bottomItemAtRefresh: ListItem? = null + + private var use24HourFormat = false + + lateinit var mView: View + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + mView = inflater.inflate(R.layout.fragment_event_list, container, false) + mView.background = ColorDrawable(context!!.config.backgroundColor) + mView.calendar_events_list_holder?.id = (System.currentTimeMillis() % 100000).toInt() + mView.calendar_empty_list_placeholder_2.apply { + setTextColor(context.getAdjustedPrimaryColor()) + underlineText() + setOnClickListener { + context.launchNewEventIntent(getNewEventDayCode()) + } + } + + use24HourFormat = context!!.config.use24HourFormat + updateActionBarTitle() + return mView + } + + override fun onResume() { + super.onResume() + checkEvents() + val use24Hour = context!!.config.use24HourFormat + if (use24Hour != use24HourFormat) { + use24HourFormat = use24Hour + (mView.calendar_events_list.adapter as? EventListAdapter)?.toggle24HourFormat(use24HourFormat) + } + } + + override fun onPause() { + super.onPause() + use24HourFormat = context!!.config.use24HourFormat + } + + private fun checkEvents() { + if (!wereInitialEventsAdded) { + minFetchedTS = DateTime().minusMinutes(context!!.config.displayPastEvents).seconds() + maxFetchedTS = DateTime().plusMonths(6).seconds() + } + + context!!.eventsHelper.getEvents(minFetchedTS, maxFetchedTS) { + if (it.size >= MIN_EVENTS_TRESHOLD) { + receivedEvents(it, NOT_UPDATING) + } else { + if (!wereInitialEventsAdded) { + maxFetchedTS += FETCH_INTERVAL + } + context!!.eventsHelper.getEvents(minFetchedTS, maxFetchedTS) { + mEvents = it + receivedEvents(mEvents, NOT_UPDATING, !wereInitialEventsAdded) + } + } + wereInitialEventsAdded = true + } + } + + private fun receivedEvents(events: ArrayList, updateStatus: Int, forceRecreation: Boolean = false) { + if (context == null || activity == null) { + return + } + + mEvents = events + val listItems = context!!.getEventListItems(mEvents) + + activity?.runOnUiThread { + if (activity == null) { + return@runOnUiThread + } + + val currAdapter = mView.calendar_events_list.adapter + if (currAdapter == null || forceRecreation) { + EventListAdapter(activity as SimpleActivity, listItems, true, this, mView.calendar_events_list) { + if (it is ListEvent) { + editEvent(it) + } + }.apply { + mView.calendar_events_list.adapter = this + } + + mView.calendar_events_list.endlessScrollListener = object : MyRecyclerView.EndlessScrollListener { + override fun updateTop() { + fetchPreviousPeriod() + } + + override fun updateBottom() { + fetchNextPeriod() + } + } + } else { + (currAdapter as EventListAdapter).updateListItems(listItems) + if (updateStatus == UPDATE_TOP) { + val item = listItems.indexOfFirst { it == bottomItemAtRefresh } + if (item != -1) { + mView.calendar_events_list.scrollToPosition(item) + } + } else if (updateStatus == UPDATE_BOTTOM) { + mView.calendar_events_list.smoothScrollBy(0, context!!.resources.getDimension(R.dimen.endless_scroll_move_height).toInt()) + } + } + checkPlaceholderVisibility() + } + } + + private fun checkPlaceholderVisibility() { + mView.calendar_empty_list_placeholder.beVisibleIf(mEvents.isEmpty()) + mView.calendar_empty_list_placeholder_2.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) + } + } + + private fun fetchPreviousPeriod() { + val lastPosition = (mView.calendar_events_list.layoutManager as MyLinearLayoutManager).findLastVisibleItemPosition() + bottomItemAtRefresh = (mView.calendar_events_list.adapter as EventListAdapter).listItems[lastPosition] + + val oldMinFetchedTS = minFetchedTS - 1 + minFetchedTS -= FETCH_INTERVAL + context!!.eventsHelper.getEvents(minFetchedTS, oldMinFetchedTS) { + mEvents.addAll(0, it) + receivedEvents(mEvents, UPDATE_TOP) + } + } + + private fun fetchNextPeriod() { + val oldMaxFetchedTS = maxFetchedTS + 1 + maxFetchedTS += FETCH_INTERVAL + context!!.eventsHelper.getEvents(oldMaxFetchedTS, maxFetchedTS) { + mEvents.addAll(it) + receivedEvents(mEvents, UPDATE_BOTTOM) + } + } + + override fun refreshItems() { + checkEvents() + } + + override fun goToToday() { + val listItems = context!!.getEventListItems(mEvents) + val firstNonPastSectionIndex = listItems.indexOfFirst { it is ListSection && !it.isPastSection } + if (firstNonPastSectionIndex != -1) { + (mView.calendar_events_list.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(firstNonPastSectionIndex, 0) + } + } + + override fun showGoToDateDialog() {} + + override fun refreshEvents() { + checkEvents() + } + + override fun shouldGoToTodayBeVisible() = false + + override fun updateActionBarTitle() { + (activity as? MainActivity)?.updateActionBarTitle(getString(R.string.app_launcher_name)) + } + + override fun getNewEventDayCode() = Formatter.getTodayCode() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragment.kt new file mode 100644 index 000000000..718dc0da8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragment.kt @@ -0,0 +1,145 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.content.Context +import android.content.res.Resources +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout +import androidx.fragment.app.Fragment +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.helpers.Config +import com.simplemobiletools.calendar.pro.helpers.DAY_CODE +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.helpers.MonthlyCalendarImpl +import com.simplemobiletools.calendar.pro.interfaces.MonthlyCalendar +import com.simplemobiletools.calendar.pro.interfaces.NavigationListener +import com.simplemobiletools.calendar.pro.models.DayMonthly +import com.simplemobiletools.commons.extensions.applyColorFilter +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 mSundayFirst = false + private var mShowWeekNumbers = false + private var mDayCode = "" + private var mPackageName = "" + private var mLastHash = 0L + private var mCalendar: MonthlyCalendarImpl? = null + + var listener: NavigationListener? = null + + lateinit var mRes: Resources + lateinit var mHolder: RelativeLayout + lateinit var mConfig: Config + + 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.month_calendar_holder + mDayCode = arguments!!.getString(DAY_CODE)!! + mConfig = context!!.config + storeStateVariables() + + setupButtons() + mCalendar = MonthlyCalendarImpl(this, context!!) + + return view + } + + override fun onPause() { + super.onPause() + storeStateVariables() + } + + override fun onResume() { + super.onResume() + if (mConfig.showWeekNumbers != mShowWeekNumbers) { + mLastHash = -1L + } + + mCalendar!!.apply { + mTargetDate = Formatter.getDateTimeFromCode(mDayCode) + getDays(false) // prefill the screen asap, even if without events + } + + storeStateVariables() + updateCalendar() + } + + private fun storeStateVariables() { + mConfig.apply { + mSundayFirst = isSundayFirst + mShowWeekNumbers = showWeekNumbers + } + } + + fun updateCalendar() { + mCalendar?.updateMonthlyCalendar(Formatter.getDateTimeFromCode(mDayCode)) + } + + override fun updateMonthlyCalendar(context: Context, month: String, days: ArrayList, checkedEvents: Boolean, currTargetDate: DateTime) { + val newHash = month.hashCode() + days.hashCode().toLong() + if ((mLastHash != 0L && !checkedEvents) || mLastHash == newHash) { + return + } + + mLastHash = newHash + + activity?.runOnUiThread { + mHolder.top_value.apply { + text = month + contentDescription = text + setTextColor(mConfig.textColor) + } + updateDays(days) + } + } + + private fun setupButtons() { + mTextColor = mConfig.textColor + + mHolder.top_left_arrow.apply { + applyColorFilter(mTextColor) + background = null + setOnClickListener { + listener?.goLeft() + } + + val pointerLeft = context!!.getDrawable(R.drawable.ic_chevron_left_vector) + pointerLeft?.isAutoMirrored = true + setImageDrawable(pointerLeft) + } + + mHolder.top_right_arrow.apply { + applyColorFilter(mTextColor) + background = null + setOnClickListener { + listener?.goRight() + } + + val pointerRight = context!!.getDrawable(R.drawable.ic_chevron_right_vector) + pointerRight?.isAutoMirrored = true + setImageDrawable(pointerRight) + } + + mHolder.top_value.apply { + setTextColor(mConfig.textColor) + setOnClickListener { + (activity as MainActivity).showGoToDateDialog() + } + } + } + + private fun updateDays(days: ArrayList) { + mHolder.month_view_wrapper.updateDays(days) { + (activity as MainActivity).openDayFromMonthly(Formatter.getDateTimeFromCode(it.code)) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt new file mode 100644 index 000000000..e9eebd787 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt @@ -0,0 +1,143 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.content.res.Resources +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.DatePicker +import androidx.appcompat.app.AlertDialog +import androidx.viewpager.widget.ViewPager +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.adapters.MyMonthPagerAdapter +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.getMonthCode +import com.simplemobiletools.calendar.pro.helpers.DAY_CODE +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.interfaces.NavigationListener +import com.simplemobiletools.commons.extensions.beGone +import com.simplemobiletools.commons.extensions.getDialogTheme +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.updateActionBarTitle +import com.simplemobiletools.commons.views.MyViewPager +import kotlinx.android.synthetic.main.fragment_months_holder.view.* +import org.joda.time.DateTime + +class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener { + private val PREFILLED_MONTHS = 251 + + private var viewPager: MyViewPager? = null + private var defaultMonthlyPage = 0 + private var todayDayCode = "" + private var currentDayCode = "" + private var isGoToTodayVisible = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + currentDayCode = arguments?.getString(DAY_CODE) ?: "" + todayDayCode = Formatter.getTodayCode() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_months_holder, container, false) + view.background = ColorDrawable(context!!.config.backgroundColor) + viewPager = view.fragment_months_viewpager + viewPager!!.id = (System.currentTimeMillis() % 100000).toInt() + setupFragment() + return view + } + + private fun setupFragment() { + val codes = getMonths(currentDayCode) + val monthlyAdapter = MyMonthPagerAdapter(activity!!.supportFragmentManager, codes, this) + defaultMonthlyPage = codes.size / 2 + + viewPager!!.apply { + adapter = monthlyAdapter + addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + } + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + } + + override fun onPageSelected(position: Int) { + currentDayCode = codes[position] + val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() + if (isGoToTodayVisible != shouldGoToTodayBeVisible) { + (activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible) + isGoToTodayVisible = shouldGoToTodayBeVisible + } + } + }) + currentItem = defaultMonthlyPage + } + updateActionBarTitle() + } + + private fun getMonths(code: String): List { + val months = ArrayList(PREFILLED_MONTHS) + val today = Formatter.getDateTimeFromCode(code).withDayOfMonth(1) + for (i in -PREFILLED_MONTHS / 2..PREFILLED_MONTHS / 2) { + months.add(Formatter.getDayCodeFromDateTime(today.plusMonths(i))) + } + + return months + } + + override fun goLeft() { + viewPager!!.currentItem = viewPager!!.currentItem - 1 + } + + override fun goRight() { + viewPager!!.currentItem = viewPager!!.currentItem + 1 + } + + override fun goToDateTime(dateTime: DateTime) { + currentDayCode = Formatter.getDayCodeFromDateTime(dateTime) + setupFragment() + } + + override fun goToToday() { + currentDayCode = todayDayCode + setupFragment() + } + + override fun showGoToDateDialog() { + 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(Formatter.getDateTimeFromCode(currentDayCode).toString()) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, 1, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> datePicked(dateTime, datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun datePicked(dateTime: DateTime, datePicker: DatePicker) { + val month = datePicker.month + 1 + val year = datePicker.year + val newDateTime = dateTime.withDate(year, month, 1) + goToDateTime(newDateTime) + } + + override fun refreshEvents() { + (viewPager?.adapter as? MyMonthPagerAdapter)?.updateCalendars(viewPager?.currentItem ?: 0) + } + + override fun shouldGoToTodayBeVisible() = currentDayCode.getMonthCode() != todayDayCode.getMonthCode() + + override fun updateActionBarTitle() { + (activity as? MainActivity)?.updateActionBarTitle(getString(R.string.app_launcher_name)) + } + + override fun getNewEventDayCode() = if (shouldGoToTodayBeVisible()) currentDayCode else todayDayCode +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MyFragmentHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MyFragmentHolder.kt new file mode 100644 index 000000000..a7708e98e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MyFragmentHolder.kt @@ -0,0 +1,17 @@ +package com.simplemobiletools.calendar.pro.fragments + +import androidx.fragment.app.Fragment + +abstract class MyFragmentHolder : Fragment() { + abstract fun goToToday() + + abstract fun showGoToDateDialog() + + abstract fun refreshEvents() + + abstract fun shouldGoToTodayBeVisible(): Boolean + + abstract fun updateActionBarTitle() + + abstract fun getNewEventDayCode(): String +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt new file mode 100644 index 000000000..1c1280f56 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt @@ -0,0 +1,588 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.annotation.SuppressLint +import android.content.Intent +import android.content.res.Resources +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.util.Range +import android.view.* +import android.widget.ImageView +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.collection.LongSparseArray +import androidx.fragment.app.Fragment +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.EventActivity +import com.simplemobiletools.calendar.pro.extensions.* +import com.simplemobiletools.calendar.pro.helpers.* +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.interfaces.WeekFragmentListener +import com.simplemobiletools.calendar.pro.interfaces.WeeklyCalendar +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.calendar.pro.models.EventWeeklyView +import com.simplemobiletools.calendar.pro.views.MyScrollView +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.DAY_SECONDS +import com.simplemobiletools.commons.helpers.WEEK_SECONDS +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 PLUS_FADEOUT_DELAY = 5000L + private val MIN_SCALE_FACTOR = 0.3f + private val MAX_SCALE_FACTOR = 5f + private val MIN_SCALE_DIFFERENCE = 0.02f + private val SCALE_RANGE = MAX_SCALE_FACTOR - MIN_SCALE_FACTOR + + var listener: WeekFragmentListener? = null + private var weekTimestamp = 0L + private var rowHeight = 0f + private var todayColumnIndex = -1 + private var primaryColor = 0 + private var lastHash = 0 + private var prevScaleSpanY = 0f + private var scaleCenterPercent = 0f + private var defaultRowHeight = 0f + private var screenHeight = 0 + private var rowHeightsAtScale = 0f + private var prevScaleFactor = 0f + private var mWasDestroyed = false + private var isFragmentVisible = false + private var wasFragmentInit = false + private var wasExtraHeightAdded = false + private var dimPastEvents = true + private var wasScaled = false + private var selectedGrid: View? = null + private var currentTimeView: ImageView? = null + private var allDayHolders = ArrayList() + private var allDayRows = ArrayList>() + private var currEvents = ArrayList() + private var eventTypeColors = LongSparseArray() + private var eventTimeRanges = LinkedHashMap>() + + private lateinit var inflater: LayoutInflater + private lateinit var mView: View + private lateinit var scrollView: MyScrollView + private lateinit var res: Resources + private lateinit var config: Config + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + res = context!!.resources + config = context!!.config + rowHeight = context!!.getWeeklyViewItemHeight() + defaultRowHeight = res.getDimension(R.dimen.weekly_view_row_height) + weekTimestamp = arguments!!.getLong(WEEK_START_TIMESTAMP) + dimPastEvents = config.dimPastEvents + primaryColor = context!!.getAdjustedPrimaryColor() + allDayRows.add(HashSet()) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + this.inflater = inflater + + val fullHeight = context!!.getWeeklyViewItemHeight().toInt() * 24 + mView = inflater.inflate(R.layout.fragment_week, container, false).apply { + scrollView = week_events_scrollview + week_horizontal_grid_holder.layoutParams.height = fullHeight + week_events_columns_holder.layoutParams.height = fullHeight + + val scaleDetector = getViewScaleDetector() + scrollView.setOnTouchListener { view, motionEvent -> + scaleDetector.onTouchEvent(motionEvent) + if (motionEvent.action == MotionEvent.ACTION_UP && wasScaled) { + scrollView.isScrollable = true + wasScaled = false + true + } else { + false + } + } + } + + scrollView.setOnScrollviewListener(object : MyScrollView.ScrollViewListener { + override fun onScrollChanged(scrollView: MyScrollView, x: Int, y: Int, oldx: Int, oldy: Int) { + checkScrollLimits(y) + } + }) + + scrollView.onGlobalLayout { + if (fullHeight < scrollView.height) { + scrollView.layoutParams.height = fullHeight - context!!.resources.getDimension(R.dimen.one_dp).toInt() + } + + val initialScrollY = (rowHeight * config.startWeeklyAt).toInt() + updateScrollY(Math.max(listener?.getCurrScrollY() ?: 0, initialScrollY)) + } + + wasFragmentInit = true + return mView + } + + override fun onResume() { + super.onResume() + context!!.eventsHelper.getEventTypes(activity!!, false) { + it.map { + eventTypeColors.put(it.id!!, it.color) + } + } + + setupDayLabels() + updateCalendar() + } + + override fun onPause() { + super.onPause() + wasExtraHeightAdded = true + } + + override fun onDestroyView() { + super.onDestroyView() + mWasDestroyed = true + } + + override fun setMenuVisibility(menuVisible: Boolean) { + super.setMenuVisibility(menuVisible) + isFragmentVisible = menuVisible + if (isFragmentVisible && wasFragmentInit) { + listener?.updateHoursTopMargin(mView.week_top_holder.height) + checkScrollLimits(scrollView.scrollY) + + // fix some glitches like at swiping from a fully scaled out fragment will all-day events to an empty one + val fullFragmentHeight = (listener?.getFullFragmentHeight() ?: 0) - mView.week_top_holder.height + if (scrollView.height < fullFragmentHeight) { + config.weeklyViewItemHeightMultiplier = fullFragmentHeight / 24 / defaultRowHeight + updateViewScale() + listener?.updateRowHeight(rowHeight.toInt()) + } + } + } + + fun updateCalendar() { + if (context != null) { + WeeklyCalendarImpl(this, context!!).updateWeeklyCalendar(weekTimestamp) + } + } + + private fun setupDayLabels() { + var curDay = Formatter.getDateTimeFromTS(weekTimestamp) + val textColor = config.textColor + val todayCode = Formatter.getDayCodeFromDateTime(DateTime()) + for (i in 0..6) { + val dayCode = Formatter.getDayCodeFromDateTime(curDay) + val dayLetters = res.getStringArray(R.array.week_day_letters).toMutableList() as ArrayList + val dayLetter = dayLetters[curDay.dayOfWeek - 1] + + mView.findViewById(res.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 checkScrollLimits(y: Int) { + if (isFragmentVisible) { + listener?.scrollTo(y) + } + } + + private fun initGrid() { + (0..6).map { getColumnWithId(it) } + .forEachIndexed { index, layout -> + layout.removeAllViews() + val gestureDetector = getViewGestureDetector(layout, index) + + layout.setOnTouchListener { view, motionEvent -> + gestureDetector.onTouchEvent(motionEvent) + true + } + } + } + + private fun getViewGestureDetector(view: ViewGroup, index: Int): GestureDetector { + return GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapUp(event: MotionEvent): Boolean { + selectedGrid?.animation?.cancel() + selectedGrid?.beGone() + + val hour = (event.y / rowHeight).toInt() + selectedGrid = (inflater.inflate(R.layout.week_grid_item, null, false) as ImageView).apply { + view.addView(this) + background = ColorDrawable(primaryColor) + layoutParams.width = view.width + layoutParams.height = rowHeight.toInt() + y = hour * rowHeight + applyColorFilter(primaryColor.getContrastColor()) + + setOnClickListener { + val timestamp = weekTimestamp + 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().setStartDelay(PLUS_FADEOUT_DELAY).alpha(0f).withEndAction { + beGone() + } + } + return super.onSingleTapUp(event) + } + }) + } + + private fun getViewScaleDetector(): ScaleGestureDetector { + return ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + val percent = (prevScaleSpanY - detector.currentSpanY) / screenHeight + prevScaleSpanY = detector.currentSpanY + + val wantedFactor = config.weeklyViewItemHeightMultiplier - (SCALE_RANGE * percent) + var newFactor = Math.max(Math.min(wantedFactor, MAX_SCALE_FACTOR), MIN_SCALE_FACTOR) + if (scrollView.height > defaultRowHeight * newFactor * 24) { + newFactor = scrollView.height / 24f / defaultRowHeight + } + + if (Math.abs(newFactor - prevScaleFactor) > MIN_SCALE_DIFFERENCE) { + prevScaleFactor = newFactor + config.weeklyViewItemHeightMultiplier = newFactor + updateViewScale() + listener?.updateRowHeight(rowHeight.toInt()) + + val targetY = rowHeightsAtScale * rowHeight - scaleCenterPercent * getVisibleHeight() + scrollView.scrollTo(0, targetY.toInt()) + } + return super.onScale(detector) + } + + override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { + scaleCenterPercent = detector.focusY / scrollView.height + rowHeightsAtScale = (scrollView.scrollY + scaleCenterPercent * getVisibleHeight()) / rowHeight + scrollView.isScrollable = false + prevScaleSpanY = detector.currentSpanY + prevScaleFactor = config.weeklyViewItemHeightMultiplier + wasScaled = true + screenHeight = context!!.realScreenSize.y + return super.onScaleBegin(detector) + } + }) + } + + private fun getVisibleHeight(): Float { + val fullContentHeight = rowHeight * 24 + val visibleRatio = scrollView.height / fullContentHeight + return fullContentHeight * visibleRatio + } + + override fun updateWeeklyCalendar(events: ArrayList) { + val newHash = events.hashCode() + if (newHash == lastHash || mWasDestroyed || context == null) { + return + } + + lastHash = newHash + + activity!!.runOnUiThread { + if (context != null && activity != null && isAdded) { + val replaceDescription = config.replaceDescription + val sorted = events.sortedWith( + compareBy { it.startTS }.thenBy { it.endTS }.thenBy { it.title }.thenBy { if (replaceDescription) it.location else it.description } + ).toMutableList() as ArrayList + + currEvents = sorted + addEvents(sorted) + } + } + } + + private fun updateViewScale() { + rowHeight = context?.getWeeklyViewItemHeight() ?: return + + val oneDp = context!!.resources.getDimension(R.dimen.one_dp).toInt() + val fullHeight = Math.max(rowHeight.toInt() * 24, scrollView.height + oneDp) + scrollView.layoutParams.height = fullHeight - oneDp + mView.week_horizontal_grid_holder.layoutParams.height = fullHeight + mView.week_events_columns_holder.layoutParams.height = fullHeight + addEvents(currEvents) + } + + private fun addEvents(events: ArrayList) { + initGrid() + allDayHolders.clear() + allDayRows.clear() + eventTimeRanges.clear() + allDayRows.add(HashSet()) + week_all_day_holder?.removeAllViews() + + addNewLine() + + val minuteHeight = rowHeight / 60 + val minimalHeight = res.getDimension(R.dimen.weekly_view_minimal_event_height).toInt() + val density = Math.round(res.displayMetrics.density) + + var hadAllDayEvent = false + + for (event in events) { + val startDateTime = Formatter.getDateTimeFromTS(event.startTS) + val endDateTime = Formatter.getDateTimeFromTS(event.endTS) + if (!event.getIsAllDay() && Formatter.getDayCodeFromDateTime(startDateTime) == Formatter.getDayCodeFromDateTime(endDateTime)) { + val startMinutes = startDateTime.minuteOfDay + val duration = endDateTime.minuteOfDay - startMinutes + val range = Range(startMinutes, startMinutes + duration) + val eventWeekly = EventWeeklyView(event.id!!, range) + + val dayCode = Formatter.getDayCodeFromDateTime(startDateTime) + if (!eventTimeRanges.containsKey(dayCode)) { + eventTimeRanges[dayCode] = ArrayList() + } + + eventTimeRanges[dayCode]?.add(eventWeekly) + } + } + + for (event in events) { + val startDateTime = Formatter.getDateTimeFromTS(event.startTS) + val endDateTime = Formatter.getDateTimeFromTS(event.endTS) + if (event.getIsAllDay() || Formatter.getDayCodeFromDateTime(startDateTime) != Formatter.getDayCodeFromDateTime(endDateTime)) { + hadAllDayEvent = true + addAllDayEvent(event) + } else { + val dayOfWeek = startDateTime.plusDays(if (config.isSundayFirst) 1 else 0).dayOfWeek - 1 + val layout = getColumnWithId(dayOfWeek) + + val startMinutes = startDateTime.minuteOfDay + val duration = endDateTime.minuteOfDay - startMinutes + val range = Range(startMinutes, startMinutes + duration) + + val dayCode = Formatter.getDayCodeFromDateTime(startDateTime) + var overlappingEvents = 0 + var currentEventOverlapIndex = 0 + var foundCurrentEvent = false + + eventTimeRanges[dayCode]!!.forEachIndexed { index, eventWeeklyView -> + if (eventWeeklyView.range.touch(range)) { + overlappingEvents++ + + if (eventWeeklyView.id == event.id) { + foundCurrentEvent = true + } + + if (!foundCurrentEvent) { + currentEventOverlapIndex++ + } + } + } + + (inflater.inflate(R.layout.week_event_marker, null, false) as TextView).apply { + var backgroundColor = eventTypeColors.get(event.eventType, primaryColor) + var textColor = backgroundColor.getContrastColor() + if (dimPastEvents && event.isPastEvent) { + backgroundColor = backgroundColor.adjustAlpha(LOW_ALPHA) + textColor = textColor.adjustAlpha(LOW_ALPHA) + } + + background = ColorDrawable(backgroundColor) + setTextColor(textColor) + text = event.title + contentDescription = text + layout.addView(this) + y = startMinutes * minuteHeight + (layoutParams as RelativeLayout.LayoutParams).apply { + width = layout.width - 1 + width /= Math.max(overlappingEvents, 1) + if (overlappingEvents > 1) { + x = width * currentEventOverlapIndex.toFloat() + if (currentEventOverlapIndex != 0) { + x += density + } + + width -= density + if (currentEventOverlapIndex + 1 != overlappingEvents) { + if (currentEventOverlapIndex != 0) { + width -= density + } + } + } + + 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) + if (currentTimeView != null) { + mView.week_events_holder.removeView(currentTimeView) + } + + currentTimeView = (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 = res.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.onGlobalLayout { + if (isFragmentVisible && activity != null && !mWasDestroyed) { + listener?.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 { + var backgroundColor = eventTypeColors.get(event.eventType, primaryColor) + var textColor = backgroundColor.getContrastColor() + if (dimPastEvents && event.isPastEvent) { + backgroundColor = backgroundColor.adjustAlpha(LOW_ALPHA) + textColor = textColor.adjustAlpha(LOW_ALPHA) + } + background = ColorDrawable(backgroundColor) + + setTextColor(textColor) + text = event.title + contentDescription = text + + val startDateTime = Formatter.getDateTimeFromTS(event.startTS) + val endDateTime = Formatter.getDateTimeFromTS(event.endTS) + + val minTS = Math.max(startDateTime.seconds(), weekTimestamp) + val maxTS = Math.min(endDateTime.seconds(), weekTimestamp + WEEK_SECONDS) + + // fix a visual glitch with all-day events or events lasting multiple days starting at midnight on monday, being shown the previous week too + if (minTS == maxTS && (minTS - weekTimestamp == WEEK_SECONDS.toLong())) { + return + } + + val isStartTimeDay = Formatter.getDateTimeFromTS(maxTS) == Formatter.getDateTimeFromTS(maxTS).withTimeAtStartOfDay() + val numDays = Days.daysBetween(Formatter.getDateTimeFromTS(minTS).toLocalDate(), Formatter.getDateTimeFromTS(maxTS).toLocalDate()).days + val daysCnt = if (numDays == 1 && isStartTimeDay) 0 else numDays + val startDateTimeInWeek = Formatter.getDateTimeFromTS(minTS) + val firstDayIndex = (startDateTimeInWeek.dayOfWeek - if (config.isSundayFirst) 0 else 1) % 7 + + 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 { + 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.onGlobalLayout { + if (activity != null && !mWasDestroyed) { + if (isFragmentVisible) { + listener?.updateHoursTopMargin(mView.week_top_holder.height) + } + + if (!wasExtraHeightAdded) { + wasExtraHeightAdded = true + } + } + } + } + + private fun getColumnWithId(id: Int) = mView.findViewById(res.getIdentifier("week_column_$id", "id", context!!.packageName)) + + fun updateScrollY(y: Int) { + if (wasFragmentInit) { + scrollView.scrollY = y + } + } + + fun updateNotVisibleViewScaleLevel() { + if (!isFragmentVisible) { + updateViewScale() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt new file mode 100644 index 000000000..8deba96df --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt @@ -0,0 +1,227 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.DatePicker +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.viewpager.widget.ViewPager +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.adapters.MyWeekPagerAdapter +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.getWeeklyViewItemHeight +import com.simplemobiletools.calendar.pro.extensions.seconds +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.helpers.WEEK_START_DATE_TIME +import com.simplemobiletools.calendar.pro.interfaces.WeekFragmentListener +import com.simplemobiletools.calendar.pro.views.MyScrollView +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.WEEK_SECONDS +import com.simplemobiletools.commons.views.MyViewPager +import kotlinx.android.synthetic.main.fragment_week_holder.view.* +import org.joda.time.DateTime + +class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { + private val PREFILLED_WEEKS = 151 + + private var viewPager: MyViewPager? = null + private var weekHolder: ViewGroup? = null + private var defaultWeeklyPage = 0 + private var thisWeekTS = 0L + private var currentWeekTS = 0L + private var isGoToTodayVisible = false + private var weekScrollY = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val dateTimeString = arguments?.getString(WEEK_START_DATE_TIME) ?: return + currentWeekTS = (DateTime.parse(dateTimeString) ?: DateTime()).seconds() + thisWeekTS = currentWeekTS + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + weekHolder = inflater.inflate(R.layout.fragment_week_holder, container, false) as ViewGroup + weekHolder!!.background = ColorDrawable(context!!.config.backgroundColor) + + val itemHeight = context!!.getWeeklyViewItemHeight().toInt() + weekHolder!!.week_view_hours_holder.setPadding(0, 0, 0, itemHeight) + + viewPager = weekHolder!!.week_view_view_pager + viewPager!!.id = (System.currentTimeMillis() % 100000).toInt() + setupFragment() + return weekHolder + } + + private fun setupFragment() { + val weekTSs = getWeekTimestamps(currentWeekTS) + val weeklyAdapter = MyWeekPagerAdapter(activity!!.supportFragmentManager, weekTSs, this) + val itemHeight = context!!.getWeeklyViewItemHeight().toInt() + + val textColor = context!!.config.textColor + weekHolder!!.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(context!!, hourDateTime.withHourOfDay(i)) + (layoutInflater.inflate(R.layout.weekly_view_hour_textview, null, false) as TextView).apply { + text = formattedHours + setTextColor(textColor) + height = itemHeight + weekHolder!!.week_view_hours_holder.addView(this) + } + } + + defaultWeeklyPage = weekTSs.size / 2 + viewPager!!.apply { + adapter = weeklyAdapter + addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) {} + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} + + override fun onPageSelected(position: Int) { + currentWeekTS = weekTSs[position] + val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() + if (isGoToTodayVisible != shouldGoToTodayBeVisible) { + (activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible) + isGoToTodayVisible = shouldGoToTodayBeVisible + } + + setupWeeklyActionbarTitle(weekTSs[position]) + } + }) + currentItem = defaultWeeklyPage + } + + weekHolder!!.week_view_hours_scrollview.setOnScrollviewListener(object : MyScrollView.ScrollViewListener { + override fun onScrollChanged(scrollView: MyScrollView, x: Int, y: Int, oldx: Int, oldy: Int) { + weekScrollY = y + weeklyAdapter.updateScrollY(viewPager!!.currentItem, y) + } + }) + weekHolder!!.week_view_hours_scrollview.setOnTouchListener { view, motionEvent -> true } + updateActionBarTitle() + } + + private fun getWeekTimestamps(targetSeconds: Long): List { + val weekTSs = ArrayList(PREFILLED_WEEKS) + val dateTime = Formatter.getDateTimeFromTS(targetSeconds) + var currentWeek = dateTime.minusWeeks(PREFILLED_WEEKS / 2) + for (i in 0 until PREFILLED_WEEKS) { + weekTSs.add(currentWeek.seconds()) + currentWeek = currentWeek.plusWeeks(1) + } + return weekTSs + } + + private fun setupWeeklyActionbarTitle(timestamp: Long) { + val startDateTime = Formatter.getDateTimeFromTS(timestamp) + val endDateTime = Formatter.getDateTimeFromTS(timestamp + WEEK_SECONDS) + val startMonthName = Formatter.getMonthName(context!!, startDateTime.monthOfYear) + if (startDateTime.monthOfYear == endDateTime.monthOfYear) { + var newTitle = startMonthName + if (startDateTime.year != DateTime().year) { + newTitle += " - ${startDateTime.year}" + } + (activity as AppCompatActivity).updateActionBarTitle(newTitle) + } else { + val endMonthName = Formatter.getMonthName(context!!, endDateTime.monthOfYear) + (activity as AppCompatActivity).updateActionBarTitle("$startMonthName - $endMonthName") + } + (activity as AppCompatActivity).updateActionBarSubtitle("${getString(R.string.week)} ${startDateTime.plusDays(3).weekOfWeekyear}") + } + + override fun goToToday() { + currentWeekTS = thisWeekTS + setupFragment() + } + + override fun showGoToDateDialog() { + activity!!.setTheme(context!!.getDialogTheme()) + val view = layoutInflater.inflate(R.layout.date_picker, null) + val datePicker = view.findViewById(R.id.date_picker) + + val dateTime = Formatter.getDateTimeFromTS(currentWeekTS) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> dateSelected(dateTime, datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun dateSelected(dateTime: DateTime, datePicker: DatePicker) { + val isSundayFirst = context!!.config.isSundayFirst + val month = datePicker.month + 1 + val year = datePicker.year + val day = datePicker.dayOfMonth + var newDateTime = dateTime.withDate(year, month, day) + + if (isSundayFirst) { + newDateTime = newDateTime.plusDays(1) + } + + var selectedWeek = newDateTime.withDayOfWeek(1).withTimeAtStartOfDay().minusDays(if (isSundayFirst) 1 else 0) + if (newDateTime.minusDays(7).seconds() > selectedWeek.seconds()) { + selectedWeek = selectedWeek.plusDays(7) + } + + currentWeekTS = selectedWeek.seconds() + setupFragment() + } + + override fun refreshEvents() { + (viewPager?.adapter as? MyWeekPagerAdapter)?.updateCalendars(viewPager!!.currentItem) + } + + override fun shouldGoToTodayBeVisible() = currentWeekTS != thisWeekTS + + override fun updateActionBarTitle() { + setupWeeklyActionbarTitle(currentWeekTS) + } + + override fun getNewEventDayCode(): String { + val currentTS = System.currentTimeMillis() / 1000 + return if (currentTS > currentWeekTS && currentTS < currentWeekTS + WEEK_SECONDS) { + Formatter.getTodayCode() + } else { + Formatter.getDayCodeFromTS(currentWeekTS) + } + } + + override fun scrollTo(y: Int) { + weekHolder!!.week_view_hours_scrollview.scrollY = y + weekScrollY = y + } + + override fun updateHoursTopMargin(margin: Int) { + weekHolder?.apply { + week_view_hours_divider?.layoutParams?.height = margin + week_view_hours_scrollview?.requestLayout() + week_view_hours_scrollview?.onGlobalLayout { + week_view_hours_scrollview.scrollY = weekScrollY + } + } + } + + override fun getCurrScrollY() = weekScrollY + + override fun updateRowHeight(rowHeight: Int) { + val childCnt = weekHolder!!.week_view_hours_holder.childCount + for (i in 0..childCnt) { + val textView = weekHolder!!.week_view_hours_holder.getChildAt(i) as? TextView ?: continue + textView.layoutParams.height = rowHeight + } + + weekHolder!!.week_view_hours_holder.setPadding(0, 0, 0, rowHeight) + (viewPager!!.adapter as? MyWeekPagerAdapter)?.updateNotVisibleScaleLevel(viewPager!!.currentItem) + } + + override fun getFullFragmentHeight() = weekHolder!!.week_view_holder.height +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/YearFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragment.kt similarity index 70% rename from app/src/main/kotlin/com/simplemobiletools/calendar/fragments/YearFragment.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragment.kt index 6231faed7..14ec7ad2b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/fragments/YearFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragment.kt @@ -1,34 +1,34 @@ -package com.simplemobiletools.calendar.fragments +package com.simplemobiletools.calendar.pro.fragments import android.content.res.Resources import android.os.Bundle -import android.support.v4.app.Fragment import android.util.SparseArray import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.helpers.YEAR_LABEL -import com.simplemobiletools.calendar.helpers.YearlyCalendarImpl -import com.simplemobiletools.calendar.interfaces.NavigationListener -import com.simplemobiletools.calendar.interfaces.YearlyCalendar -import com.simplemobiletools.calendar.models.DayYearly -import com.simplemobiletools.calendar.views.SmallMonthView +import androidx.fragment.app.Fragment +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.helpers.YEAR_LABEL +import com.simplemobiletools.calendar.pro.helpers.YearlyCalendarImpl +import com.simplemobiletools.calendar.pro.interfaces.YearlyCalendar +import com.simplemobiletools.calendar.pro.models.DayYearly +import com.simplemobiletools.calendar.pro.views.SmallMonthView +import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor import com.simplemobiletools.commons.extensions.updateTextColors import kotlinx.android.synthetic.main.fragment_year.view.* import org.joda.time.DateTime import java.util.* class YearFragment : Fragment(), YearlyCalendar { - var mListener: NavigationListener? = null private var mYear = 0 private var mSundayFirst = false private var lastHash = 0 + private var mCalendar: YearlyCalendarImpl? = null lateinit var mView: View - lateinit var mCalendar: YearlyCalendarImpl override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { mView = inflater.inflate(R.layout.fragment_year, container, false) @@ -41,6 +41,11 @@ class YearFragment : Fragment(), YearlyCalendar { return mView } + override fun onPause() { + super.onPause() + mSundayFirst = context!!.config.isSundayFirst + } + override fun onResume() { super.onResume() val sundayFirst = context!!.config.isSundayFirst @@ -48,14 +53,14 @@ class YearFragment : Fragment(), YearlyCalendar { mSundayFirst = sundayFirst setupMonths() } - updateEvents() + updateCalendar() } - fun updateEvents() { - mCalendar.getEvents(mYear) + fun updateCalendar() { + mCalendar?.getEvents(mYear) } - fun setupMonths() { + private fun setupMonths() { val dateTime = DateTime().withDate(mYear, 2, 1).withHourOfDay(12) val days = dateTime.dayOfMonth().maximumValue mView.month_2.setDays(days) @@ -66,12 +71,13 @@ class YearFragment : Fragment(), YearlyCalendar { for (i in 1..12) { val monthView = mView.findViewById(res.getIdentifier("month_" + i, "id", context!!.packageName)) var dayOfWeek = dateTime.withMonthOfYear(i).dayOfWeek().get() - if (!mSundayFirst) + if (!mSundayFirst) { dayOfWeek-- + } monthView.firstDay = dayOfWeek monthView.setOnClickListener { - mListener?.goToDateTime(DateTime().withDate(mYear, i, 1)) + (activity as MainActivity).openMonthFromYearly(DateTime().withDate(mYear, i, 1)) } } } @@ -80,7 +86,7 @@ class YearFragment : Fragment(), YearlyCalendar { val now = DateTime() if (now.year == mYear) { val monthLabel = mView.findViewById(res.getIdentifier("month_${now.monthOfYear}_label", "id", context!!.packageName)) - monthLabel.setTextColor(context!!.config.primaryColor) + monthLabel.setTextColor(context!!.getAdjustedPrimaryColor()) val monthView = mView.findViewById(res.getIdentifier("month_${now.monthOfYear}", "id", context!!.packageName)) monthView.todaysId = now.dayOfMonth diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt new file mode 100644 index 000000000..ff3221d8f --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt @@ -0,0 +1,129 @@ +package com.simplemobiletools.calendar.pro.fragments + +import android.content.res.Resources +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.DatePicker +import androidx.appcompat.app.AlertDialog +import androidx.viewpager.widget.ViewPager +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.MainActivity +import com.simplemobiletools.calendar.pro.adapters.MyYearPagerAdapter +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.commons.extensions.beGone +import com.simplemobiletools.commons.extensions.getDialogTheme +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.updateActionBarTitle +import com.simplemobiletools.commons.views.MyViewPager +import kotlinx.android.synthetic.main.fragment_years_holder.view.* +import org.joda.time.DateTime + +class YearFragmentsHolder : MyFragmentHolder() { + private val PREFILLED_YEARS = 61 + + private var viewPager: MyViewPager? = null + private var defaultYearlyPage = 0 + private var todayYear = 0 + private var currentYear = 0 + private var isGoToTodayVisible = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + currentYear = DateTime().toString(Formatter.YEAR_PATTERN).toInt() + todayYear = currentYear + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_years_holder, container, false) + view.background = ColorDrawable(context!!.config.backgroundColor) + viewPager = view.fragment_years_viewpager + viewPager!!.id = (System.currentTimeMillis() % 100000).toInt() + setupFragment() + return view + } + + private fun setupFragment() { + val years = getYears(currentYear) + val yearlyAdapter = MyYearPagerAdapter(activity!!.supportFragmentManager, years) + defaultYearlyPage = years.size / 2 + + viewPager?.apply { + adapter = yearlyAdapter + addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + override fun onPageScrollStateChanged(state: Int) { + } + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + } + + override fun onPageSelected(position: Int) { + currentYear = years[position] + val shouldGoToTodayBeVisible = shouldGoToTodayBeVisible() + if (isGoToTodayVisible != shouldGoToTodayBeVisible) { + (activity as? MainActivity)?.toggleGoToTodayVisibility(shouldGoToTodayBeVisible) + isGoToTodayVisible = shouldGoToTodayBeVisible + } + + if (position < years.size) { + (activity as? MainActivity)?.updateActionBarTitle("${getString(R.string.app_launcher_name)} - ${years[position]}") + } + } + }) + currentItem = defaultYearlyPage + } + updateActionBarTitle() + } + + private fun getYears(targetYear: Int): List { + val years = ArrayList(PREFILLED_YEARS) + years += targetYear - PREFILLED_YEARS / 2..targetYear + PREFILLED_YEARS / 2 + return years + } + + override fun goToToday() { + currentYear = todayYear + setupFragment() + } + + override fun showGoToDateDialog() { + 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() + datePicker.findViewById(Resources.getSystem().getIdentifier("month", "id", "android")).beGone() + + val dateTime = DateTime(Formatter.getDateTimeFromCode("${currentYear}0523").toString()) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, 1, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> datePicked(datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun datePicked(datePicker: DatePicker) { + val pickedYear = datePicker.year + if (currentYear != pickedYear) { + currentYear = datePicker.year + setupFragment() + } + } + + override fun refreshEvents() { + (viewPager?.adapter as? MyYearPagerAdapter)?.updateCalendars(viewPager?.currentItem ?: 0) + } + + override fun shouldGoToTodayBeVisible() = currentYear != todayYear + + override fun updateActionBarTitle() { + (activity as? MainActivity)?.updateActionBarTitle("${getString(R.string.app_launcher_name)} - $currentYear") + } + + override fun getNewEventDayCode() = Formatter.getTodayCode() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt new file mode 100644 index 000000000..888fd503b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt @@ -0,0 +1,508 @@ +package com.simplemobiletools.calendar.pro.helpers + +import android.annotation.SuppressLint +import android.content.ContentUris +import android.content.ContentValues +import android.content.Context +import android.provider.CalendarContract.* +import android.util.SparseIntArray +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.* +import com.simplemobiletools.calendar.pro.models.* +import com.simplemobiletools.calendar.pro.objects.States.isUpdatingCalDAV +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALENDAR +import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALENDAR +import org.joda.time.DateTimeZone +import org.joda.time.format.DateTimeFormat +import java.util.* +import kotlin.collections.ArrayList + +@SuppressLint("MissingPermission") +class CalDAVHelper(val context: Context) { + private val eventsHelper = context.eventsHelper + + fun refreshCalendars(showToasts: Boolean, callback: () -> Unit) { + if (isUpdatingCalDAV) { + return + } + + isUpdatingCalDAV = true + try { + val calDAVCalendars = getCalDAVCalendars(context.config.caldavSyncedCalendarIds, showToasts) + for (calendar in calDAVCalendars) { + val localEventType = eventsHelper.getEventTypeWithCalDAVCalendarId(calendar.id) ?: continue + localEventType.apply { + title = calendar.displayName + caldavDisplayName = calendar.displayName + caldavEmail = calendar.accountName + eventsHelper.insertOrUpdateEventTypeSync(this) + } + + fetchCalDAVCalendarEvents(calendar.id, localEventType.id!!, showToasts) + } + context.scheduleCalDAVSync(true) + callback() + } finally { + isUpdatingCalDAV = false + } + } + + @SuppressLint("MissingPermission") + fun getCalDAVCalendars(ids: String, showToasts: Boolean): ArrayList { + val calendars = ArrayList() + if (!context.hasPermission(PERMISSION_WRITE_CALENDAR) || !context.hasPermission(PERMISSION_READ_CALENDAR)) { + return calendars + } + + val uri = Calendars.CONTENT_URI + val projection = arrayOf( + Calendars._ID, + Calendars.CALENDAR_DISPLAY_NAME, + Calendars.ACCOUNT_NAME, + Calendars.ACCOUNT_TYPE, + Calendars.OWNER_ACCOUNT, + Calendars.CALENDAR_COLOR, + Calendars.CALENDAR_ACCESS_LEVEL) + + val selection = if (ids.trim().isNotEmpty()) "${Calendars._ID} IN ($ids)" else null + context.queryCursor(uri, projection, selection, showErrors = showToasts) { cursor -> + val id = cursor.getIntValue(Calendars._ID) + val displayName = cursor.getStringValue(Calendars.CALENDAR_DISPLAY_NAME) + val accountName = cursor.getStringValue(Calendars.ACCOUNT_NAME) + val accountType = cursor.getStringValue(Calendars.ACCOUNT_TYPE) + val ownerName = cursor.getStringValue(Calendars.OWNER_ACCOUNT) ?: "" + val color = cursor.getIntValue(Calendars.CALENDAR_COLOR) + val accessLevel = cursor.getIntValue(Calendars.CALENDAR_ACCESS_LEVEL) + val calendar = CalDAVCalendar(id, displayName, accountName, accountType, ownerName, color, accessLevel) + calendars.add(calendar) + } + + return calendars + } + + fun updateCalDAVCalendar(eventType: EventType) { + val uri = Calendars.CONTENT_URI + val values = fillCalendarContentValues(eventType) + val newUri = ContentUris.withAppendedId(uri, eventType.caldavCalendarId.toLong()) + try { + context.contentResolver.update(newUri, values, null, null) + } catch (e: IllegalArgumentException) { + } + } + + private fun fillCalendarContentValues(eventType: EventType): ContentValues { + val colorKey = getEventTypeColorKey(eventType) + return ContentValues().apply { + put(Calendars.CALENDAR_COLOR_KEY, colorKey) + put(Calendars.CALENDAR_DISPLAY_NAME, eventType.title) + } + } + + @SuppressLint("MissingPermission") + private fun getEventTypeColorKey(eventType: EventType): Int { + val uri = Colors.CONTENT_URI + val projection = arrayOf(Colors.COLOR_KEY) + val selection = "${Colors.COLOR_TYPE} = ? AND ${Colors.COLOR} = ? AND ${Colors.ACCOUNT_NAME} = ?" + val selectionArgs = arrayOf(Colors.TYPE_CALENDAR.toString(), eventType.color.toString(), eventType.caldavEmail) + + val cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) + cursor?.use { + if (cursor.moveToFirst()) { + return cursor.getStringValue(Colors.COLOR_KEY).toInt() + } + } + + return -1 + } + + @SuppressLint("MissingPermission") + fun getAvailableCalDAVCalendarColors(eventType: EventType): ArrayList { + val colors = SparseIntArray() + val uri = Colors.CONTENT_URI + val projection = arrayOf(Colors.COLOR, Colors.COLOR_KEY) + val selection = "${Colors.COLOR_TYPE} = ? AND ${Colors.ACCOUNT_NAME} = ?" + val selectionArgs = arrayOf(Colors.TYPE_CALENDAR.toString(), eventType.caldavEmail) + + context.queryCursor(uri, projection, selection, selectionArgs) { cursor -> + val colorKey = cursor.getIntValue(Colors.COLOR_KEY) + val color = cursor.getIntValue(Colors.COLOR) + colors.put(colorKey, color) + } + + var sortedColors = ArrayList(colors.size()) + (0 until colors.size()).mapTo(sortedColors) { colors[it] } + if (sortedColors.isNotEmpty()) { + sortedColors = sortedColors.distinct() as ArrayList + } + + return sortedColors + } + + @SuppressLint("MissingPermission") + private fun fetchCalDAVCalendarEvents(calendarId: Int, eventTypeId: Long, showToasts: Boolean) { + val importIdsMap = HashMap() + val fetchedEventIds = ArrayList() + val existingEvents = context.eventsDB.getEventsFromCalDAVCalendar("$CALDAV-$calendarId") + existingEvents.forEach { + importIdsMap[it.importId] = it + } + + val uri = Events.CONTENT_URI + val projection = arrayOf( + Events._ID, + Events.TITLE, + Events.DESCRIPTION, + Events.DTSTART, + Events.DTEND, + Events.DURATION, + Events.EXDATE, + Events.ALL_DAY, + Events.RRULE, + Events.ORIGINAL_ID, + Events.ORIGINAL_INSTANCE_TIME, + Events.EVENT_LOCATION, + Events.EVENT_TIMEZONE, + Events.CALENDAR_TIME_ZONE, + Events.DELETED) + + val selection = "${Events.CALENDAR_ID} = $calendarId" + context.queryCursor(uri, projection, selection, showErrors = showToasts) { cursor -> + val deleted = cursor.getIntValue(Events.DELETED) + if (deleted == 1) { + return@queryCursor + } + + val id = cursor.getLongValue(Events._ID) + val title = cursor.getStringValue(Events.TITLE) ?: "" + val description = cursor.getStringValue(Events.DESCRIPTION) ?: "" + val startTS = cursor.getLongValue(Events.DTSTART) / 1000L + var endTS = cursor.getLongValue(Events.DTEND) / 1000L + val allDay = cursor.getIntValue(Events.ALL_DAY) + val rrule = cursor.getStringValue(Events.RRULE) ?: "" + val location = cursor.getStringValue(Events.EVENT_LOCATION) ?: "" + val originalId = cursor.getStringValue(Events.ORIGINAL_ID) + val originalInstanceTime = cursor.getLongValue(Events.ORIGINAL_INSTANCE_TIME) + val reminders = getCalDAVEventReminders(id) + val attendees = Gson().toJson(getCalDAVEventAttendees(id)) + + if (endTS == 0L) { + val duration = cursor.getStringValue(Events.DURATION) ?: "" + endTS = startTS + Parser().parseDurationSeconds(duration) + } + + val reminder1 = reminders.getOrNull(0) + val reminder2 = reminders.getOrNull(1) + val reminder3 = reminders.getOrNull(2) + val importId = getCalDAVEventImportId(calendarId, id) + val eventTimeZone = cursor.getStringValue(Events.EVENT_TIMEZONE) + ?: cursor.getStringValue(Events.CALENDAR_TIME_ZONE) ?: DateTimeZone.getDefault().id + + val source = "$CALDAV-$calendarId" + val repeatRule = Parser().parseRepeatInterval(rrule, startTS) + val event = Event(null, startTS, endTS, title, location, description, reminder1?.minutes ?: REMINDER_OFF, + reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF, reminder1?.type + ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION, reminder3?.type + ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule, + repeatRule.repeatLimit, ArrayList(), attendees, importId, eventTimeZone, allDay, eventTypeId, source = source) + + if (event.getIsAllDay()) { + event.startTS = Formatter.getShiftedImportTimestamp(event.startTS) + event.endTS = Formatter.getShiftedImportTimestamp(event.endTS) + if (event.endTS > event.startTS) { + event.endTS -= DAY + } + } + + fetchedEventIds.add(importId) + + // if the event is an exception from another events repeat rule, find the original parent event + if (originalInstanceTime != 0L) { + val parentImportId = "$source-$originalId" + val parentEvent = context.eventsDB.getEventWithImportId(parentImportId) + val originalDayCode = Formatter.getDayCodeFromTS(originalInstanceTime / 1000L) + if (parentEvent != null && !parentEvent.repetitionExceptions.contains(originalDayCode)) { + event.parentId = parentEvent.id!! + parentEvent.addRepetitionException(originalDayCode) + eventsHelper.insertEvent(parentEvent, false, false) + + event.parentId = parentEvent.id!! + event.addRepetitionException(originalDayCode) + eventsHelper.insertEvent(event, false, false) + return@queryCursor + } + } + + // some calendars add repeatable event exceptions with using the "exdate" field, not by creating a child event that is an exception + val exdate = cursor.getStringValue(Events.EXDATE) ?: "" + if (exdate.length > 8) { + val lines = exdate.split("\n") + for (line in lines) { + val dates = line.split(",") + dates.forEach { + if (it.endsWith("Z")) { + // convert for example "20190216T230000Z" to "20190217000000" in Slovakia in a weird way + val formatter = DateTimeFormat.forPattern("yyyyMMdd'T'HHmmss'Z'") + val offset = DateTimeZone.getDefault().getOffset(System.currentTimeMillis()) + val dt = formatter.parseDateTime(it).plusMillis(offset) + val daycode = Formatter.getDayCodeFromDateTime(dt) + event.repetitionExceptions.add(daycode) + } else { + var potentialTS = it.substring(0, 8) + if (potentialTS.areDigitsOnly()) { + event.repetitionExceptions.add(potentialTS) + } else if (it.contains(";")) { + potentialTS = it.substringAfter(";").substring(0, 8) + event.repetitionExceptions.add(potentialTS) + } + } + } + } + } + + if (importIdsMap.containsKey(event.importId)) { + val existingEvent = importIdsMap[importId] + val originalEventId = existingEvent!!.id + + existingEvent.apply { + this.id = null + color = 0 + lastUpdated = 0L + repetitionExceptions = ArrayList() + } + + if (existingEvent.hashCode() != event.hashCode() && title.isNotEmpty()) { + event.id = originalEventId + eventsHelper.updateEvent(event, false, false) + } + } else { + if (title.isNotEmpty()) { + importIdsMap[event.importId] = event + eventsHelper.insertEvent(event, false, false) + } + } + } + + val eventIdsToDelete = ArrayList() + importIdsMap.keys.filter { !fetchedEventIds.contains(it) }.forEach { + val caldavEventId = it + existingEvents.forEach { + if (it.importId == caldavEventId) { + eventIdsToDelete.add(it.id!!) + } + } + } + + eventsHelper.deleteEvents(eventIdsToDelete.toMutableList(), false) + } + + @SuppressLint("MissingPermission") + fun insertCalDAVEvent(event: Event) { + val uri = 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) + setupCalDAVEventAttendees(event) + setupCalDAVEventImportId(event) + refreshCalDAVCalendar(event) + } + + fun updateCalDAVEvent(event: Event) { + val uri = 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) + setupCalDAVEventAttendees(event) + setupCalDAVEventImportId(event) + refreshCalDAVCalendar(event) + } + + private fun setupCalDAVEventReminders(event: Event) { + clearEventReminders(event) + event.getReminders().forEach { + val contentValues = ContentValues().apply { + put(Reminders.MINUTES, it.minutes) + put(Reminders.METHOD, if (it.type == REMINDER_EMAIL) Reminders.METHOD_EMAIL else Reminders.METHOD_ALERT) + put(Reminders.EVENT_ID, event.getCalDAVEventId()) + } + + try { + context.contentResolver.insert(Reminders.CONTENT_URI, contentValues) + } catch (e: Exception) { + context.toast(R.string.unknown_error_occurred) + } + } + } + + private fun setupCalDAVEventAttendees(event: Event) { + clearEventAttendees(event) + val attendees = Gson().fromJson>(event.attendees, object : TypeToken>() {}.type) ?: ArrayList() + attendees.forEach { + val contentValues = ContentValues().apply { + put(Attendees.ATTENDEE_NAME, it.name) + put(Attendees.ATTENDEE_EMAIL, it.email) + put(Attendees.ATTENDEE_STATUS, it.status) + put(Attendees.ATTENDEE_RELATIONSHIP, it.relationship) + put(Attendees.EVENT_ID, event.getCalDAVEventId()) + } + + try { + context.contentResolver.insert(Attendees.CONTENT_URI, contentValues) + } catch (e: Exception) { + context.toast(R.string.unknown_error_occurred) + } + } + } + + private fun setupCalDAVEventImportId(event: Event) { + context.eventsDB.updateEventImportIdAndSource(event.importId, "$CALDAV-${event.getCalDAVCalendarId()}", event.id!!) + } + + private fun fillEventContentValues(event: Event): ContentValues { + return ContentValues().apply { + put(Events.CALENDAR_ID, event.getCalDAVCalendarId()) + put(Events.TITLE, event.title) + put(Events.DESCRIPTION, event.description) + put(Events.DTSTART, event.startTS * 1000L) + put(Events.ALL_DAY, if (event.getIsAllDay()) 1 else 0) + put(Events.EVENT_TIMEZONE, event.getTimeZoneString()) + put(Events.EVENT_LOCATION, event.location) + put(Events.STATUS, Events.STATUS_CONFIRMED) + + val repeatRule = Parser().getRepeatCode(event) + if (repeatRule.isEmpty()) { + putNull(Events.RRULE) + } else { + put(Events.RRULE, repeatRule) + } + + if (event.getIsAllDay() && event.endTS >= event.startTS) + event.endTS += DAY + + if (event.repeatInterval > 0) { + put(Events.DURATION, getDurationCode(event)) + putNull(Events.DTEND) + } else { + put(Events.DTEND, event.endTS * 1000L) + putNull(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 clearEventAttendees(event: Event) { + val selection = "${Attendees.EVENT_ID} = ?" + val selectionArgs = arrayOf(event.getCalDAVEventId().toString()) + context.contentResolver.delete(Attendees.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) / 60L) + } + } + + fun deleteCalDAVCalendarEvents(calendarId: Long) { + val eventIds = context.eventsDB.getCalDAVCalendarEvents("$CALDAV-$calendarId").toMutableList() + eventsHelper.deleteEvents(eventIds, false) + } + + fun deleteCalDAVEvent(event: Event) { + val uri = Events.CONTENT_URI + val contentUri = ContentUris.withAppendedId(uri, event.getCalDAVEventId()) + try { + context.contentResolver.delete(contentUri, null, null) + } catch (ignored: Exception) { + } + refreshCalDAVCalendar(event) + } + + fun insertEventRepeatException(event: Event, occurrenceTS: Long) { + val uri = Events.CONTENT_URI + val values = fillEventRepeatExceptionValues(event, occurrenceTS) + try { + context.contentResolver.insert(uri, values) + refreshCalDAVCalendar(event) + } catch (e: Exception) { + context.showErrorToast(e) + } + } + + private fun fillEventRepeatExceptionValues(event: Event, occurrenceTS: Long): ContentValues { + return ContentValues().apply { + put(Events.CALENDAR_ID, event.getCalDAVCalendarId()) + put(Events.DTSTART, occurrenceTS) + put(Events.DTEND, occurrenceTS + (event.endTS - event.startTS)) + put(Events.ORIGINAL_ID, event.getCalDAVEventId()) + put(Events.EVENT_TIMEZONE, TimeZone.getDefault().id.toString()) + put(Events.ORIGINAL_INSTANCE_TIME, occurrenceTS * 1000L) + put(Events.EXDATE, Formatter.getDayCodeFromTS(occurrenceTS)) + } + } + + private fun getCalDAVEventReminders(eventId: Long): List { + val reminders = ArrayList() + val uri = Reminders.CONTENT_URI + val projection = arrayOf( + Reminders.MINUTES, + Reminders.METHOD) + val selection = "${Reminders.EVENT_ID} = $eventId" + + context.queryCursor(uri, projection, selection) { cursor -> + val minutes = cursor.getIntValue(Reminders.MINUTES) + val method = cursor.getIntValue(Reminders.METHOD) + if (method == Reminders.METHOD_ALERT || method == Reminders.METHOD_EMAIL) { + val type = if (method == Reminders.METHOD_EMAIL) REMINDER_EMAIL else REMINDER_NOTIFICATION + val reminder = Reminder(minutes, type) + reminders.add(reminder) + } + } + + return reminders.sortedBy { it.minutes } + } + + private fun getCalDAVEventAttendees(eventId: Long): List { + val attendees = ArrayList() + val uri = Attendees.CONTENT_URI + val projection = arrayOf( + Attendees.ATTENDEE_NAME, + Attendees.ATTENDEE_EMAIL, + Attendees.ATTENDEE_STATUS, + Attendees.ATTENDEE_RELATIONSHIP) + val selection = "${Attendees.EVENT_ID} = $eventId" + context.queryCursor(uri, projection, selection) { cursor -> + val name = cursor.getStringValue(Attendees.ATTENDEE_NAME) ?: "" + val email = cursor.getStringValue(Attendees.ATTENDEE_EMAIL) ?: "" + val status = cursor.getIntValue(Attendees.ATTENDEE_STATUS) + val relationship = cursor.getIntValue(Attendees.ATTENDEE_RELATIONSHIP) + val attendee = Attendee(0, name, email, status, "", false, relationship) + attendees.add(attendee) + } + + return attendees + } + + private fun getCalDAVEventImportId(calendarId: Int, eventId: Long) = "$CALDAV-$calendarId-$eventId" + + private fun refreshCalDAVCalendar(event: Event) = context.refreshCalDAVCalendars(event.getCalDAVCalendarId().toString(), false) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt new file mode 100644 index 000000000..153ba98cd --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Config.kt @@ -0,0 +1,186 @@ +package com.simplemobiletools.calendar.pro.helpers + +import android.content.Context +import android.media.AudioManager +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.scheduleCalDAVSync +import com.simplemobiletools.commons.extensions.getDefaultAlarmTitle +import com.simplemobiletools.commons.extensions.getDefaultAlarmUri +import com.simplemobiletools.commons.helpers.ALARM_SOUND_TYPE_NOTIFICATION +import com.simplemobiletools.commons.helpers.BaseConfig +import com.simplemobiletools.commons.helpers.DAY_MINUTES +import java.util.* + +class Config(context: Context) : BaseConfig(context) { + companion object { + fun newInstance(context: Context) = Config(context) + } + + var showWeekNumbers: Boolean + get() = prefs.getBoolean(WEEK_NUMBERS, false) + set(showWeekNumbers) = prefs.edit().putBoolean(WEEK_NUMBERS, showWeekNumbers).apply() + + var startWeeklyAt: Int + get() = prefs.getInt(START_WEEKLY_AT, 7) + set(startWeeklyAt) = prefs.edit().putInt(START_WEEKLY_AT, startWeeklyAt).apply() + + var vibrateOnReminder: Boolean + get() = prefs.getBoolean(VIBRATE, false) + set(vibrate) = prefs.edit().putBoolean(VIBRATE, vibrate).apply() + + var reminderSoundUri: String + get() = prefs.getString(REMINDER_SOUND_URI, context.getDefaultAlarmUri(ALARM_SOUND_TYPE_NOTIFICATION).toString())!! + set(reminderSoundUri) = prefs.edit().putString(REMINDER_SOUND_URI, reminderSoundUri).apply() + + var reminderSoundTitle: String + get() = prefs.getString(REMINDER_SOUND_TITLE, context.getDefaultAlarmTitle(ALARM_SOUND_TYPE_NOTIFICATION))!! + set(reminderSoundTitle) = prefs.edit().putString(REMINDER_SOUND_TITLE, reminderSoundTitle).apply() + + var lastSoundUri: String + get() = prefs.getString(LAST_SOUND_URI, "")!! + set(lastSoundUri) = prefs.edit().putString(LAST_SOUND_URI, lastSoundUri).apply() + + var lastReminderChannel: Long + get() = prefs.getLong(LAST_REMINDER_CHANNEL_ID, 0L) + set(lastReminderChannel) = prefs.edit().putLong(LAST_REMINDER_CHANNEL_ID, lastReminderChannel).apply() + + var storedView: Int + get() = prefs.getInt(VIEW, MONTHLY_VIEW) + set(view) = prefs.edit().putInt(VIEW, view).apply() + + var lastEventReminderMinutes1: Int + get() = prefs.getInt(LAST_EVENT_REMINDER_MINUTES, 10) + set(lastEventReminderMinutes) = prefs.edit().putInt(LAST_EVENT_REMINDER_MINUTES, lastEventReminderMinutes).apply() + + var lastEventReminderMinutes2: Int + get() = prefs.getInt(LAST_EVENT_REMINDER_MINUTES_2, REMINDER_OFF) + set(lastEventReminderMinutes2) = prefs.edit().putInt(LAST_EVENT_REMINDER_MINUTES_2, lastEventReminderMinutes2).apply() + + var lastEventReminderMinutes3: Int + get() = prefs.getInt(LAST_EVENT_REMINDER_MINUTES_3, REMINDER_OFF) + set(lastEventReminderMinutes3) = prefs.edit().putInt(LAST_EVENT_REMINDER_MINUTES_3, lastEventReminderMinutes3).apply() + + var displayPastEvents: Int + get() = prefs.getInt(DISPLAY_PAST_EVENTS, DAY_MINUTES) + 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 listWidgetViewToOpen: Int + get() = prefs.getInt(LIST_WIDGET_VIEW_TO_OPEN, DAILY_VIEW) + set(viewToOpenFromListWidget) = prefs.edit().putInt(LIST_WIDGET_VIEW_TO_OPEN, viewToOpenFromListWidget).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 lastUsedCaldavCalendarId: Int + get() = prefs.getInt(LAST_USED_CALDAV_CALENDAR, getSyncedCalendarIdsAsList().first().toInt()) + set(calendarId) = prefs.edit().putInt(LAST_USED_CALDAV_CALENDAR, calendarId).apply() + + var lastUsedLocalEventTypeId: Long + get() = prefs.getLong(LAST_USED_LOCAL_EVENT_TYPE_ID, REGULAR_EVENT_TYPE_ID) + set(lastUsedLocalEventTypeId) = prefs.edit().putLong(LAST_USED_LOCAL_EVENT_TYPE_ID, lastUsedLocalEventTypeId).apply() + + var reminderAudioStream: Int + get() = prefs.getInt(REMINDER_AUDIO_STREAM, AudioManager.STREAM_ALARM) + set(reminderAudioStream) = prefs.edit().putInt(REMINDER_AUDIO_STREAM, reminderAudioStream).apply() + + var replaceDescription: Boolean + get() = prefs.getBoolean(REPLACE_DESCRIPTION, false) + set(replaceDescription) = prefs.edit().putBoolean(REPLACE_DESCRIPTION, replaceDescription).apply() + + var showGrid: Boolean + get() = prefs.getBoolean(SHOW_GRID, false) + set(showGrid) = prefs.edit().putBoolean(SHOW_GRID, showGrid).apply() + + var loopReminders: Boolean + get() = prefs.getBoolean(LOOP_REMINDERS, false) + set(loopReminders) = prefs.edit().putBoolean(LOOP_REMINDERS, loopReminders).apply() + + var dimPastEvents: Boolean + get() = prefs.getBoolean(DIM_PAST_EVENTS, true) + set(dimPastEvents) = prefs.edit().putBoolean(DIM_PAST_EVENTS, dimPastEvents).apply() + + fun getSyncedCalendarIdsAsList() = caldavSyncedCalendarIds.split(",").filter { it.trim().isNotEmpty() }.map { Integer.parseInt(it) }.toMutableList() as ArrayList + + fun getDisplayEventTypessAsList() = displayEventTypes.map { it.toLong() }.toMutableList() 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 + } + + var usePreviousEventReminders: Boolean + get() = prefs.getBoolean(USE_PREVIOUS_EVENT_REMINDERS, true) + set(usePreviousEventReminders) = prefs.edit().putBoolean(USE_PREVIOUS_EVENT_REMINDERS, usePreviousEventReminders).apply() + + var defaultReminder1: Int + get() = prefs.getInt(DEFAULT_REMINDER_1, 10) + set(defaultReminder1) = prefs.edit().putInt(DEFAULT_REMINDER_1, defaultReminder1).apply() + + var defaultReminder2: Int + get() = prefs.getInt(DEFAULT_REMINDER_2, REMINDER_OFF) + set(defaultReminder2) = prefs.edit().putInt(DEFAULT_REMINDER_2, defaultReminder2).apply() + + var defaultReminder3: Int + get() = prefs.getInt(DEFAULT_REMINDER_3, REMINDER_OFF) + set(defaultReminder3) = prefs.edit().putInt(DEFAULT_REMINDER_3, defaultReminder3).apply() + + var pullToRefresh: Boolean + get() = prefs.getBoolean(PULL_TO_REFRESH, false) + set(pullToRefresh) = prefs.edit().putBoolean(PULL_TO_REFRESH, pullToRefresh).apply() + + var lastVibrateOnReminder: Boolean + get() = prefs.getBoolean(LAST_VIBRATE_ON_REMINDER, context.config.vibrateOnReminder) + set(lastVibrateOnReminder) = prefs.edit().putBoolean(LAST_VIBRATE_ON_REMINDER, lastVibrateOnReminder).apply() + + var defaultStartTime: Int + get() = prefs.getInt(DEFAULT_START_TIME, -1) + set(defaultStartTime) = prefs.edit().putInt(DEFAULT_START_TIME, defaultStartTime).apply() + + var defaultDuration: Int + get() = prefs.getInt(DEFAULT_DURATION, 0) + set(defaultDuration) = prefs.edit().putInt(DEFAULT_DURATION, defaultDuration).apply() + + var defaultEventTypeId: Long + get() = prefs.getLong(DEFAULT_EVENT_TYPE_ID, -1L) + set(defaultEventTypeId) = prefs.edit().putLong(DEFAULT_EVENT_TYPE_ID, defaultEventTypeId).apply() + + var allowChangingTimeZones: Boolean + get() = prefs.getBoolean(ALLOW_CHANGING_TIME_ZONES, false) + set(allowChangingTimeZones) = prefs.edit().putBoolean(ALLOW_CHANGING_TIME_ZONES, allowChangingTimeZones).apply() + + var lastExportPath: String + get() = prefs.getString(LAST_EXPORT_PATH, "")!! + set(lastExportPath) = prefs.edit().putString(LAST_EXPORT_PATH, lastExportPath).apply() + + var exportPastEvents: Boolean + get() = prefs.getBoolean(EXPORT_PAST_EVENTS, false) + set(exportPastEvents) = prefs.edit().putBoolean(EXPORT_PAST_EVENTS, exportPastEvents).apply() + + var weeklyViewItemHeightMultiplier: Float + get() = prefs.getFloat(WEEKLY_VIEW_ITEM_HEIGHT_MULTIPLIER, 1f) + set(weeklyViewItemHeightMultiplier) = prefs.edit().putFloat(WEEKLY_VIEW_ITEM_HEIGHT_MULTIPLIER, weeklyViewItemHeightMultiplier).apply() +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt new file mode 100644 index 000000000..e63fde821 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -0,0 +1,158 @@ +package com.simplemobiletools.calendar.pro.helpers + +const val LOW_ALPHA = .3f +const val MEDIUM_ALPHA = .6f +const val STORED_LOCALLY_ONLY = 0 + +const val DAY_CODE = "day_code" +const val YEAR_LABEL = "year" +const val EVENT_ID = "event_id" +const val IS_DUPLICATE_INTENT = "is_duplicate_intent" +const val EVENT_OCCURRENCE_TS = "event_occurrence_ts" +const val NEW_EVENT_START_TS = "new_event_start_ts" +const val WEEK_START_TIMESTAMP = "week_start_timestamp" +const val NEW_EVENT_SET_HOUR_DURATION = "new_event_set_hour_duration" +const val WEEK_START_DATE_TIME = "week_start_date_time" +const val CALDAV = "Caldav" +const val VIEW_TO_OPEN = "view_to_open" +const val SHORTCUT_NEW_EVENT = "shortcut_new_event" +const val REGULAR_EVENT_TYPE_ID = 1L +const val TIME_ZONE = "time_zone" +const val CURRENT_TIME_ZONE = "current_time_zone" + +const val MONTHLY_VIEW = 1 +const val YEARLY_VIEW = 2 +const val EVENTS_LIST_VIEW = 3 +const val WEEKLY_VIEW = 4 +const val DAILY_VIEW = 5 +const val LAST_VIEW = 6 + +const val REMINDER_OFF = -1 + +const val ITEM_EVENT = 0 +const val ITEM_EVENT_SIMPLE = 1 +const val ITEM_HEADER = 2 + +const val DAY = 86400 +const val WEEK = 604800 +const val MONTH = 2592001 // exact value not taken into account, Joda is used for adding months and years +const val YEAR = 31536000 + +// Shared Preferences +const val WEEK_NUMBERS = "week_numbers" +const val START_WEEKLY_AT = "start_weekly_at" +const val VIBRATE = "vibrate" +const val REMINDER_SOUND_URI = "reminder_sound_uri" +const val REMINDER_SOUND_TITLE = "reminder_sound_title" +const val VIEW = "view" +const val LAST_EVENT_REMINDER_MINUTES = "reminder_minutes" +const val LAST_EVENT_REMINDER_MINUTES_2 = "reminder_minutes_2" +const val LAST_EVENT_REMINDER_MINUTES_3 = "reminder_minutes_3" +const val DISPLAY_EVENT_TYPES = "display_event_types" +const val LIST_WIDGET_VIEW_TO_OPEN = "list_widget_view_to_open" +const val CALDAV_SYNC = "caldav_sync" +const val CALDAV_SYNCED_CALENDAR_IDS = "caldav_synced_calendar_ids" +const val LAST_USED_CALDAV_CALENDAR = "last_used_caldav_calendar" +const val LAST_USED_LOCAL_EVENT_TYPE_ID = "last_used_local_event_type_id" +const val DISPLAY_PAST_EVENTS = "display_past_events" +const val REPLACE_DESCRIPTION = "replace_description" +const val SHOW_GRID = "show_grid" +const val LOOP_REMINDERS = "loop_reminders" +const val DIM_PAST_EVENTS = "dim_past_events" +const val LAST_SOUND_URI = "last_sound_uri" +const val LAST_REMINDER_CHANNEL_ID = "last_reminder_channel_ID" +const val REMINDER_AUDIO_STREAM = "reminder_audio_stream" +const val USE_PREVIOUS_EVENT_REMINDERS = "use_previous_event_reminders" +const val DEFAULT_REMINDER_1 = "default_reminder_1" +const val DEFAULT_REMINDER_2 = "default_reminder_2" +const val DEFAULT_REMINDER_3 = "default_reminder_3" +const val PULL_TO_REFRESH = "pull_to_refresh" +const val LAST_VIBRATE_ON_REMINDER = "last_vibrate_on_reminder" +const val DEFAULT_START_TIME = "default_start_time" +const val DEFAULT_DURATION = "default_duration" +const val DEFAULT_EVENT_TYPE_ID = "default_event_type_id" +const val ALLOW_CHANGING_TIME_ZONES = "allow_changing_time_zones" +const val LAST_EXPORT_PATH = "last_export_path" +const val EXPORT_PAST_EVENTS = "export_past_events" +const val WEEKLY_VIEW_ITEM_HEIGHT_MULTIPLIER = "weekly_view_item_height_multiplier" + +// repeat_rule for monthly and yearly repetition +const val REPEAT_SAME_DAY = 1 // i.e. 25th every month, or 3rd june (if yearly repetition) +const val REPEAT_ORDER_WEEKDAY_USE_LAST = 2 // i.e. every last sunday. 4th if a month has 4 sundays, 5th if 5 (or last sunday in june, if yearly) +const val REPEAT_LAST_DAY = 3 // i.e. every last day of the month +const val REPEAT_ORDER_WEEKDAY = 4 // i.e. every 4th sunday, even if a month has 4 sundays only (will stay 4th even at months with 5) + +// special event flags +const val FLAG_ALL_DAY = 1 +const val FLAG_IS_PAST_EVENT = 2 + +// constants related to ICS file exporting / importing +const val BEGIN_CALENDAR = "BEGIN:VCALENDAR" +const val END_CALENDAR = "END:VCALENDAR" +const val CALENDAR_PRODID = "PRODID:-//Simple Mobile Tools//NONSGML Event Calendar//EN" +const val CALENDAR_VERSION = "VERSION:2.0" +const val BEGIN_EVENT = "BEGIN:VEVENT" +const val END_EVENT = "END:VEVENT" +const val BEGIN_ALARM = "BEGIN:VALARM" +const val END_ALARM = "END:VALARM" +const val DTSTART = "DTSTART" +const val DTEND = "DTEND" +const val LAST_MODIFIED = "LAST-MODIFIED" +const val DURATION = "DURATION:" +const val SUMMARY = "SUMMARY" +const val DESCRIPTION = "DESCRIPTION:" +const val UID = "UID:" +const val ACTION = "ACTION:" +const val ATTENDEE = "ATTENDEE:" +const val MAILTO = "mailto:" +const val TRIGGER = "TRIGGER:" +const val RRULE = "RRULE:" +const val CATEGORIES = "CATEGORIES:" +const val STATUS = "STATUS:" +const val EXDATE = "EXDATE" +const val BYDAY = "BYDAY" +const val BYMONTHDAY = "BYMONTHDAY" +const val BYMONTH = "BYMONTH" +const val LOCATION = "LOCATION" +const val RECURRENCE_ID = "RECURRENCE-ID" +const val SEQUENCE = "SEQUENCE" + +// this tag isn't a standard ICS tag, but there's no official way of adding a category color in an ics file +const val CATEGORY_COLOR = "CATEGORY_COLOR:" + +const val DISPLAY = "DISPLAY" +const val EMAIL = "EMAIL" +const val FREQ = "FREQ" +const val UNTIL = "UNTIL" +const val COUNT = "COUNT" +const val INTERVAL = "INTERVAL" +const val CONFIRMED = "CONFIRMED" +const val VALUE = "VALUE" +const val DATE = "DATE" + +const val DAILY = "DAILY" +const val WEEKLY = "WEEKLY" +const val MONTHLY = "MONTHLY" +const val YEARLY = "YEARLY" + +const val MO = "MO" +const val TU = "TU" +const val WE = "WE" +const val TH = "TH" +const val FR = "FR" +const val SA = "SA" +const val SU = "SU" + +const val SOURCE_SIMPLE_CALENDAR = "simple-calendar" +const val SOURCE_IMPORTED_ICS = "imported-ics" +const val SOURCE_CONTACT_BIRTHDAY = "contact-birthday" +const val SOURCE_CONTACT_ANNIVERSARY = "contact-anniversary" + +const val DELETE_SELECTED_OCCURRENCE = 0 +const val DELETE_FUTURE_OCCURRENCES = 1 +const val DELETE_ALL_OCCURRENCES = 2 + +const val REMINDER_NOTIFICATION = 0 +const val REMINDER_EMAIL = 1 + +fun getNowSeconds() = System.currentTimeMillis() / 1000L diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Converters.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Converters.kt new file mode 100644 index 000000000..45de469a6 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Converters.kt @@ -0,0 +1,24 @@ +package com.simplemobiletools.calendar.pro.helpers + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +class Converters { + private val gson = Gson() + private val stringType = object : TypeToken>() {}.type + + @TypeConverter + fun jsonToStringList(value: String): ArrayList { + val newValue = if (value.isNotEmpty() && !value.startsWith("[")) { + "[$value]" + } else { + value + } + + return gson.fromJson(newValue, stringType) + } + + @TypeConverter + fun stringListToJson(list: ArrayList) = gson.toJson(list) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt new file mode 100644 index 000000000..fbfd5821f --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt @@ -0,0 +1,426 @@ +package com.simplemobiletools.calendar.pro.helpers + +import android.app.Activity +import android.content.Context +import androidx.collection.LongSparseArray +import com.simplemobiletools.calendar.pro.extensions.* +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.calendar.pro.models.EventType +import com.simplemobiletools.commons.extensions.getChoppedList +import com.simplemobiletools.commons.helpers.ensureBackgroundThread + +class EventsHelper(val context: Context) { + private val config = context.config + private val eventsDB = context.eventsDB + private val eventTypesDB = context.eventTypesDB + + fun getEventTypes(activity: Activity, showWritableOnly: Boolean, callback: (notes: ArrayList) -> Unit) { + ensureBackgroundThread { + var eventTypes = ArrayList() + try { + eventTypes = eventTypesDB.getEventTypes().toMutableList() as ArrayList + } catch (ignored: Exception) { + } + + if (showWritableOnly) { + val caldavCalendars = activity.calDAVHelper.getCalDAVCalendars("", true) + eventTypes = eventTypes.filter { + val eventType = it + it.caldavCalendarId == 0 || caldavCalendars.firstOrNull { it.id == eventType.caldavCalendarId }?.canWrite() == true + }.toMutableList() as ArrayList + } + + activity.runOnUiThread { + callback(eventTypes) + } + } + } + + fun getEventTypesSync() = eventTypesDB.getEventTypes().toMutableList() as ArrayList + + fun insertOrUpdateEventType(activity: Activity, eventType: EventType, callback: ((newEventTypeId: Long) -> Unit)? = null) { + ensureBackgroundThread { + val eventTypeId = insertOrUpdateEventTypeSync(eventType) + activity.runOnUiThread { + callback?.invoke(eventTypeId) + } + } + } + + fun insertOrUpdateEventTypeSync(eventType: EventType): Long { + if (eventType.id != null && eventType.id!! > 0 && eventType.caldavCalendarId != 0) { + context.calDAVHelper.updateCalDAVCalendar(eventType) + } + + val newId = eventTypesDB.insertOrUpdate(eventType) + if (eventType.id == null) { + config.addDisplayEventType(newId.toString()) + } + return newId + } + + fun getEventTypeIdWithTitle(title: String) = eventTypesDB.getEventTypeIdWithTitle(title) ?: -1L + + fun getEventTypeWithCalDAVCalendarId(calendarId: Int) = eventTypesDB.getEventTypeWithCalDAVCalendarId(calendarId) + + fun deleteEventTypes(eventTypes: ArrayList, deleteEvents: Boolean) { + val typesToDelete = eventTypes.asSequence().filter { it.caldavCalendarId == 0 && it.id != REGULAR_EVENT_TYPE_ID }.toMutableList() + val deleteIds = typesToDelete.map { it.id }.toMutableList() + val deletedSet = deleteIds.map { it.toString() }.toHashSet() + config.removeDisplayEventTypes(deletedSet) + + if (deleteIds.isEmpty()) { + return + } + + for (eventTypeId in deleteIds) { + if (deleteEvents) { + deleteEventsWithType(eventTypeId!!) + } else { + eventsDB.resetEventsWithType(eventTypeId!!) + } + } + + eventTypesDB.deleteEventTypes(typesToDelete) + } + + fun insertEvent(event: Event, addToCalDAV: Boolean, showToasts: Boolean, callback: ((id: Long) -> Unit)? = null) { + if (event.startTS > event.endTS) { + callback?.invoke(0) + return + } + + event.id = eventsDB.insertOrUpdate(event) + + context.updateWidgets() + context.scheduleNextEventReminder(event, showToasts) + + if (addToCalDAV && event.source != SOURCE_SIMPLE_CALENDAR && config.caldavSync) { + context.calDAVHelper.insertCalDAVEvent(event) + } + + callback?.invoke(event.id!!) + } + + fun insertEvents(events: ArrayList, addToCalDAV: Boolean) { + try { + for (event in events) { + if (event.startTS > event.endTS) { + continue + } + + event.id = eventsDB.insertOrUpdate(event) + + context.scheduleNextEventReminder(event, false) + if (addToCalDAV && event.source != SOURCE_SIMPLE_CALENDAR && event.source != SOURCE_IMPORTED_ICS && config.caldavSync) { + context.calDAVHelper.insertCalDAVEvent(event) + } + } + } finally { + context.updateWidgets() + } + } + + fun updateEvent(event: Event, updateAtCalDAV: Boolean, showToasts: Boolean, callback: (() -> Unit)? = null) { + eventsDB.insertOrUpdate(event) + + context.updateWidgets() + context.scheduleNextEventReminder(event, showToasts) + if (updateAtCalDAV && event.source != SOURCE_SIMPLE_CALENDAR && config.caldavSync) { + context.calDAVHelper.updateCalDAVEvent(event) + } + callback?.invoke() + } + + fun deleteAllEvents() { + ensureBackgroundThread { + val eventIds = eventsDB.getEventIds().toMutableList() + deleteEvents(eventIds, true) + } + } + + fun deleteEvent(id: Long, deleteFromCalDAV: Boolean) = deleteEvents(arrayListOf(id), deleteFromCalDAV) + + fun deleteEvents(ids: MutableList, deleteFromCalDAV: Boolean) { + if (ids.isEmpty()) { + return + } + + ids.getChoppedList().forEach { + val eventsWithImportId = eventsDB.getEventsByIdsWithImportIds(it) + eventsDB.deleteEvents(it) + + it.forEach { + context.cancelNotification(it) + } + + if (deleteFromCalDAV && config.caldavSync) { + eventsWithImportId.forEach { + context.calDAVHelper.deleteCalDAVEvent(it) + } + } + + deleteChildEvents(it, deleteFromCalDAV) + context.updateWidgets() + } + } + + private fun deleteChildEvents(ids: MutableList, deleteFromCalDAV: Boolean) { + val childIds = eventsDB.getEventIdsWithParentIds(ids).toMutableList() + if (childIds.isNotEmpty()) { + deleteEvents(childIds, deleteFromCalDAV) + } + } + + private fun deleteEventsWithType(eventTypeId: Long) { + val eventIds = eventsDB.getEventIdsByEventType(eventTypeId).toMutableList() + deleteEvents(eventIds, true) + } + + fun addEventRepeatLimit(eventId: Long, limitTS: Long) { + val time = Formatter.getDateTimeFromTS(limitTS) + eventsDB.updateEventRepetitionLimit(limitTS - time.hourOfDay, eventId) + + if (config.caldavSync) { + val event = eventsDB.getEventWithId(eventId) + if (event?.getCalDAVCalendarId() != 0) { + context.calDAVHelper.updateCalDAVEvent(event!!) + } + } + } + + fun doEventTypesContainEvents(eventTypeIds: ArrayList, callback: (contain: Boolean) -> Unit) { + ensureBackgroundThread { + val eventIds = eventsDB.getEventIdsByEventType(eventTypeIds) + callback(eventIds.isNotEmpty()) + } + } + + fun getEventsWithSearchQuery(text: String, activity: Activity, callback: (searchedText: String, events: List) -> Unit) { + ensureBackgroundThread { + val searchQuery = "%$text%" + val events = eventsDB.getEventsForSearch(searchQuery) + val displayEventTypes = config.displayEventTypes + val filteredEvents = events.filter { displayEventTypes.contains(it.eventType.toString()) } + + val eventTypeColors = LongSparseArray() + eventTypesDB.getEventTypes().forEach { + eventTypeColors.put(it.id!!, it.color) + } + + filteredEvents.forEach { + it.updateIsPastEvent() + it.color = eventTypeColors.get(it.eventType) ?: config.primaryColor + } + + activity.runOnUiThread { + callback(text, filteredEvents) + } + } + } + + fun addEventRepetitionException(parentEventId: Long, occurrenceTS: Long, addToCalDAV: Boolean) { + ensureBackgroundThread { + val parentEvent = eventsDB.getEventWithId(parentEventId) ?: return@ensureBackgroundThread + var repetitionExceptions = parentEvent.repetitionExceptions + repetitionExceptions.add(Formatter.getDayCodeFromTS(occurrenceTS)) + repetitionExceptions = repetitionExceptions.distinct().toMutableList() as ArrayList + + eventsDB.updateEventRepetitionExceptions(repetitionExceptions.toString(), parentEventId) + context.scheduleNextEventReminder(parentEvent, false) + + if (addToCalDAV && config.caldavSync) { + context.calDAVHelper.insertEventRepeatException(parentEvent, occurrenceTS) + } + } + } + + fun getEvents(fromTS: Long, toTS: Long, eventId: Long = -1L, applyTypeFilter: Boolean = true, callback: (events: ArrayList) -> Unit) { + ensureBackgroundThread { + getEventsSync(fromTS, toTS, eventId, applyTypeFilter, callback) + } + } + + fun getEventsSync(fromTS: Long, toTS: Long, eventId: Long = -1L, applyTypeFilter: Boolean, callback: (events: ArrayList) -> Unit) { + var events = if (applyTypeFilter) { + val displayEventTypes = context.config.displayEventTypes + if (displayEventTypes.isEmpty()) { + callback(ArrayList()) + return + } else { + eventsDB.getOneTimeEventsFromToWithTypes(toTS, fromTS, context.config.getDisplayEventTypessAsList()).toMutableList() as ArrayList + } + } else { + if (eventId == -1L) { + eventsDB.getOneTimeEventsFromTo(toTS, fromTS).toMutableList() as ArrayList + } else { + eventsDB.getOneTimeEventFromToWithId(eventId, toTS, fromTS).toMutableList() as ArrayList + } + } + + events.addAll(getRepeatableEventsFor(fromTS, toTS, eventId, applyTypeFilter)) + + events = events + .asSequence() + .distinct() + .filterNot { it.repetitionExceptions.contains(Formatter.getDayCodeFromTS(it.startTS)) } + .toMutableList() as ArrayList + + val eventTypeColors = LongSparseArray() + context.eventTypesDB.getEventTypes().forEach { + eventTypeColors.put(it.id!!, it.color) + } + + events.forEach { + it.updateIsPastEvent() + it.color = eventTypeColors.get(it.eventType) ?: config.primaryColor + } + + callback(events) + } + + fun getRepeatableEventsFor(fromTS: Long, toTS: Long, eventId: Long = -1L, applyTypeFilter: Boolean = false): List { + val events = if (applyTypeFilter) { + val displayEventTypes = context.config.displayEventTypes + if (displayEventTypes.isEmpty()) { + return ArrayList() + } else { + eventsDB.getRepeatableEventsFromToWithTypes(toTS, context.config.getDisplayEventTypessAsList()).toMutableList() as ArrayList + } + } else { + if (eventId == -1L) { + eventsDB.getRepeatableEventsFromToWithTypes(toTS).toMutableList() as ArrayList + } else { + eventsDB.getRepeatableEventFromToWithId(eventId, toTS).toMutableList() as ArrayList + } + } + + val startTimes = LongSparseArray() + val newEvents = ArrayList() + 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 getEventsRepeatingXTimes(fromTS: Long, toTS: Long, startTimes: LongSparseArray, 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 (event.isOnProperWeek(startTimes)) { + if (event.endTS >= fromTS) { + event.copy().apply { + updateIsPastEvent() + color = event.color + events.add(this) + } + } + event.repeatLimit++ + } + } + } else { + if (event.endTS >= fromTS) { + event.copy().apply { + updateIsPastEvent() + color = event.color + events.add(this) + } + } else if (event.getIsAllDay()) { + val dayCode = Formatter.getDayCodeFromTS(fromTS) + val endDayCode = Formatter.getDayCodeFromTS(event.endTS) + if (dayCode == endDayCode) { + event.copy().apply { + updateIsPastEvent() + color = event.color + events.add(this) + } + } + } + event.repeatLimit++ + } + event.addIntervalTime(original) + } + return events + } + + private fun getEventsRepeatingTillDateOrForever(fromTS: Long, toTS: Long, startTimes: LongSparseArray, event: Event): ArrayList { + val original = event.copy() + val events = ArrayList() + while (event.startTS <= toTS && (event.repeatLimit == 0L || event.repeatLimit >= event.startTS)) { + if (event.endTS >= fromTS) { + if (event.repeatInterval.isXWeeklyRepetition()) { + if (event.startTS.isTsOnProperDay(event)) { + if (event.isOnProperWeek(startTimes)) { + event.copy().apply { + updateIsPastEvent() + color = event.color + events.add(this) + } + } + } + } else { + event.copy().apply { + updateIsPastEvent() + color = event.color + events.add(this) + } + } + } + + if (event.getIsAllDay()) { + if (event.repeatInterval.isXWeeklyRepetition()) { + if (event.endTS >= toTS && event.startTS.isTsOnProperDay(event)) { + if (event.isOnProperWeek(startTimes)) { + event.copy().apply { + updateIsPastEvent() + color = event.color + events.add(this) + } + } + } + } else { + val dayCode = Formatter.getDayCodeFromTS(fromTS) + val endDayCode = Formatter.getDayCodeFromTS(event.endTS) + if (dayCode == endDayCode) { + event.copy().apply { + updateIsPastEvent() + color = event.color + events.add(this) + } + } + } + } + event.addIntervalTime(original) + } + return events + } + + fun getRunningEvents(): List { + val ts = getNowSeconds() + val events = eventsDB.getOneTimeEventsFromTo(ts, ts).toMutableList() as ArrayList + events.addAll(getRepeatableEventsFor(ts, ts)) + return events + } + + fun getEventsToExport(eventTypes: ArrayList): ArrayList { + val currTS = getNowSeconds() + var events = ArrayList() + if (config.exportPastEvents) { + events.addAll(eventsDB.getAllEventsWithTypes(eventTypes)) + } else { + events.addAll(eventsDB.getOneTimeFutureEventsWithTypes(currTS, eventTypes)) + events.addAll(eventsDB.getRepeatableFutureEventsWithTypes(currTS, eventTypes)) + } + + events = events.distinctBy { it.id } as ArrayList + return events + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Formatter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Formatter.kt similarity index 61% rename from app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Formatter.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Formatter.kt index 0e2651cf4..e24ffcf82 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Formatter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Formatter.kt @@ -1,30 +1,33 @@ -package com.simplemobiletools.calendar.helpers +package com.simplemobiletools.calendar.pro.helpers import android.content.Context -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.extensions.seconds +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.seconds import org.joda.time.DateTime import org.joda.time.DateTimeZone +import org.joda.time.LocalDate import org.joda.time.format.DateTimeFormat object Formatter { - val DAYCODE_PATTERN = "YYYYMMdd" - val YEAR_PATTERN = "YYYY" - val TIME_PATTERN = "HHmmss" - private val DAY_PATTERN = "d" - private val DAY_OF_WEEK_PATTERN = "EEE" - private val PATTERN_TIME_12 = "hh:mm a" - private val PATTERN_TIME_24 = "HH:mm" + const val DAYCODE_PATTERN = "YYYYMMdd" + const val YEAR_PATTERN = "YYYY" + const val TIME_PATTERN = "HHmmss" + private const val MONTH_PATTERN = "MMM" + private const val DAY_PATTERN = "d" + private const val DAY_OF_WEEK_PATTERN = "EEE" + private const val LONGEST_PATTERN = "MMMM d YYYY (EEEE)" + private const val PATTERN_TIME_12 = "hh:mm a" + private const val PATTERN_TIME_24 = "HH:mm" - private val PATTERN_HOURS_12 = "h a" - private val PATTERN_HOURS_24 = "HH" + private const val PATTERN_HOURS_12 = "h a" + private const val PATTERN_HOURS_24 = "HH" fun getDateFromCode(context: Context, dayCode: String, shortMonth: Boolean = false): String { val dateTime = getDateTimeFromCode(dayCode) val day = dateTime.toString(DAY_PATTERN) val year = dateTime.toString(YEAR_PATTERN) - val monthIndex = Integer.valueOf(dayCode.substring(4, 6))!! + val monthIndex = Integer.valueOf(dayCode.substring(4, 6)) var month = getMonthName(context, monthIndex) if (shortMonth) month = month.substring(0, Math.min(month.length, 3)) @@ -44,6 +47,8 @@ object Formatter { date } + fun getLongestDate(ts: Long) = getDateTimeFromTS(ts).toString(LONGEST_PATTERN) + fun getDate(context: Context, dateTime: DateTime, addDayOfWeek: Boolean = true) = getDayTitle(context, getDayCodeFromDateTime(dateTime), addDayOfWeek) fun getFullDate(context: Context, dateTime: DateTime): String { @@ -54,7 +59,11 @@ object Formatter { return "$month $day $year" } - fun getTodayCode() = Formatter.getDayCodeFromTS((System.currentTimeMillis() / 1000).toInt()) + fun getTodayCode() = getDayCodeFromTS(getNowSeconds()) + + fun getTodayDayNumber() = getDateTimeFromTS(getNowSeconds()).toString(DAY_PATTERN) + + fun getCurrentMonthShort() = getDateTimeFromTS(getNowSeconds()).toString(MONTH_PATTERN) fun getHours(context: Context, dateTime: DateTime) = dateTime.toString(getHourPattern(context)) @@ -62,9 +71,9 @@ object Formatter { fun getDateTimeFromCode(dayCode: String) = DateTimeFormat.forPattern(DAYCODE_PATTERN).withZone(DateTimeZone.UTC).parseDateTime(dayCode) - fun getLocalDateTimeFromCode(dayCode: String) = DateTimeFormat.forPattern(DAYCODE_PATTERN).withZone(DateTimeZone.getDefault()).parseDateTime(dayCode) + fun getLocalDateTimeFromCode(dayCode: String) = DateTimeFormat.forPattern(DAYCODE_PATTERN).withZone(DateTimeZone.getDefault()).parseLocalDate(dayCode).toDateTimeAtStartOfDay() - fun getTimeFromTS(context: Context, ts: Int) = getTime(context, getDateTimeFromTS(ts)) + fun getTimeFromTS(context: Context, ts: Long) = getTime(context, getDateTimeFromTS(ts)) fun getDayStartTS(dayCode: String) = getLocalDateTimeFromCode(dayCode).seconds() @@ -72,27 +81,32 @@ object Formatter { fun getDayCodeFromDateTime(dateTime: DateTime) = dateTime.toString(DAYCODE_PATTERN) - fun getDateTimeFromTS(ts: Int) = DateTime(ts * 1000L, DateTimeZone.getDefault()) + fun getDateFromTS(ts: Long) = LocalDate(ts * 1000L, DateTimeZone.getDefault()) + + fun getDateTimeFromTS(ts: Long) = DateTime(ts * 1000L, DateTimeZone.getDefault()) + + fun getUTCDateTimeFromTS(ts: Long) = DateTime(ts * 1000L, DateTimeZone.UTC) // use manually translated month names, as DateFormat and Joda have issues with a lot of languages fun getMonthName(context: Context, id: Int) = context.resources.getStringArray(R.array.months)[id - 1] - fun getYear(dateTime: DateTime) = dateTime.toString(YEAR_PATTERN) + fun getHourPattern(context: Context) = if (context.config.use24HourFormat) PATTERN_HOURS_24 else PATTERN_HOURS_12 - fun getHourPattern(context: Context) = if (context.config.use24hourFormat) PATTERN_HOURS_24 else PATTERN_HOURS_12 - - fun getTimePattern(context: Context) = if (context.config.use24hourFormat) PATTERN_TIME_24 else PATTERN_TIME_12 + fun getTimePattern(context: Context) = if (context.config.use24HourFormat) PATTERN_TIME_24 else PATTERN_TIME_12 fun getExportedTime(ts: Long): String { val dateTime = DateTime(ts, DateTimeZone.UTC) return "${dateTime.toString(DAYCODE_PATTERN)}T${dateTime.toString(TIME_PATTERN)}Z" } - fun getDayCodeFromTS(ts: Int): String { + fun getDayCodeFromTS(ts: Long): String { val daycode = getDateTimeFromTS(ts).toString(DAYCODE_PATTERN) - return if (daycode.isNotEmpty()) + return if (daycode.isNotEmpty()) { daycode - else + } else { "0" + } } + + fun getShiftedImportTimestamp(ts: Long) = getUTCDateTimeFromTS(ts).withTime(13, 0, 0, 0).withZoneRetainFields(DateTimeZone.getDefault()).seconds() } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/IcsExporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt similarity index 55% rename from app/src/main/kotlin/com/simplemobiletools/calendar/helpers/IcsExporter.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt index c2dba46f6..868bd1567 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/IcsExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt @@ -1,13 +1,17 @@ -package com.simplemobiletools.calendar.helpers +package com.simplemobiletools.calendar.pro.helpers -import com.simplemobiletools.calendar.extensions.dbHelper -import com.simplemobiletools.calendar.extensions.writeLn -import com.simplemobiletools.calendar.helpers.IcsExporter.ExportResult.* -import com.simplemobiletools.calendar.models.Event +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.calDAVHelper +import com.simplemobiletools.calendar.pro.extensions.eventTypesDB +import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.* +import com.simplemobiletools.calendar.pro.models.CalDAVCalendar +import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.commons.activities.BaseSimpleActivity -import com.simplemobiletools.commons.extensions.getFileOutputStream +import com.simplemobiletools.commons.extensions.toast +import com.simplemobiletools.commons.extensions.writeLn +import com.simplemobiletools.commons.helpers.ensureBackgroundThread import java.io.BufferedWriter -import java.io.File +import java.io.OutputStream class IcsExporter { enum class ExportResult { @@ -16,15 +20,21 @@ class IcsExporter { private var eventsExported = 0 private var eventsFailed = 0 + private var calendars = ArrayList() - fun exportEvents(activity: BaseSimpleActivity, file: File, events: ArrayList, callback: (result: ExportResult) -> Unit) { - activity.getFileOutputStream(file) { - if (it == null) { - callback(EXPORT_FAIL) - return@getFileOutputStream + fun exportEvents(activity: BaseSimpleActivity, outputStream: OutputStream?, events: ArrayList, showExportingToast: Boolean, callback: (result: ExportResult) -> Unit) { + if (outputStream == null) { + callback(EXPORT_FAIL) + return + } + + ensureBackgroundThread { + calendars = activity.calDAVHelper.getCalDAVCalendars("", false) + if (showExportingToast) { + activity.toast(R.string.exporting) } - it.bufferedWriter().use { out -> + outputStream.bufferedWriter().use { out -> out.writeLn(BEGIN_CALENDAR) out.writeLn(CALENDAR_PRODID) out.writeLn(CALENDAR_VERSION) @@ -33,10 +43,10 @@ class IcsExporter { event.title.replace("\n", "\\n").let { if (it.isNotEmpty()) out.writeLn("$SUMMARY:$it") } event.description.replace("\n", "\\n").let { if (it.isNotEmpty()) out.writeLn("$DESCRIPTION$it") } event.importId.let { if (it.isNotEmpty()) out.writeLn("$UID$it") } - event.eventType.let { out.writeLn("$CATEGORY_COLOR${activity.dbHelper.getEventType(it)?.color}") } - event.eventType.let { out.writeLn("$CATEGORIES${activity.dbHelper.getEventType(it)?.title}") } + event.eventType.let { out.writeLn("$CATEGORY_COLOR${activity.eventTypesDB.getEventTypeWithId(it)?.color}") } + event.eventType.let { out.writeLn("$CATEGORIES${activity.eventTypesDB.getEventTypeWithId(it)?.title}") } event.lastUpdated.let { out.writeLn("$LAST_MODIFIED:${Formatter.getExportedTime(it)}") } - event.location.let { if (it.isNotEmpty()) out.writeLn("$LOCATION$it") } + event.location.let { if (it.isNotEmpty()) out.writeLn("$LOCATION:$it") } if (event.getIsAllDay()) { out.writeLn("$DTSTART;$VALUE=$DATE:${Formatter.getDayCodeFromTS(event.startTS)}") @@ -67,25 +77,28 @@ class IcsExporter { } private fun fillReminders(event: Event, out: BufferedWriter) { - checkReminder(event.reminder1Minutes, out) - checkReminder(event.reminder2Minutes, out) - checkReminder(event.reminder3Minutes, out) - } - - private fun checkReminder(minutes: Int, out: BufferedWriter) { - if (minutes != -1) { + event.getReminders().forEach { + val reminder = it out.apply { writeLn(BEGIN_ALARM) - writeLn("$ACTION$DISPLAY") - writeLn("$TRIGGER-${Parser().getDurationCode(minutes)}") + if (reminder.type == REMINDER_NOTIFICATION) { + writeLn("$ACTION$DISPLAY") + } else { + writeLn("$ACTION$EMAIL") + val attendee = calendars.firstOrNull { it.id == event.getCalDAVCalendarId() }?.accountName + if (attendee != null) { + writeLn("$ATTENDEE$MAILTO$attendee") + } + } + writeLn("$TRIGGER-${Parser().getDurationCode(reminder.minutes.toLong())}") writeLn(END_ALARM) } } } private fun fillIgnoredOccurrences(event: Event, out: BufferedWriter) { - event.ignoreEventOccurrences.forEach { - out.writeLn("$EXDATE:$it}") + event.repetitionExceptions.forEach { + out.writeLn("$EXDATE:$it") } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt new file mode 100644 index 000000000..d2fe2bdde --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt @@ -0,0 +1,333 @@ +package com.simplemobiletools.calendar.pro.helpers + +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.calendar.pro.extensions.eventsDB +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.helpers.IcsImporter.ImportResult.* +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.calendar.pro.models.EventType +import com.simplemobiletools.calendar.pro.models.Reminder +import com.simplemobiletools.commons.extensions.areDigitsOnly +import com.simplemobiletools.commons.extensions.showErrorToast +import org.joda.time.DateTimeZone +import java.io.File + +class IcsImporter(val activity: SimpleActivity) { + enum class ImportResult { + IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW + } + + private var curStart = -1L + private var curEnd = -1L + private var curTitle = "" + private var curLocation = "" + private var curDescription = "" + private var curImportId = "" + private var curRecurrenceDayCode = "" + private var curRrule = "" + private var curFlags = 0 + private var curReminderMinutes = ArrayList() + private var curReminderActions = ArrayList() + private var curRepeatExceptions = ArrayList() + private var curRepeatInterval = 0 + private var curRepeatLimit = 0L + private var curRepeatRule = 0 + private var curEventTypeId = REGULAR_EVENT_TYPE_ID + private var curLastModified = 0L + private var curCategoryColor = -2 + private var isNotificationDescription = false + private var isProperReminderAction = false + private var isDescription = false + private var isSequence = false + private var isParsingEvent = false + private var curReminderTriggerMinutes = REMINDER_OFF + private var curReminderTriggerAction = REMINDER_NOTIFICATION + private val eventsHelper = activity.eventsHelper + + private var eventsImported = 0 + private var eventsFailed = 0 + private var eventsAlreadyExist = 0 + + fun importEvents(path: String, defaultEventTypeId: Long, calDAVCalendarId: Int, overrideFileEventTypes: Boolean): ImportResult { + try { + val eventTypes = eventsHelper.getEventTypesSync() + val existingEvents = activity.eventsDB.getEventsWithImportIds().toMutableList() as ArrayList + val eventsToInsert = ArrayList() + 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 (isDescription) { + if (line.startsWith('\t')) { + curDescription += line.trimStart('\t').replace("\\n", "\n") + } else { + isDescription = false + } + } + + if (line == BEGIN_EVENT) { + resetValues() + curEventTypeId = defaultEventTypeId + isParsingEvent = true + } else if (line.startsWith(DTSTART)) { + if (isParsingEvent) { + curStart = getTimestamp(line.substring(DTSTART.length)) + + if (curRrule != "") { + parseRepeatRule() + } + } + } 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").replace("\\,", ",") + } else if (line.startsWith(DESCRIPTION) && !isNotificationDescription) { + curDescription = line.substring(DESCRIPTION.length).replace("\\n", "\n").replace("\\,", ",") + isDescription = true + } else if (line.startsWith(UID)) { + curImportId = line.substring(UID.length).trim() + } else if (line.startsWith(RRULE)) { + curRrule = line.substring(RRULE.length) + // some RRULEs need to know the events start datetime. If it's yet unknown, postpone RRULE parsing + if (curStart != -1L) { + parseRepeatRule() + } + } else if (line.startsWith(ACTION)) { + isNotificationDescription = true + val action = line.substring(ACTION.length) + isProperReminderAction = action == DISPLAY || action == EMAIL + if (isProperReminderAction) { + curReminderTriggerAction = if (action == DISPLAY) REMINDER_NOTIFICATION else REMINDER_EMAIL + } + } 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) && !overrideFileEventTypes) { + val categories = line.substring(CATEGORIES.length) + tryAddCategories(categories) + } 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(Formatter.getDayCodeFromTS(getTimestamp(value))) + } else if (line.startsWith(LOCATION)) { + curLocation = getLocation(line.substring(LOCATION.length).replace("\\,", ",")) + } else if (line.startsWith(RECURRENCE_ID)) { + val timestamp = getTimestamp(line.substring(RECURRENCE_ID.length)) + curRecurrenceDayCode = Formatter.getDayCodeFromTS(timestamp) + } else if (line.startsWith(SEQUENCE)) { + isSequence = true + } else if (line == END_ALARM) { + if (isProperReminderAction && curReminderTriggerMinutes != REMINDER_OFF) { + curReminderMinutes.add(curReminderTriggerMinutes) + curReminderActions.add(curReminderTriggerAction) + } + } else if (line == END_EVENT) { + isParsingEvent = false + if (curStart != -1L && curEnd == -1L) { + curEnd = curStart + } + + if (curTitle.isEmpty() || curStart == -1L) { + continue + } + + // repeating event exceptions can have the same import id as their parents, so pick the latest event to update + val eventToUpdate = existingEvents.filter { curImportId.isNotEmpty() && curImportId == it.importId }.sortedByDescending { it.lastUpdated }.firstOrNull() + if (eventToUpdate != null && eventToUpdate.lastUpdated >= curLastModified) { + eventsAlreadyExist++ + continue + } + + var reminders = arrayListOf( + Reminder(curReminderMinutes.getOrElse(0) { REMINDER_OFF }, curReminderActions.getOrElse(0) { REMINDER_NOTIFICATION }), + Reminder(curReminderMinutes.getOrElse(1) { REMINDER_OFF }, curReminderActions.getOrElse(1) { REMINDER_NOTIFICATION }), + Reminder(curReminderMinutes.getOrElse(2) { REMINDER_OFF }, curReminderActions.getOrElse(2) { REMINDER_NOTIFICATION }) + ) + + reminders = reminders.sortedBy { it.minutes }.sortedBy { it.minutes == REMINDER_OFF }.toMutableList() as ArrayList + + val eventType = eventTypes.firstOrNull { it.id == curEventTypeId } + val source = if (calDAVCalendarId == 0 || eventType?.isSyncedEventType() == false) SOURCE_IMPORTED_ICS else "$CALDAV-$calDAVCalendarId" + val event = Event(null, curStart, curEnd, curTitle, curLocation, curDescription, reminders[0].minutes, + reminders[1].minutes, reminders[2].minutes, reminders[0].type, reminders[1].type, reminders[2].type, curRepeatInterval, curRepeatRule, + curRepeatLimit, curRepeatExceptions, "", curImportId, DateTimeZone.getDefault().id, curFlags, curEventTypeId, 0, curLastModified, source) + + if (event.getIsAllDay() && curEnd > curStart) { + event.endTS -= DAY + } + + if (event.importId.isEmpty()) { + event.importId = event.hashCode().toString() + if (existingEvents.map { it.importId }.contains(event.importId)) { + eventsAlreadyExist++ + continue + } + } + + if (eventToUpdate == null) { + // if an event belongs to a sequence insert it immediately, to avoid some glitches with linked events + if (isSequence) { + if (curRecurrenceDayCode.isEmpty()) { + eventsHelper.insertEvent(event, true, false) + } else { + // if an event contains the RECURRENCE-ID field, it is an exception to a recurring event, so update its parent too + val parentEvent = activity.eventsDB.getEventWithImportId(event.importId) + if (parentEvent != null && !parentEvent.repetitionExceptions.contains(curRecurrenceDayCode)) { + parentEvent.addRepetitionException(curRecurrenceDayCode) + eventsHelper.insertEvent(parentEvent, true, false) + + event.parentId = parentEvent.id!! + eventsToInsert.add(event) + } + } + } else { + eventsToInsert.add(event) + } + } else { + event.id = eventToUpdate.id + eventsHelper.updateEvent(event, true, false) + } + eventsImported++ + resetValues() + } + prevLine = line + } + } + + eventsHelper.insertEvents(eventsToInsert, true) + } catch (e: Exception) { + activity.showErrorToast(e) + eventsFailed++ + } + + return when { + eventsImported == 0 -> { + if (eventsAlreadyExist > 0) { + IMPORT_NOTHING_NEW + } else { + IMPORT_FAIL + } + } + eventsFailed > 0 -> IMPORT_PARTIAL + else -> IMPORT_OK + } + } + + private fun getTimestamp(fullString: String): Long { + return try { + if (fullString.startsWith(';')) { + val value = fullString.substring(fullString.lastIndexOf(':') + 1).replace(" ", "") + if (value.isEmpty()) { + return 0 + } else 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) + eventsFailed++ + -1 + } + } + + private fun getLocation(fullString: String): String { + return if (fullString.startsWith(":")) { + fullString.trimStart(':') + } else { + fullString.substringAfter(':').trim() + } + } + + private fun tryAddCategories(categories: String) { + val eventTypeTitle = if (categories.contains(",")) { + categories.split(",")[0] + } else { + categories + } + + val eventId = eventsHelper.getEventTypeIdWithTitle(eventTypeTitle) + curEventTypeId = if (eventId == -1L) { + val newTypeColor = if (curCategoryColor == -2) activity.resources.getColor(R.color.color_primary) else curCategoryColor + val eventType = EventType(null, eventTypeTitle, newTypeColor) + eventsHelper.insertOrUpdateEventTypeSync(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, 180)) + } + } + + private fun parseRepeatRule() { + val repeatRule = Parser().parseRepeatInterval(curRrule, curStart) + curRepeatRule = repeatRule.repeatRule + curRepeatInterval = repeatRule.repeatInterval + curRepeatLimit = repeatRule.repeatLimit + } + + private fun resetValues() { + curStart = -1L + curEnd = -1L + curTitle = "" + curLocation = "" + curDescription = "" + curImportId = "" + curRecurrenceDayCode = "" + curRrule = "" + curFlags = 0 + curReminderMinutes = ArrayList() + curReminderActions = ArrayList() + curRepeatExceptions = ArrayList() + curRepeatInterval = 0 + curRepeatLimit = 0L + curRepeatRule = 0 + curEventTypeId = REGULAR_EVENT_TYPE_ID + curLastModified = 0L + curCategoryColor = -2 + isNotificationDescription = false + isProperReminderAction = false + isSequence = false + isParsingEvent = false + curReminderTriggerMinutes = REMINDER_OFF + curReminderTriggerAction = REMINDER_NOTIFICATION + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MonthlyCalendarImpl.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MonthlyCalendarImpl.kt new file mode 100644 index 000000000..bb5f68c7f --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MonthlyCalendarImpl.kt @@ -0,0 +1,131 @@ +package com.simplemobiletools.calendar.pro.helpers + +import android.content.Context +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.extensions.seconds +import com.simplemobiletools.calendar.pro.interfaces.MonthlyCalendar +import com.simplemobiletools.calendar.pro.models.DayMonthly +import com.simplemobiletools.calendar.pro.models.Event +import org.joda.time.DateTime +import java.util.* +import kotlin.collections.ArrayList + +class MonthlyCalendarImpl(val callback: MonthlyCalendar, val context: Context) { + private val DAYS_CNT = 42 + private val YEAR_PATTERN = "YYYY" + + private val mToday: String = DateTime().toString(Formatter.DAYCODE_PATTERN) + private var mEvents = ArrayList() + + lateinit var mTargetDate: DateTime + + fun updateMonthlyCalendar(targetDate: DateTime) { + mTargetDate = targetDate + val startTS = mTargetDate.minusDays(7).seconds() + val endTS = mTargetDate.plusDays(43).seconds() + context.eventsHelper.getEvents(startTS, endTS) { + gotEvents(it) + } + } + + 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 (!context.config.isSundayFirst) + firstDayIndex -= 1 + val prevMonthDays = mTargetDate.minusMonths(1).dayOfMonth().maximumValue + + var isThisMonth = false + var isToday: Boolean + var value = prevMonthDays - firstDayIndex + 1 + var curDay = 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 = isToday(curDay, value) + + val newDay = curDay.withDayOfMonth(value) + val dayCode = Formatter.getDayCodeFromDateTime(newDay) + val day = DayMonthly(value, isThisMonth, isToday, dayCode, newDay.weekOfWeekyear, ArrayList(), i) + days.add(day) + value++ + } + + if (markDaysWithEvents) { + markDaysWithEvents(days) + } else { + callback.updateMonthlyCalendar(context, monthName, days, false, mTargetDate) + } + } + + // it works more often than not, dont touch + private fun markDaysWithEvents(days: ArrayList) { + 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() + currDayEvents.add(it) + dayEvents[dayCode] = currDayEvents + + while (Formatter.getDayCodeFromDateTime(currDay) != endCode) { + currDay = currDay.plusDays(1) + dayCode = Formatter.getDayCodeFromDateTime(currDay) + currDayEvents = dayEvents[dayCode] ?: ArrayList() + currDayEvents.add(it) + dayEvents[dayCode] = currDayEvents + } + } + + days.filter { dayEvents.keys.contains(it.code) }.forEach { + it.dayEvents = dayEvents[it.code]!! + } + callback.updateMonthlyCalendar(context, monthName, days, true, mTargetDate) + } + + private fun isToday(targetDate: DateTime, curDayInMonth: Int): Boolean { + val targetMonthDays = targetDate.dayOfMonth().maximumValue + return targetDate.withDayOfMonth(Math.min(curDayInMonth, targetMonthDays)).toString(Formatter.DAYCODE_PATTERN) == mToday + } + + private val monthName: String + get() { + var month = Formatter.getMonthName(context, mTargetDate.monthOfYear) + val targetYear = mTargetDate.toString(YEAR_PATTERN) + if (targetYear != DateTime().toString(YEAR_PATTERN)) { + month += " $targetYear" + } + return month + } + + private fun gotEvents(events: ArrayList) { + mEvents = events + getDays(true) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyTimeZones.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyTimeZones.kt new file mode 100644 index 000000000..925ef12a6 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyTimeZones.kt @@ -0,0 +1,474 @@ +package com.simplemobiletools.calendar.pro.helpers + +import com.simplemobiletools.calendar.pro.models.MyTimeZone + +// timezones fetched from https://www.joda.org/joda-time/timezones.html +fun getAllTimeZones() = arrayListOf( + MyTimeZone("GMT-12", "Etc/GMT+12"), + MyTimeZone("GMT-11", "Etc/GMT+11"), + MyTimeZone("GMT-11", "Pacific/Midway"), + MyTimeZone("GMT-11", "Pacific/Niue"), + MyTimeZone("GMT-11", "Pacific/Pago_Pago"), + MyTimeZone("GMT-10", "America/Adak"), + MyTimeZone("GMT-10", "Etc/GMT+10"), + MyTimeZone("GMT-10", "HST"), + MyTimeZone("GMT-10", "Pacific/Honolulu"), + MyTimeZone("GMT-10", "Pacific/Rarotonga"), + MyTimeZone("GMT-10", "Pacific/Tahiti"), + MyTimeZone("GMT-9:30", "Pacific/Marquesas"), + MyTimeZone("GMT-9", "America/Anchorage"), + MyTimeZone("GMT-9", "America/Juneau"), + MyTimeZone("GMT-9", "America/Metlakatla"), + MyTimeZone("GMT-9", "America/Nome"), + MyTimeZone("GMT-9", "America/Sitka"), + MyTimeZone("GMT-9", "America/Yakutat"), + MyTimeZone("GMT-9", "Etc/GMT+9"), + MyTimeZone("GMT-9", "Pacific/Gambier"), + MyTimeZone("GMT-8", "America/Dawson"), + MyTimeZone("GMT-8", "America/Los_Angeles"), + MyTimeZone("GMT-8", "America/Tijuana"), + MyTimeZone("GMT-8", "America/Vancouver"), + MyTimeZone("GMT-8", "America/Whitehorse"), + MyTimeZone("GMT-8", "Etc/GMT+8"), + MyTimeZone("GMT-8", "PST8PDT"), + MyTimeZone("GMT-8", "Pacific/Pitcairn"), + MyTimeZone("GMT-7", "America/Boise"), + MyTimeZone("GMT-7", "America/Cambridge_Bay"), + MyTimeZone("GMT-7", "America/Chihuahua"), + MyTimeZone("GMT-7", "America/Creston"), + MyTimeZone("GMT-7", "America/Dawson_Creek"), + MyTimeZone("GMT-7", "America/Denver"), + MyTimeZone("GMT-7", "America/Edmonton"), + MyTimeZone("GMT-7", "America/Fort_Nelson"), + MyTimeZone("GMT-7", "America/Hermosillo"), + MyTimeZone("GMT-7", "America/Inuvik"), + MyTimeZone("GMT-7", "America/Mazatlan"), + MyTimeZone("GMT-7", "America/Ojinaga"), + MyTimeZone("GMT-7", "America/Phoenix"), + MyTimeZone("GMT-7", "America/Yellowknife"), + MyTimeZone("GMT-7", "Etc/GMT+7"), + MyTimeZone("GMT-7", "MST"), + MyTimeZone("GMT-7", "MST7MDT"), + MyTimeZone("GMT-6", "America/Bahia_Banderas"), + MyTimeZone("GMT-6", "America/Belize"), + MyTimeZone("GMT-6", "America/Chicago"), + MyTimeZone("GMT-6", "America/Costa_Rica"), + MyTimeZone("GMT-6", "America/El_Salvador"), + MyTimeZone("GMT-6", "America/Guatemala"), + MyTimeZone("GMT-6", "America/Indiana/Knox"), + MyTimeZone("GMT-6", "America/Indiana/Tell_City"), + MyTimeZone("GMT-6", "America/Managua"), + MyTimeZone("GMT-6", "America/Matamoros"), + MyTimeZone("GMT-6", "America/Menominee"), + MyTimeZone("GMT-6", "America/Merida"), + MyTimeZone("GMT-6", "America/Mexico_City"), + MyTimeZone("GMT-6", "America/Monterrey"), + MyTimeZone("GMT-6", "America/North_Dakota/Beulah"), + MyTimeZone("GMT-6", "America/North_Dakota/Center"), + MyTimeZone("GMT-6", "America/North_Dakota/New_Salem"), + MyTimeZone("GMT-6", "America/Rainy_River"), + MyTimeZone("GMT-6", "America/Rankin_Inlet"), + MyTimeZone("GMT-6", "America/Regina"), + MyTimeZone("GMT-6", "America/Resolute"), + MyTimeZone("GMT-6", "America/Swift_Current"), + MyTimeZone("GMT-6", "America/Tegucigalpa"), + MyTimeZone("GMT-6", "America/Winnipeg"), + MyTimeZone("GMT-6", "CST6CDT"), + MyTimeZone("GMT-6", "Etc/GMT+6"), + MyTimeZone("GMT-6", "Pacific/Easter"), + MyTimeZone("GMT-6", "Pacific/Galapagos"), + MyTimeZone("GMT-5", "America/Atikokan"), + MyTimeZone("GMT-5", "America/Bogota"), + MyTimeZone("GMT-5", "America/Cancun"), + MyTimeZone("GMT-5", "America/Cayman"), + MyTimeZone("GMT-5", "America/Detroit"), + MyTimeZone("GMT-5", "America/Eirunepe"), + MyTimeZone("GMT-5", "America/Grand_Turk"), + MyTimeZone("GMT-5", "America/Guayaquil"), + MyTimeZone("GMT-5", "America/Havana"), + MyTimeZone("GMT-5", "America/Indiana/Indianapolis"), + MyTimeZone("GMT-5", "America/Indiana/Marengo"), + MyTimeZone("GMT-5", "America/Indiana/Petersburg"), + MyTimeZone("GMT-5", "America/Indiana/Vevay"), + MyTimeZone("GMT-5", "America/Indiana/Vincennes"), + MyTimeZone("GMT-5", "America/Indiana/Winamac"), + MyTimeZone("GMT-5", "America/Iqaluit"), + MyTimeZone("GMT-5", "America/Jamaica"), + MyTimeZone("GMT-5", "America/Kentucky/Louisville"), + MyTimeZone("GMT-5", "America/Kentucky/Monticello"), + MyTimeZone("GMT-5", "America/Lima"), + MyTimeZone("GMT-5", "America/Nassau"), + MyTimeZone("GMT-5", "America/New_York"), + MyTimeZone("GMT-5", "America/Nipigon"), + MyTimeZone("GMT-5", "America/Panama"), + MyTimeZone("GMT-5", "America/Pangnirtung"), + MyTimeZone("GMT-5", "America/Port-au-Prince"), + MyTimeZone("GMT-5", "America/Rio_Branco"), + MyTimeZone("GMT-5", "America/Thunder_Bay"), + MyTimeZone("GMT-5", "America/Toronto"), + MyTimeZone("GMT-5", "EST"), + MyTimeZone("GMT-5", "EST5EDT"), + MyTimeZone("GMT-5", "Etc/GMT+5"), + MyTimeZone("GMT-4", "America/Anguilla"), + MyTimeZone("GMT-4", "America/Antigua"), + MyTimeZone("GMT-4", "America/Aruba"), + MyTimeZone("GMT-4", "America/Asuncion"), + MyTimeZone("GMT-4", "America/Barbados"), + MyTimeZone("GMT-4", "America/Blanc-Sablon"), + MyTimeZone("GMT-4", "America/Boa_Vista"), + MyTimeZone("GMT-4", "America/Campo_Grande"), + MyTimeZone("GMT-4", "America/Caracas"), + MyTimeZone("GMT-4", "America/Cuiaba"), + MyTimeZone("GMT-4", "America/Curacao"), + MyTimeZone("GMT-4", "America/Dominica"), + MyTimeZone("GMT-4", "America/Glace_Bay"), + MyTimeZone("GMT-4", "America/Goose_Bay"), + MyTimeZone("GMT-4", "America/Grenada"), + MyTimeZone("GMT-4", "America/Guadeloupe"), + MyTimeZone("GMT-4", "America/Guyana"), + MyTimeZone("GMT-4", "America/Halifax"), + MyTimeZone("GMT-4", "America/Kralendijk"), + MyTimeZone("GMT-4", "America/La_Paz"), + MyTimeZone("GMT-4", "America/Lower_Princes"), + MyTimeZone("GMT-4", "America/Manaus"), + MyTimeZone("GMT-4", "America/Marigot"), + MyTimeZone("GMT-4", "America/Martinique"), + MyTimeZone("GMT-4", "America/Moncton"), + MyTimeZone("GMT-4", "America/Montserrat"), + MyTimeZone("GMT-4", "America/Port_of_Spain"), + MyTimeZone("GMT-4", "America/Porto_Velho"), + MyTimeZone("GMT-4", "America/Puerto_Rico"), + MyTimeZone("GMT-4", "America/Santiago"), + MyTimeZone("GMT-4", "America/Santo_Domingo"), + MyTimeZone("GMT-4", "America/St_Barthelemy"), + MyTimeZone("GMT-4", "America/St_Kitts"), + MyTimeZone("GMT-4", "America/St_Lucia"), + MyTimeZone("GMT-4", "America/St_Thomas"), + MyTimeZone("GMT-4", "America/St_Vincent"), + MyTimeZone("GMT-4", "America/Thule"), + MyTimeZone("GMT-4", "America/Tortola"), + MyTimeZone("GMT-4", "Atlantic/Bermuda"), + MyTimeZone("GMT-4", "Etc/GMT+4"), + MyTimeZone("GMT-3:30", "America/St_Johns"), + MyTimeZone("GMT-3", "America/Araguaina"), + MyTimeZone("GMT-3", "America/Argentina/Buenos_Aires"), + MyTimeZone("GMT-3", "America/Argentina/Catamarca"), + MyTimeZone("GMT-3", "America/Argentina/Cordoba"), + MyTimeZone("GMT-3", "America/Argentina/Jujuy"), + MyTimeZone("GMT-3", "America/Argentina/La_Rioja"), + MyTimeZone("GMT-3", "America/Argentina/Mendoza"), + MyTimeZone("GMT-3", "America/Argentina/Rio_Gallegos"), + MyTimeZone("GMT-3", "America/Argentina/Salta"), + MyTimeZone("GMT-3", "America/Argentina/San_Juan"), + MyTimeZone("GMT-3", "America/Argentina/San_Luis"), + MyTimeZone("GMT-3", "America/Argentina/Tucuman"), + MyTimeZone("GMT-3", "America/Argentina/Ushuaia"), + MyTimeZone("GMT-3", "America/Bahia"), + MyTimeZone("GMT-3", "America/Belem"), + MyTimeZone("GMT-3", "America/Cayenne"), + MyTimeZone("GMT-3", "America/Fortaleza"), + MyTimeZone("GMT-3", "America/Godthab"), + MyTimeZone("GMT-3", "America/Maceio"), + MyTimeZone("GMT-3", "America/Miquelon"), + MyTimeZone("GMT-3", "America/Montevideo"), + MyTimeZone("GMT-3", "America/Paramaribo"), + MyTimeZone("GMT-3", "America/Punta_Arenas"), + MyTimeZone("GMT-3", "America/Recife"), + MyTimeZone("GMT-3", "America/Santarem"), + MyTimeZone("GMT-3", "America/Sao_Paulo"), + MyTimeZone("GMT-3", "Antarctica/Palmer"), + MyTimeZone("GMT-3", "Antarctica/Rothera"), + MyTimeZone("GMT-3", "Atlantic/Stanley"), + MyTimeZone("GMT-3", "Etc/GMT+3"), + MyTimeZone("GMT-2", "America/Noronha"), + MyTimeZone("GMT-2", "Atlantic/South_Georgia"), + MyTimeZone("GMT-2", "Etc/GMT+2"), + MyTimeZone("GMT-1", "America/Scoresbysund"), + MyTimeZone("GMT-1", "Atlantic/Azores"), + MyTimeZone("GMT-1", "Atlantic/Cape_Verde"), + MyTimeZone("GMT-1", "Etc/GMT+1"), + MyTimeZone("GMT", "Africa/Abidjan"), + MyTimeZone("GMT", "Africa/Accra"), + MyTimeZone("GMT", "Africa/Bamako"), + MyTimeZone("GMT", "Africa/Banjul"), + MyTimeZone("GMT", "Africa/Bissau"), + MyTimeZone("GMT", "Africa/Casablanca"), + MyTimeZone("GMT", "Africa/Conakry"), + MyTimeZone("GMT", "Africa/Dakar"), + MyTimeZone("GMT", "Africa/El_Aaiun"), + MyTimeZone("GMT", "Africa/Freetown"), + MyTimeZone("GMT", "Africa/Lome"), + MyTimeZone("GMT", "Africa/Monrovia"), + MyTimeZone("GMT", "Africa/Nouakchott"), + MyTimeZone("GMT", "Africa/Ouagadougou"), + MyTimeZone("GMT", "America/Danmarkshavn"), + MyTimeZone("GMT", "Antarctica/Troll"), + MyTimeZone("GMT", "Atlantic/Canary"), + MyTimeZone("GMT", "Atlantic/Faroe"), + MyTimeZone("GMT", "Atlantic/Madeira"), + MyTimeZone("GMT", "Atlantic/Reykjavik"), + MyTimeZone("GMT", "Atlantic/St_Helena"), + MyTimeZone("GMT", "Etc/GMT"), + MyTimeZone("GMT", "Etc/UCT"), + MyTimeZone("GMT", "Etc/UTC"), + MyTimeZone("GMT", "Europe/Dublin"), + MyTimeZone("GMT", "Europe/Guernsey"), + MyTimeZone("GMT", "Europe/Isle_of_Man"), + MyTimeZone("GMT", "Europe/Jersey"), + MyTimeZone("GMT", "Europe/Lisbon"), + MyTimeZone("GMT", "Europe/London"), + MyTimeZone("GMT", "UTC"), + MyTimeZone("GMT", "WET"), + MyTimeZone("GMT+1", "Africa/Algiers"), + MyTimeZone("GMT+1", "Africa/Bangui"), + MyTimeZone("GMT+1", "Africa/Brazzaville"), + MyTimeZone("GMT+1", "Africa/Ceuta"), + MyTimeZone("GMT+1", "Africa/Douala"), + MyTimeZone("GMT+1", "Africa/Kinshasa"), + MyTimeZone("GMT+1", "Africa/Lagos"), + MyTimeZone("GMT+1", "Africa/Libreville"), + MyTimeZone("GMT+1", "Africa/Luanda"), + MyTimeZone("GMT+1", "Africa/Malabo"), + MyTimeZone("GMT+1", "Africa/Ndjamena"), + MyTimeZone("GMT+1", "Africa/Niamey"), + MyTimeZone("GMT+1", "Africa/Porto-Novo"), + MyTimeZone("GMT+1", "Africa/Sao_Tome"), + MyTimeZone("GMT+1", "Africa/Tunis"), + MyTimeZone("GMT+2", "Africa/Windhoek"), + MyTimeZone("GMT+1", "Arctic/Longyearbyen"), + MyTimeZone("GMT+1", "CET"), + MyTimeZone("GMT+1", "Etc/GMT-1"), + MyTimeZone("GMT+1", "Europe/Amsterdam"), + MyTimeZone("GMT+1", "Europe/Andorra"), + MyTimeZone("GMT+1", "Europe/Belgrade"), + MyTimeZone("GMT+1", "Europe/Berlin"), + MyTimeZone("GMT+1", "Europe/Bratislava"), + MyTimeZone("GMT+1", "Europe/Brussels"), + MyTimeZone("GMT+1", "Europe/Budapest"), + MyTimeZone("GMT+1", "Europe/Busingen"), + MyTimeZone("GMT+1", "Europe/Copenhagen"), + MyTimeZone("GMT+1", "Europe/Gibraltar"), + MyTimeZone("GMT+1", "Europe/Ljubljana"), + MyTimeZone("GMT+1", "Europe/Luxembourg"), + MyTimeZone("GMT+1", "Europe/Madrid"), + MyTimeZone("GMT+1", "Europe/Malta"), + MyTimeZone("GMT+1", "Europe/Monaco"), + MyTimeZone("GMT+1", "Europe/Oslo"), + MyTimeZone("GMT+1", "Europe/Paris"), + MyTimeZone("GMT+1", "Europe/Podgorica"), + MyTimeZone("GMT+1", "Europe/Prague"), + MyTimeZone("GMT+1", "Europe/Rome"), + MyTimeZone("GMT+1", "Europe/San_Marino"), + MyTimeZone("GMT+1", "Europe/Sarajevo"), + MyTimeZone("GMT+1", "Europe/Skopje"), + MyTimeZone("GMT+1", "Europe/Stockholm"), + MyTimeZone("GMT+1", "Europe/Tirane"), + MyTimeZone("GMT+1", "Europe/Vaduz"), + MyTimeZone("GMT+1", "Europe/Vatican"), + MyTimeZone("GMT+1", "Europe/Vienna"), + MyTimeZone("GMT+1", "Europe/Warsaw"), + MyTimeZone("GMT+1", "Europe/Zagreb"), + MyTimeZone("GMT+1", "Europe/Zurich"), + MyTimeZone("GMT+1", "MET"), + MyTimeZone("GMT+2", "Africa/Blantyre"), + MyTimeZone("GMT+2", "Africa/Bujumbura"), + MyTimeZone("GMT+2", "Africa/Cairo"), + MyTimeZone("GMT+2", "Africa/Gaborone"), + MyTimeZone("GMT+2", "Africa/Harare"), + MyTimeZone("GMT+2", "Africa/Johannesburg"), + MyTimeZone("GMT+2", "Africa/Khartoum"), + MyTimeZone("GMT+2", "Africa/Kigali"), + MyTimeZone("GMT+2", "Africa/Lubumbashi"), + MyTimeZone("GMT+2", "Africa/Lusaka"), + MyTimeZone("GMT+2", "Africa/Maputo"), + MyTimeZone("GMT+2", "Africa/Maseru"), + MyTimeZone("GMT+2", "Africa/Mbabane"), + MyTimeZone("GMT+2", "Africa/Tripoli"), + MyTimeZone("GMT+2", "Asia/Amman"), + MyTimeZone("GMT+2", "Asia/Beirut"), + MyTimeZone("GMT+2", "Asia/Damascus"), + MyTimeZone("GMT+2", "Asia/Famagusta"), + MyTimeZone("GMT+2", "Asia/Gaza"), + MyTimeZone("GMT+2", "Asia/Hebron"), + MyTimeZone("GMT+2", "Asia/Jerusalem"), + MyTimeZone("GMT+2", "Asia/Nicosia"), + MyTimeZone("GMT+2", "EET"), + MyTimeZone("GMT+2", "Etc/GMT-2"), + MyTimeZone("GMT+2", "Europe/Athens"), + MyTimeZone("GMT+2", "Europe/Bucharest"), + MyTimeZone("GMT+2", "Europe/Chisinau"), + MyTimeZone("GMT+2", "Europe/Helsinki"), + MyTimeZone("GMT+2", "Europe/Kaliningrad"), + MyTimeZone("GMT+2", "Europe/Kiev"), + MyTimeZone("GMT+2", "Europe/Mariehamn"), + MyTimeZone("GMT+2", "Europe/Nicosia"), + MyTimeZone("GMT+2", "Europe/Riga"), + MyTimeZone("GMT+2", "Europe/Sofia"), + MyTimeZone("GMT+2", "Europe/Tallinn"), + MyTimeZone("GMT+2", "Europe/Uzhgorod"), + MyTimeZone("GMT+2", "Europe/Vilnius"), + MyTimeZone("GMT+2", "Europe/Zaporozhye"), + MyTimeZone("GMT+3", "Africa/Addis_Ababa"), + MyTimeZone("GMT+3", "Africa/Asmara"), + MyTimeZone("GMT+3", "Africa/Dar_es_Salaam"), + MyTimeZone("GMT+3", "Africa/Djibouti"), + MyTimeZone("GMT+3", "Africa/Juba"), + MyTimeZone("GMT+3", "Africa/Kampala"), + MyTimeZone("GMT+3", "Africa/Mogadishu"), + MyTimeZone("GMT+3", "Africa/Nairobi"), + MyTimeZone("GMT+3", "Antarctica/Syowa"), + MyTimeZone("GMT+3", "Asia/Aden"), + MyTimeZone("GMT+3", "Asia/Baghdad"), + MyTimeZone("GMT+3", "Asia/Bahrain"), + MyTimeZone("GMT+3", "Asia/Istanbul"), + MyTimeZone("GMT+3", "Asia/Kuwait"), + MyTimeZone("GMT+3", "Asia/Qatar"), + MyTimeZone("GMT+3", "Asia/Riyadh"), + MyTimeZone("GMT+3", "Etc/GMT-3"), + MyTimeZone("GMT+3", "Europe/Istanbul"), + MyTimeZone("GMT+3", "Europe/Kirov"), + MyTimeZone("GMT+3", "Europe/Minsk"), + MyTimeZone("GMT+3", "Europe/Moscow"), + MyTimeZone("GMT+3", "Europe/Simferopol"), + MyTimeZone("GMT+3", "Indian/Antananarivo"), + MyTimeZone("GMT+3", "Indian/Comoro"), + MyTimeZone("GMT+3", "Indian/Mayotte"), + MyTimeZone("GMT+3:30", "Asia/Tehran"), + MyTimeZone("GMT+4", "Asia/Baku"), + MyTimeZone("GMT+4", "Asia/Dubai"), + MyTimeZone("GMT+4", "Asia/Muscat"), + MyTimeZone("GMT+4", "Asia/Tbilisi"), + MyTimeZone("GMT+4", "Asia/Yerevan"), + MyTimeZone("GMT+4", "Etc/GMT-4"), + MyTimeZone("GMT+4", "Europe/Astrakhan"), + MyTimeZone("GMT+4", "Europe/Samara"), + MyTimeZone("GMT+4", "Europe/Saratov"), + MyTimeZone("GMT+4", "Europe/Ulyanovsk"), + MyTimeZone("GMT+4", "Europe/Volgograd"), + MyTimeZone("GMT+4", "Indian/Mahe"), + MyTimeZone("GMT+4", "Indian/Mauritius"), + MyTimeZone("GMT+4", "Indian/Reunion"), + MyTimeZone("GMT+4:30", "Asia/Kabul"), + MyTimeZone("GMT+5", "Antarctica/Mawson"), + MyTimeZone("GMT+5", "Asia/Aqtau"), + MyTimeZone("GMT+5", "Asia/Aqtobe"), + MyTimeZone("GMT+5", "Asia/Ashgabat"), + MyTimeZone("GMT+5", "Asia/Atyrau"), + MyTimeZone("GMT+5", "Asia/Dushanbe"), + MyTimeZone("GMT+5", "Asia/Karachi"), + MyTimeZone("GMT+5", "Asia/Oral"), + MyTimeZone("GMT+5", "Asia/Samarkand"), + MyTimeZone("GMT+5", "Asia/Tashkent"), + MyTimeZone("GMT+5", "Asia/Yekaterinburg"), + MyTimeZone("GMT+5", "Etc/GMT-5"), + MyTimeZone("GMT+5", "Indian/Kerguelen"), + MyTimeZone("GMT+5", "Indian/Maldives"), + MyTimeZone("GMT+5:30", "Asia/Colombo"), + MyTimeZone("GMT+5:30", "Asia/Kolkata"), + MyTimeZone("GMT+5:45", "Asia/Kathmandu"), + MyTimeZone("GMT+6", "Antarctica/Vostok"), + MyTimeZone("GMT+6", "Asia/Almaty"), + MyTimeZone("GMT+6", "Asia/Bishkek"), + MyTimeZone("GMT+6", "Asia/Dhaka"), + MyTimeZone("GMT+6", "Asia/Omsk"), + MyTimeZone("GMT+6", "Asia/Qyzylorda"), + MyTimeZone("GMT+6", "Asia/Thimphu"), + MyTimeZone("GMT+6", "Asia/Urumqi"), + MyTimeZone("GMT+6", "Etc/GMT-6"), + MyTimeZone("GMT+6", "Indian/Chagos"), + MyTimeZone("GMT+6:30", "Asia/Yangon"), + MyTimeZone("GMT+6:30", "Indian/Cocos"), + MyTimeZone("GMT+7", "Antarctica/Davis"), + MyTimeZone("GMT+7", "Asia/Bangkok"), + MyTimeZone("GMT+7", "Asia/Barnaul"), + MyTimeZone("GMT+7", "Asia/Ho_Chi_Minh"), + MyTimeZone("GMT+7", "Asia/Hovd"), + MyTimeZone("GMT+7", "Asia/Jakarta"), + MyTimeZone("GMT+7", "Asia/Krasnoyarsk"), + MyTimeZone("GMT+7", "Asia/Novokuznetsk"), + MyTimeZone("GMT+7", "Asia/Novosibirsk"), + MyTimeZone("GMT+7", "Asia/Phnom_Penh"), + MyTimeZone("GMT+7", "Asia/Pontianak"), + MyTimeZone("GMT+7", "Asia/Tomsk"), + MyTimeZone("GMT+7", "Asia/Vientiane"), + MyTimeZone("GMT+7", "Etc/GMT-7"), + MyTimeZone("GMT+7", "Indian/Christmas"), + MyTimeZone("GMT+8", "Antarctica/Casey"), + MyTimeZone("GMT+8", "Asia/Brunei"), + MyTimeZone("GMT+8", "Asia/Choibalsan"), + MyTimeZone("GMT+8", "Asia/Hong_Kong"), + MyTimeZone("GMT+8", "Asia/Irkutsk"), + MyTimeZone("GMT+8", "Asia/Kuala_Lumpur"), + MyTimeZone("GMT+8", "Asia/Kuching"), + MyTimeZone("GMT+8", "Asia/Macau"), + MyTimeZone("GMT+8", "Asia/Makassar"), + MyTimeZone("GMT+8", "Asia/Manila"), + MyTimeZone("GMT+8", "Asia/Shanghai"), + MyTimeZone("GMT+8", "Asia/Singapore"), + MyTimeZone("GMT+8", "Asia/Taipei"), + MyTimeZone("GMT+8", "Asia/Ulaanbaatar"), + MyTimeZone("GMT+8", "Australia/Perth"), + MyTimeZone("GMT+8", "Etc/GMT-8"), + MyTimeZone("GMT+8:45", "Australia/Eucla"), + MyTimeZone("GMT+9", "Asia/Chita"), + MyTimeZone("GMT+9", "Asia/Dili"), + MyTimeZone("GMT+9", "Asia/Jayapura"), + MyTimeZone("GMT+9", "Asia/Khandyga"), + MyTimeZone("GMT+9", "Asia/Pyongyang"), + MyTimeZone("GMT+9", "Asia/Seoul"), + MyTimeZone("GMT+9", "Asia/Tokyo"), + MyTimeZone("GMT+9", "Asia/Yakutsk"), + MyTimeZone("GMT+9", "Etc/GMT-9"), + MyTimeZone("GMT+9", "Pacific/Palau"), + MyTimeZone("GMT+9:30", "Australia/Adelaide"), + MyTimeZone("GMT+9:30", "Australia/Broken_Hill"), + MyTimeZone("GMT+9:30", "Australia/Darwin"), + MyTimeZone("GMT+10", "Antarctica/DumontDUrville"), + MyTimeZone("GMT+10", "Asia/Ust-Nera"), + MyTimeZone("GMT+10", "Asia/Vladivostok"), + MyTimeZone("GMT+10", "Australia/Brisbane"), + MyTimeZone("GMT+10", "Australia/Currie"), + MyTimeZone("GMT+10", "Australia/Hobart"), + MyTimeZone("GMT+10", "Australia/Lindeman"), + MyTimeZone("GMT+10", "Australia/Melbourne"), + MyTimeZone("GMT+10", "Australia/Sydney"), + MyTimeZone("GMT+10", "Etc/GMT-10"), + MyTimeZone("GMT+10", "Pacific/Chuuk"), + MyTimeZone("GMT+10", "Pacific/Guam"), + MyTimeZone("GMT+10", "Pacific/Port_Moresby"), + MyTimeZone("GMT+10", "Pacific/Saipan"), + MyTimeZone("GMT+10:30", "Australia/Lord_Howe"), + MyTimeZone("GMT+11", "Antarctica/Macquarie"), + MyTimeZone("GMT+11", "Asia/Magadan"), + MyTimeZone("GMT+11", "Asia/Sakhalin"), + MyTimeZone("GMT+11", "Asia/Srednekolymsk"), + MyTimeZone("GMT+11", "Etc/GMT-11"), + MyTimeZone("GMT+11", "Pacific/Bougainville"), + MyTimeZone("GMT+11", "Pacific/Efate"), + MyTimeZone("GMT+11", "Pacific/Guadalcanal"), + MyTimeZone("GMT+11", "Pacific/Kosrae"), + MyTimeZone("GMT+11", "Pacific/Norfolk"), + MyTimeZone("GMT+11", "Pacific/Noumea"), + MyTimeZone("GMT+11", "Pacific/Pohnpei"), + MyTimeZone("GMT+12", "Antarctica/McMurdo"), + MyTimeZone("GMT+12", "Asia/Anadyr"), + MyTimeZone("GMT+12", "Asia/Kamchatka"), + MyTimeZone("GMT+12", "Etc/GMT-12"), + MyTimeZone("GMT+12", "Pacific/Auckland"), + MyTimeZone("GMT+12", "Pacific/Fiji"), + MyTimeZone("GMT+12", "Pacific/Funafuti"), + MyTimeZone("GMT+12", "Pacific/Kwajalein"), + MyTimeZone("GMT+12", "Pacific/Majuro"), + MyTimeZone("GMT+12", "Pacific/Nauru"), + MyTimeZone("GMT+12", "Pacific/Tarawa"), + MyTimeZone("GMT+12", "Pacific/Wake"), + MyTimeZone("GMT+12", "Pacific/Wallis"), + MyTimeZone("GMT+12:45", "Pacific/Chatham"), + MyTimeZone("GMT+13", "Etc/GMT-13"), + MyTimeZone("GMT+13", "Pacific/Apia"), + MyTimeZone("GMT+13", "Pacific/Enderbury"), + MyTimeZone("GMT+13", "Pacific/Fakaofo"), + MyTimeZone("GMT+13", "Pacific/Tongatapu"), + MyTimeZone("GMT+14", "Etc/GMT-14"), + MyTimeZone("GMT+14", "Pacific/Kiritimati") +) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetDateProvider.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetDateProvider.kt new file mode 100644 index 000000000..95cbd7f80 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetDateProvider.kt @@ -0,0 +1,46 @@ +package com.simplemobiletools.calendar.pro.helpers + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.widget.RemoteViews +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SplashActivity +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.commons.extensions.applyColorFilter +import com.simplemobiletools.commons.extensions.getLaunchIntent +import com.simplemobiletools.commons.extensions.setText + +class MyWidgetDateProvider : AppWidgetProvider() { + private val OPEN_APP_INTENT_ID = 1 + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + appWidgetManager.getAppWidgetIds(getComponentName(context)).forEach { + RemoteViews(context.packageName, R.layout.widget_date).apply { + applyColorFilter(R.id.widget_date_background, context.config.widgetBgColor) + setText(R.id.widget_date_label, Formatter.getTodayDayNumber()) + setText(R.id.widget_month_label, Formatter.getCurrentMonthShort()) + + setTextColor(R.id.widget_date_label, context.config.widgetTextColor) + setTextColor(R.id.widget_month_label, context.config.widgetTextColor) + + setupAppOpenIntent(context, this) + appWidgetManager.updateAppWidget(it, this) + } + + appWidgetManager.notifyAppWidgetViewDataChanged(it, R.id.widget_date_holder) + } + } + + private fun getComponentName(context: Context) = ComponentName(context, MyWidgetDateProvider::class.java) + + private fun setupAppOpenIntent(context: Context, views: RemoteViews) { + (context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java)).apply { + val pendingIntent = PendingIntent.getActivity(context, OPEN_APP_INTENT_ID, this, PendingIntent.FLAG_UPDATE_CURRENT) + views.setOnClickPendingIntent(R.id.widget_date_holder, pendingIntent) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MyWidgetListProvider.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetListProvider.kt similarity index 55% rename from app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MyWidgetListProvider.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetListProvider.kt index 9deb3f86b..4f56d9d0b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MyWidgetListProvider.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetListProvider.kt @@ -1,4 +1,4 @@ -package com.simplemobiletools.calendar.helpers +package com.simplemobiletools.calendar.pro.helpers import android.app.PendingIntent import android.appwidget.AppWidgetManager @@ -8,56 +8,56 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.widget.RemoteViews -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.activities.DayActivity -import com.simplemobiletools.calendar.activities.SplashActivity -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.extensions.launchNewEventIntent -import com.simplemobiletools.calendar.services.WidgetService -import com.simplemobiletools.commons.extensions.getColoredBitmap -import com.simplemobiletools.commons.extensions.setBackgroundColor -import com.simplemobiletools.commons.extensions.setText -import com.simplemobiletools.commons.extensions.setTextSize +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SplashActivity +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.getWidgetFontSize +import com.simplemobiletools.calendar.pro.extensions.launchNewEventIntent +import com.simplemobiletools.calendar.pro.services.WidgetService +import com.simplemobiletools.calendar.pro.services.WidgetServiceEmpty +import com.simplemobiletools.commons.extensions.* import org.joda.time.DateTime class MyWidgetListProvider : AppWidgetProvider() { private val NEW_EVENT = "new_event" - private val LAUNCH_TODAY = "launch_today" + private val LAUNCH_CAL = "launch_cal" + private val GO_TO_TODAY = "go_to_today" override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { performUpdate(context) } private fun performUpdate(context: Context) { - val fontSize = context.config.getFontSize() + val fontSize = context.getWidgetFontSize() val textColor = context.config.widgetTextColor val appWidgetManager = AppWidgetManager.getInstance(context) appWidgetManager.getAppWidgetIds(getComponentName(context)).forEach { val views = RemoteViews(context.packageName, R.layout.widget_event_list).apply { - setBackgroundColor(R.id.widget_event_list_holder, context.config.widgetBgColor) + applyColorFilter(R.id.widget_event_list_background, context.config.widgetBgColor) setTextColor(R.id.widget_event_list_empty, textColor) setTextSize(R.id.widget_event_list_empty, fontSize) setTextColor(R.id.widget_event_list_today, textColor) - setTextSize(R.id.widget_event_list_today, fontSize + 3) + setTextSize(R.id.widget_event_list_today, fontSize) } - val now = (System.currentTimeMillis() / 1000).toInt() - val todayCode = Formatter.getDayCodeFromTS(now) - val todayText = Formatter.getDayTitle(context, todayCode) + val todayText = Formatter.getLongestDate(getNowSeconds()) views.setText(R.id.widget_event_list_today, todayText) - views.setImageViewBitmap(R.id.widget_event_new_event, context.resources.getColoredBitmap(R.drawable.ic_plus, textColor)) + views.setImageViewBitmap(R.id.widget_event_new_event, context.resources.getColoredBitmap(R.drawable.ic_plus_vector, textColor)) setupIntent(context, views, NEW_EVENT, R.id.widget_event_new_event) - setupIntent(context, views, LAUNCH_TODAY, R.id.widget_event_list_today) + setupIntent(context, views, LAUNCH_CAL, R.id.widget_event_list_today) + + views.setImageViewBitmap(R.id.widget_event_go_to_today, context.resources.getColoredBitmap(R.drawable.ic_today_vector, textColor)) + setupIntent(context, views, GO_TO_TODAY, R.id.widget_event_go_to_today) Intent(context, WidgetService::class.java).apply { data = Uri.parse(this.toUri(Intent.URI_INTENT_SCHEME)) views.setRemoteAdapter(R.id.widget_event_list, this) } - val startActivityIntent = Intent(context, SplashActivity::class.java) + val startActivityIntent = context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java) val startActivityPendingIntent = PendingIntent.getActivity(context, 0, startActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT) views.setPendingIntentTemplate(R.id.widget_event_list, startActivityPendingIntent) views.setEmptyView(R.id.widget_event_list, R.id.widget_event_list_empty) @@ -80,16 +80,34 @@ class MyWidgetListProvider : AppWidgetProvider() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { NEW_EVENT -> context.launchNewEventIntent() - LAUNCH_TODAY -> launchDayActivity(context) + LAUNCH_CAL -> launchCalenderInDefaultView(context) + GO_TO_TODAY -> goToToday(context) else -> super.onReceive(context, intent) } } - private fun launchDayActivity(context: Context) { - Intent(context, DayActivity::class.java).apply { + private fun launchCalenderInDefaultView(context: Context) { + (context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java)).apply { putExtra(DAY_CODE, Formatter.getDayCodeFromDateTime(DateTime())) + putExtra(VIEW_TO_OPEN, context.config.listWidgetViewToOpen) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(this) } } + + // hacky solution for reseting the events list + private fun goToToday(context: Context) { + val appWidgetManager = AppWidgetManager.getInstance(context) + appWidgetManager.getAppWidgetIds(getComponentName(context)).forEach { + val views = RemoteViews(context.packageName, R.layout.widget_event_list) + Intent(context, WidgetServiceEmpty::class.java).apply { + data = Uri.parse(this.toUri(Intent.URI_INTENT_SCHEME)) + views.setRemoteAdapter(R.id.widget_event_list, this) + } + + appWidgetManager.updateAppWidget(it, views) + } + + performUpdate(context) + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MyWidgetMonthlyProvider.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetMonthlyProvider.kt similarity index 57% rename from app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MyWidgetMonthlyProvider.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetMonthlyProvider.kt index a7d84982e..d1a5138ba 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/MyWidgetMonthlyProvider.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/MyWidgetMonthlyProvider.kt @@ -1,4 +1,4 @@ -package com.simplemobiletools.calendar.helpers +package com.simplemobiletools.calendar.pro.helpers import android.app.PendingIntent import android.appwidget.AppWidgetManager @@ -9,22 +9,25 @@ import android.content.Intent import android.content.res.Resources import android.view.View import android.widget.RemoteViews -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.activities.SplashActivity -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.extensions.launchNewEventIntent -import com.simplemobiletools.calendar.interfaces.MonthlyCalendar -import com.simplemobiletools.calendar.models.DayMonthly +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SplashActivity +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.getWidgetFontSize +import com.simplemobiletools.calendar.pro.extensions.launchNewEventIntent +import com.simplemobiletools.calendar.pro.interfaces.MonthlyCalendar +import com.simplemobiletools.calendar.pro.models.DayMonthly +import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.commons.extensions.* import org.joda.time.DateTime class MyWidgetMonthlyProvider : AppWidgetProvider() { private val PREV = "prev" private val NEXT = "next" + private val GO_TO_TODAY = "go_to_today" private val NEW_EVENT = "new_event" companion object { - var targetDate = DateTime.now() + private var targetDate = DateTime.now().withDayOfMonth(1) } override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { @@ -32,36 +35,7 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { } private fun performUpdate(context: Context) { - val largerFontSize = context.config.getFontSize() + 3f - val textColor = context.config.widgetTextColor - val resources = context.resources - - val appWidgetManager = AppWidgetManager.getInstance(context) - appWidgetManager.getAppWidgetIds(getComponentName(context)).forEach { - val views = RemoteViews(context.packageName, R.layout.fragment_month_widget) - views.setBackgroundColor(R.id.calendar_holder, context.config.widgetBgColor) - - views.setTextColor(R.id.top_value, textColor) - views.setTextSize(R.id.top_value, largerFontSize) - - var bmp = resources.getColoredBitmap(R.drawable.ic_pointer_left, textColor) - views.setImageViewBitmap(R.id.top_left_arrow, bmp) - - bmp = resources.getColoredBitmap(R.drawable.ic_pointer_right, textColor) - views.setImageViewBitmap(R.id.top_right_arrow, bmp) - - bmp = resources.getColoredBitmap(R.drawable.ic_plus, textColor) - views.setImageViewBitmap(R.id.top_new_event, bmp) - - setupIntent(context, views, PREV, R.id.top_left_arrow) - setupIntent(context, views, NEXT, R.id.top_right_arrow) - setupIntent(context, views, NEW_EVENT, R.id.top_new_event) - setupAppOpenIntent(context, views, R.id.top_value) - updateDayLabels(context, views, resources, textColor) - - appWidgetManager.updateAppWidget(it, views) - MonthlyCalendarImpl(monthlyCalendar, context).getMonth(targetDate) - } + MonthlyCalendarImpl(monthlyCalendar, context).getMonth(targetDate) } private fun getComponentName(context: Context) = ComponentName(context, MyWidgetMonthlyProvider::class.java) @@ -74,14 +48,17 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { } } - private fun setupAppOpenIntent(context: Context, views: RemoteViews, id: Int) { - val intent = Intent(context, SplashActivity::class.java) - val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) - views.setOnClickPendingIntent(id, pendingIntent) + private fun setupAppOpenIntent(context: Context, views: RemoteViews, id: Int, dayCode: String) { + (context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java)).apply { + putExtra(DAY_CODE, dayCode) + putExtra(VIEW_TO_OPEN, MONTHLY_VIEW) + val pendingIntent = PendingIntent.getActivity(context, Integer.parseInt(dayCode.substring(0, 6)), this, 0) + views.setOnClickPendingIntent(id, pendingIntent) + } } private fun setupDayOpenIntent(context: Context, views: RemoteViews, id: Int, dayCode: String) { - Intent(context, SplashActivity::class.java).apply { + (context.getLaunchIntent() ?: Intent(context, SplashActivity::class.java)).apply { putExtra(DAY_CODE, dayCode) val pendingIntent = PendingIntent.getActivity(context, Integer.parseInt(dayCode), this, 0) views.setOnClickPendingIntent(id, pendingIntent) @@ -92,6 +69,7 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { when (intent.action) { PREV -> getPrevMonth(context) NEXT -> getNextMonth(context) + GO_TO_TODAY -> goToToday(context) NEW_EVENT -> context.launchNewEventIntent() else -> super.onReceive(context, intent) } @@ -107,10 +85,16 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { MonthlyCalendarImpl(monthlyCalendar, context).getMonth(targetDate!!) } + private fun goToToday(context: Context) { + targetDate = DateTime.now().withDayOfMonth(1) + MonthlyCalendarImpl(monthlyCalendar, context).getMonth(targetDate!!) + } + private fun updateDays(context: Context, views: RemoteViews, days: List) { - val displayWeekNumbers = context.config.displayWeekNumbers + val displayWeekNumbers = context.config.showWeekNumbers val textColor = context.config.widgetTextColor - val smallerFontSize = context.config.getFontSize() - 3f + val dimPastEvents = context.config.dimPastEvents + val smallerFontSize = context.getWidgetFontSize() - 3f val res = context.resources val len = days.size val packageName = context.packageName @@ -130,7 +114,7 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { } } - val weakTextColor = textColor.adjustAlpha(LOW_ALPHA) + val weakTextColor = textColor.adjustAlpha(MEDIUM_ALPHA) for (i in 0 until len) { val day = days[i] val currTextColor = if (day.isThisMonth) textColor else weakTextColor @@ -139,13 +123,16 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { addDayNumber(context, views, day, currTextColor, id) setupDayOpenIntent(context, views, id, day.code) + day.dayEvents = day.dayEvents.asSequence().sortedWith(compareBy({ it.flags and FLAG_ALL_DAY == 0 }, { it.startTS }, { it.title })) + .toMutableList() as ArrayList + day.dayEvents.forEach { var backgroundColor = it.color var eventTextColor = backgroundColor.getContrastColor() - if (!day.isThisMonth) { - eventTextColor = eventTextColor.adjustAlpha(0.25f) - backgroundColor = backgroundColor.adjustAlpha(0.25f) + if (!day.isThisMonth || (dimPastEvents && it.isPastEvent)) { + eventTextColor = eventTextColor.adjustAlpha(MEDIUM_ALPHA) + backgroundColor = backgroundColor.adjustAlpha(MEDIUM_ALPHA) } val newRemoteView = RemoteViews(packageName, R.layout.day_monthly_event_view).apply { @@ -162,7 +149,7 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { private fun addDayNumber(context: Context, views: RemoteViews, day: DayMonthly, textColor: Int, id: Int) { val newRemoteView = RemoteViews(context.packageName, R.layout.day_monthly_number_view).apply { setText(R.id.day_monthly_number_id, day.value.toString()) - setTextSize(R.id.day_monthly_number_id, context.config.getFontSize() - 3f) + setTextSize(R.id.day_monthly_number_id, context.getWidgetFontSize() - 3f) if (day.isToday) { setBackgroundColor(R.id.day_monthly_number_id, textColor) @@ -175,33 +162,71 @@ class MyWidgetMonthlyProvider : AppWidgetProvider() { } private val monthlyCalendar = object : MonthlyCalendar { - override fun updateMonthlyCalendar(context: Context, month: String, days: List, checkedEvents: Boolean) { + override fun updateMonthlyCalendar(context: Context, month: String, days: ArrayList, checkedEvents: Boolean, currTargetDate: DateTime) { + val largerFontSize = context.getWidgetFontSize() + 3f + val textColor = context.config.widgetTextColor + val resources = context.resources + val appWidgetManager = AppWidgetManager.getInstance(context) appWidgetManager.getAppWidgetIds(getComponentName(context)).forEach { val views = RemoteViews(context.packageName, R.layout.fragment_month_widget) views.setText(R.id.top_value, month) + + views.applyColorFilter(R.id.widget_month_background, context.config.widgetBgColor) + + views.setTextColor(R.id.top_value, textColor) + views.setTextSize(R.id.top_value, largerFontSize) + + var bmp = resources.getColoredBitmap(R.drawable.ic_chevron_left_vector, textColor) + views.setImageViewBitmap(R.id.top_left_arrow, bmp) + + bmp = resources.getColoredBitmap(R.drawable.ic_chevron_right_vector, textColor) + views.setImageViewBitmap(R.id.top_right_arrow, bmp) + + bmp = resources.getColoredBitmap(R.drawable.ic_today_vector, textColor) + views.setImageViewBitmap(R.id.top_go_to_today, bmp) + + bmp = resources.getColoredBitmap(R.drawable.ic_plus_vector, textColor) + views.setImageViewBitmap(R.id.top_new_event, bmp) + + val shouldGoToTodayBeVisible = currTargetDate.withTime(0, 0, 0, 0) != DateTime.now().withDayOfMonth(1).withTime(0, 0, 0, 0) + views.setVisibleIf(R.id.top_go_to_today, shouldGoToTodayBeVisible) + + updateDayLabels(context, views, resources, textColor) updateDays(context, views, days) - appWidgetManager.updateAppWidget(it, views) + + setupIntent(context, views, PREV, R.id.top_left_arrow) + setupIntent(context, views, NEXT, R.id.top_right_arrow) + setupIntent(context, views, GO_TO_TODAY, R.id.top_go_to_today) + setupIntent(context, views, NEW_EVENT, R.id.top_new_event) + + val monthCode = days.firstOrNull { it.code.substring(6) == "01" }?.code ?: Formatter.getTodayCode() + setupAppOpenIntent(context, views, R.id.top_value, monthCode) + + try { + appWidgetManager.updateAppWidget(it, views) + } catch (ignored: RuntimeException) { + } } } } private fun updateDayLabels(context: Context, views: RemoteViews, resources: Resources, textColor: Int) { val sundayFirst = context.config.isSundayFirst - val smallerFontSize = context.config.getFontSize() + val smallerFontSize = context.getWidgetFontSize() val packageName = context.packageName - val letters = letterIDs + val letters = context.resources.getStringArray(R.array.week_day_letters) for (i in 0..6) { val id = resources.getIdentifier("label_$i", "id", packageName) views.setTextColor(id, textColor) views.setTextSize(id, smallerFontSize) var index = i - if (!sundayFirst) { - index = (index + 1) % letters.size + if (sundayFirst) { + index = (index + 6) % letters.size } - views.setText(id, resources.getString(letters[index])) + views.setText(id, letters[index]) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Parser.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Parser.kt similarity index 60% rename from app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Parser.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Parser.kt index 3a81ce0e0..b9420dac7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/Parser.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Parser.kt @@ -1,26 +1,30 @@ -package com.simplemobiletools.calendar.helpers +package com.simplemobiletools.calendar.pro.helpers -import com.simplemobiletools.calendar.extensions.isXMonthlyRepetition -import com.simplemobiletools.calendar.extensions.isXWeeklyRepetition -import com.simplemobiletools.calendar.extensions.seconds -import com.simplemobiletools.calendar.models.Event -import com.simplemobiletools.calendar.models.RepeatRule +import com.simplemobiletools.calendar.pro.extensions.isXMonthlyRepetition +import com.simplemobiletools.calendar.pro.extensions.isXWeeklyRepetition +import com.simplemobiletools.calendar.pro.extensions.isXYearlyRepetition +import com.simplemobiletools.calendar.pro.extensions.seconds +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.calendar.pro.models.EventRepetition +import com.simplemobiletools.commons.extensions.areDigitsOnly +import com.simplemobiletools.commons.helpers.* import org.joda.time.DateTimeZone import org.joda.time.format.DateTimeFormat class Parser { // from RRULE:FREQ=DAILY;COUNT=5 to Daily, 5x... - fun parseRepeatInterval(fullString: String, startTS: Int): RepeatRule { - val parts = fullString.split(";") + fun parseRepeatInterval(fullString: String, startTS: Long): EventRepetition { + val parts = fullString.split(";").filter { it.isNotEmpty() } var repeatInterval = 0 var repeatRule = 0 - var repeatLimit = 0 - if (fullString.isEmpty()) { - return RepeatRule(repeatInterval, repeatRule, repeatLimit) - } + var repeatLimit = 0L for (part in parts) { val keyValue = part.split("=") + if (keyValue.size <= 1) { + continue + } + val key = keyValue[0] val value = keyValue[1] if (key == FREQ) { @@ -28,11 +32,24 @@ class Parser { if (value == WEEKLY) { val start = Formatter.getDateTimeFromTS(startTS) repeatRule = Math.pow(2.0, (start.dayOfWeek - 1).toDouble()).toInt() - } else if (value == MONTHLY) { - repeatRule = REPEAT_MONTH_SAME_DAY + } else if (value == MONTHLY || value == YEARLY) { + repeatRule = REPEAT_SAME_DAY + } else if (value == DAILY && fullString.contains(INTERVAL)) { + val interval = fullString.substringAfter("$INTERVAL=").substringBefore(";") + // properly handle events repeating by 14 days or so, just add a repeat rule to specify a day of the week + if (interval.areDigitsOnly() && interval.toInt() % 7 == 0) { + val dateTime = Formatter.getDateTimeFromTS(startTS) + repeatRule = Math.pow(2.0, (dateTime.dayOfWeek - 1).toDouble()).toInt() + } else if (fullString.contains("BYDAY")) { + // some services use weekly repetition for repeating on specific week days, some use daily + // make these produce the same result + // RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR + // RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR + repeatInterval = WEEK_SECONDS + } } } else if (key == COUNT) { - repeatLimit = -value.toInt() + repeatLimit = -value.toLong() } else if (key == UNTIL) { repeatLimit = parseDateTimeValue(value) } else if (key == INTERVAL) { @@ -40,14 +57,16 @@ class Parser { } else if (key == BYDAY) { if (repeatInterval.isXWeeklyRepetition()) { repeatRule = handleRepeatRule(value) - } else if (repeatInterval.isXMonthlyRepetition()) { - repeatRule = if (value.startsWith("-1")) REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST else REPEAT_MONTH_ORDER_WEEKDAY + } else if (repeatInterval.isXMonthlyRepetition() || repeatInterval.isXYearlyRepetition()) { + repeatRule = if (value.startsWith("-1")) REPEAT_ORDER_WEEKDAY_USE_LAST else REPEAT_ORDER_WEEKDAY + } + } else if (key == BYMONTHDAY) { + if (value.split(",").any { it.toInt() == -1 }) { + repeatRule = REPEAT_LAST_DAY } - } else if (key == BYMONTHDAY && value.toInt() == -1) { - repeatRule = REPEAT_MONTH_LAST_DAY } } - return RepeatRule(repeatInterval, repeatRule, repeatLimit) + return EventRepetition(repeatInterval, repeatRule, repeatLimit) } private fun getFrequencySeconds(interval: String) = when (interval) { @@ -61,33 +80,33 @@ class Parser { private fun handleRepeatRule(value: String): Int { var newRepeatRule = 0 if (value.contains(MO)) - newRepeatRule = newRepeatRule or MONDAY + newRepeatRule = newRepeatRule or MONDAY_BIT if (value.contains(TU)) - newRepeatRule = newRepeatRule or TUESDAY + newRepeatRule = newRepeatRule or TUESDAY_BIT if (value.contains(WE)) - newRepeatRule = newRepeatRule or WEDNESDAY + newRepeatRule = newRepeatRule or WEDNESDAY_BIT if (value.contains(TH)) - newRepeatRule = newRepeatRule or THURSDAY + newRepeatRule = newRepeatRule or THURSDAY_BIT if (value.contains(FR)) - newRepeatRule = newRepeatRule or FRIDAY + newRepeatRule = newRepeatRule or FRIDAY_BIT if (value.contains(SA)) - newRepeatRule = newRepeatRule or SATURDAY + newRepeatRule = newRepeatRule or SATURDAY_BIT if (value.contains(SU)) - newRepeatRule = newRepeatRule or SUNDAY + newRepeatRule = newRepeatRule or SUNDAY_BIT return newRepeatRule } - fun parseDateTimeValue(value: String): Int { + fun parseDateTimeValue(value: String): Long { val edited = value.replace("T", "").replace("Z", "") return if (edited.length == 14) { parseLongFormat(edited, value.endsWith("Z")) } else { val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMdd") - dateTimeFormat.parseDateTime(edited).withZoneRetainFields(DateTimeZone.getDefault()).withHourOfDay(1).seconds() + dateTimeFormat.parseDateTime(edited).withHourOfDay(5).seconds() } } - private fun parseLongFormat(digitString: String, useUTC: Boolean): Int { + private fun parseLongFormat(digitString: String, useUTC: Boolean): Long { val dateTimeFormat = DateTimeFormat.forPattern("yyyyMMddHHmmss") val dateTimeZone = if (useUTC) DateTimeZone.UTC else DateTimeZone.getDefault() return dateTimeFormat.parseDateTime(digitString).withZoneRetainFields(dateTimeZone).seconds() @@ -102,8 +121,9 @@ class Parser { val freq = getFreq(repeatInterval) val interval = getInterval(repeatInterval) val repeatLimit = getRepeatLimitString(event) + val byMonth = getByMonth(event) val byDay = getByDay(event) - return "$FREQ=$freq;$INTERVAL=$interval$repeatLimit$byDay" + return "$FREQ=$freq;$INTERVAL=$interval$repeatLimit$byMonth$byDay" } private fun getFreq(interval: Int) = when { @@ -121,19 +141,27 @@ class Parser { } private fun getRepeatLimitString(event: Event) = when { - event.repeatLimit == 0 -> "" + event.repeatLimit == 0L -> "" event.repeatLimit < 0 -> ";$COUNT=${-event.repeatLimit}" else -> ";$UNTIL=${Formatter.getDayCodeFromTS(event.repeatLimit)}" } + private fun getByMonth(event: Event) = when { + event.repeatInterval.isXYearlyRepetition() -> { + val start = Formatter.getDateTimeFromTS(event.startTS) + ";$BYMONTH=${start.monthOfYear}" + } + else -> "" + } + private fun getByDay(event: Event) = when { event.repeatInterval.isXWeeklyRepetition() -> { val days = getByDayString(event.repeatRule) ";$BYDAY=$days" } - event.repeatInterval.isXMonthlyRepetition() -> when (event.repeatRule) { - REPEAT_MONTH_LAST_DAY -> ";$BYMONTHDAY=-1" - REPEAT_MONTH_ORDER_WEEKDAY_USE_LAST, REPEAT_MONTH_ORDER_WEEKDAY -> { + event.repeatInterval.isXMonthlyRepetition() || event.repeatInterval.isXYearlyRepetition() -> when (event.repeatRule) { + REPEAT_LAST_DAY -> ";$BYMONTHDAY=-1" + REPEAT_ORDER_WEEKDAY_USE_LAST, REPEAT_ORDER_WEEKDAY -> { val start = Formatter.getDateTimeFromTS(event.startTS) val dayOfMonth = start.dayOfMonth val isLastWeekday = start.monthOfYear != start.plusDays(7).monthOfYear @@ -148,19 +176,19 @@ class Parser { private fun getByDayString(rule: Int): String { var result = "" - if (rule and MONDAY != 0) + if (rule and MONDAY_BIT != 0) result += "$MO," - if (rule and TUESDAY != 0) + if (rule and TUESDAY_BIT != 0) result += "$TU," - if (rule and WEDNESDAY != 0) + if (rule and WEDNESDAY_BIT != 0) result += "$WE," - if (rule and THURSDAY != 0) + if (rule and THURSDAY_BIT != 0) result += "$TH," - if (rule and FRIDAY != 0) + if (rule and FRIDAY_BIT != 0) result += "$FR," - if (rule and SATURDAY != 0) + if (rule and SATURDAY_BIT != 0) result += "$SA," - if (rule and SUNDAY != 0) + if (rule and SUNDAY_BIT != 0) result += "$SU," return result.trimEnd(',') } @@ -194,7 +222,7 @@ class Parser { private fun getDurationValue(duration: String, char: String) = Regex("[0-9]+(?=$char)").find(duration)?.value?.toInt() ?: 0 // from 65 to P0DT1H5M0S - fun getDurationCode(minutes: Int): String { + fun getDurationCode(minutes: Long): String { var days = 0 var hours = 0 var remainder = minutes diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/WeeklyCalendarImpl.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/WeeklyCalendarImpl.kt new file mode 100644 index 000000000..7aedef406 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/WeeklyCalendarImpl.kt @@ -0,0 +1,20 @@ +package com.simplemobiletools.calendar.pro.helpers + +import android.content.Context +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.interfaces.WeeklyCalendar +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.commons.helpers.WEEK_SECONDS +import java.util.* + +class WeeklyCalendarImpl(val callback: WeeklyCalendar, val context: Context) { + var mEvents = ArrayList() + + fun updateWeeklyCalendar(weekStartTS: Long) { + val endTS = weekStartTS + WEEK_SECONDS + context.eventsHelper.getEvents(weekStartTS, endTS) { + mEvents = it + callback.updateWeeklyCalendar(it) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/YearlyCalendarImpl.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/YearlyCalendarImpl.kt similarity index 74% rename from app/src/main/kotlin/com/simplemobiletools/calendar/helpers/YearlyCalendarImpl.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/YearlyCalendarImpl.kt index ab1974cc3..ec9ac1cd6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/helpers/YearlyCalendarImpl.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/YearlyCalendarImpl.kt @@ -1,13 +1,12 @@ -package com.simplemobiletools.calendar.helpers +package com.simplemobiletools.calendar.pro.helpers import android.content.Context import android.util.SparseArray -import com.simplemobiletools.calendar.extensions.dbHelper -import com.simplemobiletools.calendar.extensions.getFilteredEvents -import com.simplemobiletools.calendar.extensions.seconds -import com.simplemobiletools.calendar.interfaces.YearlyCalendar -import com.simplemobiletools.calendar.models.DayYearly -import com.simplemobiletools.calendar.models.Event +import com.simplemobiletools.calendar.pro.extensions.eventsHelper +import com.simplemobiletools.calendar.pro.extensions.seconds +import com.simplemobiletools.calendar.pro.interfaces.YearlyCalendar +import com.simplemobiletools.calendar.pro.models.DayYearly +import com.simplemobiletools.calendar.pro.models.Event import org.joda.time.DateTime import java.util.* @@ -17,16 +16,15 @@ class YearlyCalendarImpl(val callback: YearlyCalendar, val context: Context, val val startDateTime = DateTime().withTime(0, 0, 0, 0).withDate(year, 1, 1) val startTS = startDateTime.seconds() val endTS = startDateTime.plusYears(1).minusSeconds(1).seconds() - context.dbHelper.getEvents(startTS, endTS) { + context.eventsHelper.getEvents(startTS, endTS) { gotEvents(it) } } private fun gotEvents(events: MutableList) { - val filtered = context.getFilteredEvents(events) val arr = SparseArray>(12) - filtered.forEach { + events.forEach { val startDateTime = Formatter.getDateTimeFromTS(it.startTS) markDay(arr, startDateTime, it) @@ -41,7 +39,7 @@ class YearlyCalendarImpl(val callback: YearlyCalendar, val context: Context, val } } } - callback.updateYearlyCalendar(arr, filtered.hashCode()) + callback.updateYearlyCalendar(arr, events.hashCode()) } private fun markDay(arr: SparseArray>, dateTime: DateTime, event: Event) { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/DeleteEventTypesListener.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/DeleteEventTypesListener.kt new file mode 100644 index 000000000..83cf1f6f8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/DeleteEventTypesListener.kt @@ -0,0 +1,8 @@ +package com.simplemobiletools.calendar.pro.interfaces + +import com.simplemobiletools.calendar.pro.models.EventType +import java.util.* + +interface DeleteEventTypesListener { + fun deleteEventTypes(eventTypes: ArrayList, deleteEvents: Boolean): Boolean +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventTypesDao.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventTypesDao.kt new file mode 100644 index 000000000..a308fcb79 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventTypesDao.kt @@ -0,0 +1,28 @@ +package com.simplemobiletools.calendar.pro.interfaces + +import androidx.room.* +import com.simplemobiletools.calendar.pro.models.EventType + +@Dao +interface EventTypesDao { + @Query("SELECT * FROM event_types ORDER BY title ASC") + fun getEventTypes(): List + + @Query("SELECT * FROM event_types WHERE id = :id") + fun getEventTypeWithId(id: Long): EventType? + + @Query("SELECT id FROM event_types WHERE title = :title COLLATE NOCASE") + fun getEventTypeIdWithTitle(title: String): Long? + + @Query("SELECT * FROM event_types WHERE caldav_calendar_id = :calendarId") + fun getEventTypeWithCalDAVCalendarId(calendarId: Int): EventType? + + @Query("DELETE FROM event_types WHERE caldav_calendar_id IN (:ids)") + fun deleteEventTypesWithCalendarId(ids: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrUpdate(eventType: EventType): Long + + @Delete + fun deleteEventTypes(eventTypes: List) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt new file mode 100644 index 000000000..5c19ea063 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt @@ -0,0 +1,116 @@ +package com.simplemobiletools.calendar.pro.interfaces + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.simplemobiletools.calendar.pro.helpers.REGULAR_EVENT_TYPE_ID +import com.simplemobiletools.calendar.pro.helpers.SOURCE_CONTACT_ANNIVERSARY +import com.simplemobiletools.calendar.pro.helpers.SOURCE_CONTACT_BIRTHDAY +import com.simplemobiletools.calendar.pro.models.Event + +@Dao +interface EventsDao { + @Query("SELECT * FROM events") + fun getAllEvents(): List + + @Query("SELECT * FROM events WHERE event_type IN (:eventTypeIds)") + fun getAllEventsWithTypes(eventTypeIds: List): List + + @Query("SELECT * FROM events WHERE id = :id") + fun getEventWithId(id: Long): Event? + + @Query("SELECT * FROM events WHERE import_id = :importId") + fun getEventWithImportId(importId: String): Event? + + @Query("SELECT * FROM events WHERE start_ts <= :toTS AND end_ts >= :fromTS AND repeat_interval = 0") + fun getOneTimeEventsFromTo(toTS: Long, fromTS: Long): List + + @Query("SELECT * FROM events WHERE id = :id AND start_ts <= :toTS AND end_ts >= :fromTS AND repeat_interval = 0") + fun getOneTimeEventFromToWithId(id: Long, toTS: Long, fromTS: Long): List + + @Query("SELECT * FROM events WHERE start_ts <= :toTS AND end_ts >= :fromTS AND start_ts != 0 AND repeat_interval = 0 AND event_type IN (:eventTypeIds)") + fun getOneTimeEventsFromToWithTypes(toTS: Long, fromTS: Long, eventTypeIds: List): List + + @Query("SELECT * FROM events WHERE end_ts > :toTS AND repeat_interval = 0 AND event_type IN (:eventTypeIds)") + fun getOneTimeFutureEventsWithTypes(toTS: Long, eventTypeIds: List): List + + @Query("SELECT * FROM events WHERE start_ts <= :toTS AND repeat_interval != 0") + fun getRepeatableEventsFromToWithTypes(toTS: Long): List + + @Query("SELECT * FROM events WHERE id = :id AND start_ts <= :toTS AND repeat_interval != 0") + fun getRepeatableEventFromToWithId(id: Long, toTS: Long): List + + @Query("SELECT * FROM events WHERE start_ts <= :toTS AND start_ts != 0 AND repeat_interval != 0 AND event_type IN (:eventTypeIds)") + fun getRepeatableEventsFromToWithTypes(toTS: Long, eventTypeIds: List): List + + @Query("SELECT * FROM events WHERE repeat_interval != 0 AND (repeat_limit == 0 OR repeat_limit > :currTS) AND event_type IN (:eventTypeIds)") + fun getRepeatableFutureEventsWithTypes(currTS: Long, eventTypeIds: List): List + + @Query("SELECT * FROM events WHERE id IN (:ids) AND import_id != \"\"") + fun getEventsByIdsWithImportIds(ids: List): List + + @Query("SELECT * FROM events WHERE title LIKE :searchQuery OR location LIKE :searchQuery OR description LIKE :searchQuery") + fun getEventsForSearch(searchQuery: String): List + + @Query("SELECT * FROM events WHERE source = \'$SOURCE_CONTACT_BIRTHDAY\'") + fun getBirthdays(): List + + @Query("SELECT * FROM events WHERE source = \'$SOURCE_CONTACT_ANNIVERSARY\'") + fun getAnniversaries(): List + + @Query("SELECT * FROM events WHERE import_id != \"\"") + fun getEventsWithImportIds(): List + + @Query("SELECT * FROM events WHERE source = :source") + fun getEventsFromCalDAVCalendar(source: String): List + + @Query("SELECT * FROM events WHERE id IN (:ids)") + fun getEventsWithIds(ids: List): List + + //val selection = "$COL_REMINDER_MINUTES != -1 AND ($COL_START_TS > ? OR $COL_REPEAT_INTERVAL != 0) AND $COL_START_TS != 0" + @Query("SELECT * FROM events WHERE reminder_1_minutes != -1 AND (start_ts > :currentTS OR repeat_interval != 0) AND start_ts != 0") + fun getEventsAtReboot(currentTS: Long): List + + @Query("SELECT id FROM events") + fun getEventIds(): List + + @Query("SELECT id FROM events WHERE import_id = :importId") + fun getEventIdWithImportId(importId: String): Long? + + @Query("SELECT id FROM events WHERE import_id LIKE :importId") + fun getEventIdWithLastImportId(importId: String): Long? + + @Query("SELECT id FROM events WHERE event_type = :eventTypeId") + fun getEventIdsByEventType(eventTypeId: Long): List + + @Query("SELECT id FROM events WHERE event_type IN (:eventTypeIds)") + fun getEventIdsByEventType(eventTypeIds: List): List + + @Query("SELECT id FROM events WHERE parent_id IN (:parentIds)") + fun getEventIdsWithParentIds(parentIds: List): List + + @Query("SELECT id FROM events WHERE source = :source AND import_id != \"\"") + fun getCalDAVCalendarEvents(source: String): List + + @Query("UPDATE events SET event_type = $REGULAR_EVENT_TYPE_ID WHERE event_type = :eventTypeId") + fun resetEventsWithType(eventTypeId: Long) + + @Query("UPDATE events SET import_id = :importId, source = :source WHERE id = :id") + fun updateEventImportIdAndSource(importId: String, source: String, id: Long) + + @Query("UPDATE events SET repeat_limit = :repeatLimit WHERE id = :id") + fun updateEventRepetitionLimit(repeatLimit: Long, id: Long) + + @Query("UPDATE events SET repetition_exceptions = :repetitionExceptions WHERE id = :id") + fun updateEventRepetitionExceptions(repetitionExceptions: String, id: Long) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrUpdate(event: Event): Long + + @Query("DELETE FROM events WHERE id IN (:ids)") + fun deleteEvents(ids: List) + + @Query("DELETE FROM events WHERE source = :source AND import_id = :importId") + fun deleteBirthdayAnniversary(source: String, importId: String): Int +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/MonthlyCalendar.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/MonthlyCalendar.kt new file mode 100644 index 000000000..1a33eba54 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/MonthlyCalendar.kt @@ -0,0 +1,9 @@ +package com.simplemobiletools.calendar.pro.interfaces + +import android.content.Context +import com.simplemobiletools.calendar.pro.models.DayMonthly +import org.joda.time.DateTime + +interface MonthlyCalendar { + fun updateMonthlyCalendar(context: Context, month: String, days: ArrayList, checkedEvents: Boolean, currTargetDate: DateTime) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/NavigationListener.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/NavigationListener.kt similarity index 72% rename from app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/NavigationListener.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/NavigationListener.kt index d8a071d02..036c72de1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/NavigationListener.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/NavigationListener.kt @@ -1,4 +1,4 @@ -package com.simplemobiletools.calendar.interfaces +package com.simplemobiletools.calendar.pro.interfaces import org.joda.time.DateTime diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/WeekFragmentListener.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/WeekFragmentListener.kt new file mode 100644 index 000000000..ae982cbd3 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/WeekFragmentListener.kt @@ -0,0 +1,13 @@ +package com.simplemobiletools.calendar.pro.interfaces + +interface WeekFragmentListener { + fun scrollTo(y: Int) + + fun updateHoursTopMargin(margin: Int) + + fun getCurrScrollY(): Int + + fun updateRowHeight(rowHeight: Int) + + fun getFullFragmentHeight(): Int +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/WeeklyCalendar.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/WeeklyCalendar.kt new file mode 100644 index 000000000..0b50a5175 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/WeeklyCalendar.kt @@ -0,0 +1,7 @@ +package com.simplemobiletools.calendar.pro.interfaces + +import com.simplemobiletools.calendar.pro.models.Event + +interface WeeklyCalendar { + fun updateWeeklyCalendar(events: ArrayList) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/YearlyCalendar.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/YearlyCalendar.kt similarity index 59% rename from app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/YearlyCalendar.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/YearlyCalendar.kt index cf6ad5da6..90c1832f9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/interfaces/YearlyCalendar.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/YearlyCalendar.kt @@ -1,7 +1,7 @@ -package com.simplemobiletools.calendar.interfaces +package com.simplemobiletools.calendar.pro.interfaces import android.util.SparseArray -import com.simplemobiletools.calendar.models.DayYearly +import com.simplemobiletools.calendar.pro.models.DayYearly import java.util.* interface YearlyCalendar { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/jobs/CalDAVUpdateListener.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/jobs/CalDAVUpdateListener.kt new file mode 100644 index 000000000..d6d45abb5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/jobs/CalDAVUpdateListener.kt @@ -0,0 +1,67 @@ +package com.simplemobiletools.calendar.pro.jobs + +import android.annotation.TargetApi +import android.app.job.JobInfo +import android.app.job.JobParameters +import android.app.job.JobScheduler +import android.app.job.JobService +import android.content.ComponentName +import android.content.Context +import android.os.Build +import android.os.Handler +import android.provider.CalendarContract +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.recheckCalDAVCalendars +import com.simplemobiletools.calendar.pro.extensions.refreshCalDAVCalendars + +// based on https://developer.android.com/reference/android/app/job/JobInfo.Builder.html#addTriggerContentUri(android.app.job.JobInfo.TriggerContentUri) +@TargetApi(Build.VERSION_CODES.N) +class CalDAVUpdateListener : JobService() { + companion object { + const val CALDAV_EVENT_CONTENT_JOB = 1 + } + + private val mHandler = Handler() + private val mWorker = Runnable { + scheduleJob(this@CalDAVUpdateListener) + jobFinished(mRunningParams, false) + } + + private var mRunningParams: JobParameters? = null + + fun scheduleJob(context: Context) { + val componentName = ComponentName(context, CalDAVUpdateListener::class.java) + val uri = CalendarContract.Calendars.CONTENT_URI + JobInfo.Builder(CALDAV_EVENT_CONTENT_JOB, componentName).apply { + addTriggerContentUri(JobInfo.TriggerContentUri(uri, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) + context.getSystemService(JobScheduler::class.java).schedule(build()) + } + } + + fun isScheduled(context: Context): Boolean { + val jobScheduler = context.getSystemService(JobScheduler::class.java) + val jobs = jobScheduler.allPendingJobs ?: return false + return jobs.any { it.id == CALDAV_EVENT_CONTENT_JOB } + } + + fun cancelJob(context: Context) { + val js = context.getSystemService(JobScheduler::class.java) + js.cancel(CALDAV_EVENT_CONTENT_JOB) + } + + override fun onStartJob(params: JobParameters): Boolean { + mRunningParams = params + + if (params.triggeredContentAuthorities != null && params.triggeredContentUris != null) { + recheckCalDAVCalendars {} + } + + mHandler.post(mWorker) + return true + } + + override fun onStopJob(params: JobParameters): Boolean { + mHandler.removeCallbacks(mWorker) + return false + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Attendee.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Attendee.kt new file mode 100644 index 000000000..004b3201f --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Attendee.kt @@ -0,0 +1,37 @@ +package com.simplemobiletools.calendar.pro.models + +import android.content.Context +import android.graphics.drawable.Drawable +import android.provider.CalendarContract +import android.widget.ImageView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestOptions + +data class Attendee(val contactId: Int, var name: String, val email: String, var status: Int, var photoUri: String, var isMe: Boolean, var relationship: Int) { + fun getPublicName() = if (name.isNotEmpty()) name else email + + fun updateImage(context: Context, imageView: ImageView, placeholder: Drawable) { + if (photoUri.isEmpty()) { + imageView.setImageDrawable(placeholder) + } else { + val options = RequestOptions() + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(placeholder) + .centerCrop() + + Glide.with(context) + .load(photoUri) + .transition(DrawableTransitionOptions.withCrossFade()) + .placeholder(placeholder) + .apply(options) + .apply(RequestOptions.circleCropTransform()) + .into(imageView) + } + } + + fun showStatusImage() = status == CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED || + status == CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED || + status == CalendarContract.Attendees.ATTENDEE_STATUS_TENTATIVE +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/CalDAVCalendar.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/CalDAVCalendar.kt new file mode 100644 index 000000000..2b53fd851 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/CalDAVCalendar.kt @@ -0,0 +1,8 @@ +package com.simplemobiletools.calendar.pro.models + +data class CalDAVCalendar(val id: Int, val displayName: String, val accountName: String, val accountType: String, val ownerName: String, + var color: Int, val accessLevel: Int) { + fun canWrite() = accessLevel >= 500 + + fun getFullTitle() = "$displayName ($accountName)" +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/DayMonthly.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/DayMonthly.kt similarity index 50% rename from app/src/main/kotlin/com/simplemobiletools/calendar/models/DayMonthly.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/DayMonthly.kt index 8f8cfe30e..06ed13492 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/DayMonthly.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/DayMonthly.kt @@ -1,6 +1,4 @@ -package com.simplemobiletools.calendar.models +package com.simplemobiletools.calendar.pro.models -data class DayMonthly(val value: Int, val isThisMonth: Boolean, val isToday: Boolean, val code: String, val weekOfYear: Int, var dayEvents: ArrayList) { - - fun hasEvent() = dayEvents.isNotEmpty() -} +data class DayMonthly(val value: Int, val isThisMonth: Boolean, val isToday: Boolean, val code: String, val weekOfYear: Int, var dayEvents: ArrayList, + var indexOnMonthView: Int) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/models/DayYearly.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/DayYearly.kt similarity index 71% rename from app/src/main/kotlin/com/simplemobiletools/calendar/models/DayYearly.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/DayYearly.kt index 5a965a3c6..f7a89aa43 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/models/DayYearly.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/DayYearly.kt @@ -1,4 +1,4 @@ -package com.simplemobiletools.calendar.models +package com.simplemobiletools.calendar.pro.models data class DayYearly(var eventColors: HashSet = HashSet()) { fun addColor(color: Int) = eventColors.add(color) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Event.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Event.kt new file mode 100644 index 000000000..ecedb5f91 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Event.kt @@ -0,0 +1,189 @@ +package com.simplemobiletools.calendar.pro.models + +import androidx.collection.LongSparseArray +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import com.simplemobiletools.calendar.pro.extensions.seconds +import com.simplemobiletools.calendar.pro.helpers.* +import com.simplemobiletools.commons.extensions.addBitIf +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import java.io.Serializable + +@Entity(tableName = "events", indices = [(Index(value = ["id"], unique = true))]) +data class Event( + @PrimaryKey(autoGenerate = true) var id: Long?, + @ColumnInfo(name = "start_ts") var startTS: Long = 0L, + @ColumnInfo(name = "end_ts") var endTS: Long = 0L, + @ColumnInfo(name = "title") var title: String = "", + @ColumnInfo(name = "location") var location: String = "", + @ColumnInfo(name = "description") var description: String = "", + @ColumnInfo(name = "reminder_1_minutes") var reminder1Minutes: Int = -1, + @ColumnInfo(name = "reminder_2_minutes") var reminder2Minutes: Int = -1, + @ColumnInfo(name = "reminder_3_minutes") var reminder3Minutes: Int = -1, + @ColumnInfo(name = "reminder_1_type") var reminder1Type: Int = REMINDER_NOTIFICATION, + @ColumnInfo(name = "reminder_2_type") var reminder2Type: Int = REMINDER_NOTIFICATION, + @ColumnInfo(name = "reminder_3_type") var reminder3Type: Int = REMINDER_NOTIFICATION, + @ColumnInfo(name = "repeat_interval") var repeatInterval: Int = 0, + @ColumnInfo(name = "repeat_rule") var repeatRule: Int = 0, + @ColumnInfo(name = "repeat_limit") var repeatLimit: Long = 0L, + @ColumnInfo(name = "repetition_exceptions") var repetitionExceptions: ArrayList = ArrayList(), + @ColumnInfo(name = "attendees") var attendees: String = "", + @ColumnInfo(name = "import_id") var importId: String = "", + @ColumnInfo(name = "time_zone") var timeZone: String = "", + @ColumnInfo(name = "flags") var flags: Int = 0, + @ColumnInfo(name = "event_type") var eventType: Long = REGULAR_EVENT_TYPE_ID, + @ColumnInfo(name = "parent_id") var parentId: Long = 0, + @ColumnInfo(name = "last_updated") var lastUpdated: Long = 0L, + @ColumnInfo(name = "source") var source: String = SOURCE_SIMPLE_CALENDAR) + : Serializable { + + companion object { + private const val serialVersionUID = -32456795132345616L + } + + fun addIntervalTime(original: Event) { + val oldStart = Formatter.getDateTimeFromTS(startTS) + val newStart: DateTime + newStart = when (repeatInterval) { + DAY -> oldStart.plusDays(1) + else -> { + when { + repeatInterval % YEAR == 0 -> when (repeatRule) { + REPEAT_ORDER_WEEKDAY -> addXthDayInterval(oldStart, original, false) + REPEAT_ORDER_WEEKDAY_USE_LAST -> addXthDayInterval(oldStart, original, true) + else -> oldStart.plusYears(repeatInterval / YEAR) + } + repeatInterval % MONTH == 0 -> when (repeatRule) { + REPEAT_SAME_DAY -> addMonthsWithSameDay(oldStart, original) + REPEAT_ORDER_WEEKDAY -> addXthDayInterval(oldStart, original, false) + REPEAT_ORDER_WEEKDAY_USE_LAST -> addXthDayInterval(oldStart, original, true) + else -> oldStart.plusMonths(repeatInterval / MONTH).dayOfMonth().withMaximumValue() + } + repeatInterval % WEEK == 0 -> { + // step through weekly repetition by days too, as events can trigger multiple times a week + oldStart.plusDays(1) + } + else -> oldStart.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(currStart.dayOfMonth) + } + 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( + Reminder(reminder1Minutes, reminder1Type), + Reminder(reminder2Minutes, reminder2Type), + Reminder(reminder3Minutes, reminder3Type) + ).filter { it.minutes != REMINDER_OFF } + + // properly return the start time of all-day events as midnight + fun getEventStartTS(): Long { + 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 + + // check if it's the proper week, for events repeating every x weeks + // get the week number since 1970, not just in the current year + fun isOnProperWeek(startTimes: LongSparseArray): Boolean { + val initialWeekNumber = Formatter.getDateTimeFromTS(startTimes[id!!]!!).withTimeAtStartOfDay().millis / (7 * 24 * 60 * 60 * 1000f) + val currentWeekNumber = Formatter.getDateTimeFromTS(startTS).withTimeAtStartOfDay().millis / (7 * 24 * 60 * 60 * 1000f) + return (Math.round(initialWeekNumber) - Math.round(currentWeekNumber)) % (repeatInterval / WEEK) == 0 + } + + fun updateIsPastEvent() { + val endTSToCheck = if (startTS < getNowSeconds() && getIsAllDay()) { + Formatter.getDayEndTS(Formatter.getDayCodeFromTS(endTS)) + } else { + endTS + } + isPastEvent = endTSToCheck < getNowSeconds() + } + + fun addRepetitionException(daycode: String) { + var newRepetitionExceptions = repetitionExceptions + newRepetitionExceptions.add(daycode) + newRepetitionExceptions = newRepetitionExceptions.distinct().toMutableList() as ArrayList + repetitionExceptions = newRepetitionExceptions + } + + var isPastEvent: Boolean + get() = flags and FLAG_IS_PAST_EVENT != 0 + set(isPastEvent) { + flags = flags.addBitIf(isPastEvent, FLAG_IS_PAST_EVENT) + } + + var color: Int = 0 + + fun getTimeZoneString(): String { + return if (timeZone.isNotEmpty() && getAllTimeZones().map { it.zoneName }.contains(timeZone)) { + timeZone + } else { + DateTimeZone.getDefault().id + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventRepetition.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventRepetition.kt new file mode 100644 index 000000000..7af651fc2 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventRepetition.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.calendar.pro.models + +data class EventRepetition(val repeatInterval: Int, val repeatRule: Int, val repeatLimit: Long) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventType.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventType.kt new file mode 100644 index 000000000..696e81436 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventType.kt @@ -0,0 +1,19 @@ +package com.simplemobiletools.calendar.pro.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity(tableName = "event_types", indices = [(Index(value = ["id"], unique = true))]) +data class EventType( + @PrimaryKey(autoGenerate = true) var id: Long?, + @ColumnInfo(name = "title") var title: String, + @ColumnInfo(name = "color") var color: Int, + @ColumnInfo(name = "caldav_calendar_id") var caldavCalendarId: Int = 0, + @ColumnInfo(name = "caldav_display_name") var caldavDisplayName: String = "", + @ColumnInfo(name = "caldav_email") var caldavEmail: String = "") { + fun getDisplayTitle() = if (caldavCalendarId == 0) title else "$caldavDisplayName ($caldavEmail)" + + fun isSyncedEventType() = caldavCalendarId != 0 +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventWeeklyView.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventWeeklyView.kt new file mode 100644 index 000000000..c57b55d5a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/EventWeeklyView.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.calendar.pro.models + +import android.util.Range + +data class EventWeeklyView(val id: Long, val range: Range) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListEvent.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListEvent.kt new file mode 100644 index 000000000..c04072bed --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListEvent.kt @@ -0,0 +1,4 @@ +package com.simplemobiletools.calendar.pro.models + +data class ListEvent(var id: Long, var startTS: Long, var endTS: Long, var title: String, var description: String, var isAllDay: Boolean, var color: Int, + var location: String, var isPastEvent: Boolean, var isRepeatable: Boolean) : ListItem() diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListItem.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListItem.kt new file mode 100644 index 000000000..73e9a3605 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListItem.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.calendar.pro.models + +open class ListItem diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListSection.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListSection.kt new file mode 100644 index 000000000..13803956a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/ListSection.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.calendar.pro.models + +data class ListSection(val title: String, val code: String, val isToday: Boolean, val isPastSection: Boolean) : ListItem() diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/MonthViewEvent.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/MonthViewEvent.kt new file mode 100644 index 000000000..24c861338 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/MonthViewEvent.kt @@ -0,0 +1,4 @@ +package com.simplemobiletools.calendar.pro.models + +data class MonthViewEvent(val id: Long, val title: String, val startTS: Long, val color: Int, val startDayIndex: Int, val daysCnt: Int, val originalStartDayIndex: Int, + val isAllDay: Boolean, val isPastEvent: Boolean) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/MyTimeZone.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/MyTimeZone.kt new file mode 100644 index 000000000..3c32bd322 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/MyTimeZone.kt @@ -0,0 +1,10 @@ +package com.simplemobiletools.calendar.pro.models + +import java.io.Serializable + +// sample MyTimeZone(title="GMT+1", zoneName="Europe/Bratislava") +data class MyTimeZone(var title: String, val zoneName: String) : Serializable { + companion object { + private const val serialVersionUID = -32456354132688616L + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Reminder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Reminder.kt new file mode 100644 index 000000000..6f9b34f5b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Reminder.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.calendar.pro.models + +data class Reminder(val minutes: Int, val type: Int) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/objects/States.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/objects/States.kt new file mode 100644 index 000000000..45f7e9059 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/objects/States.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.calendar.pro.objects + +object States { + var isUpdatingCalDAV = false +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt new file mode 100644 index 000000000..876df8bd6 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/BootCompletedReceiver.kt @@ -0,0 +1,22 @@ +package com.simplemobiletools.calendar.pro.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.simplemobiletools.calendar.pro.extensions.notifyRunningEvents +import com.simplemobiletools.calendar.pro.extensions.recheckCalDAVCalendars +import com.simplemobiletools.calendar.pro.extensions.scheduleAllEvents +import com.simplemobiletools.commons.helpers.ensureBackgroundThread + +class BootCompletedReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + ensureBackgroundThread { + context.apply { + scheduleAllEvents() + notifyRunningEvents() + recheckCalDAVCalendars {} + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/CalDAVSyncReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/CalDAVSyncReceiver.kt new file mode 100644 index 000000000..b5260463a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/CalDAVSyncReceiver.kt @@ -0,0 +1,21 @@ +package com.simplemobiletools.calendar.pro.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.extensions.recheckCalDAVCalendars +import com.simplemobiletools.calendar.pro.extensions.refreshCalDAVCalendars +import com.simplemobiletools.calendar.pro.extensions.updateWidgets + +class CalDAVSyncReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (context.config.caldavSync) { + context.refreshCalDAVCalendars(context.config.caldavSyncedCalendarIds, false) + } + + context.recheckCalDAVCalendars { + context.updateWidgets() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/NotificationReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/NotificationReceiver.kt new file mode 100644 index 000000000..ae5fafc40 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/NotificationReceiver.kt @@ -0,0 +1,44 @@ +package com.simplemobiletools.calendar.pro.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.PowerManager +import com.simplemobiletools.calendar.pro.extensions.eventsDB +import com.simplemobiletools.calendar.pro.extensions.notifyEvent +import com.simplemobiletools.calendar.pro.extensions.scheduleNextEventReminder +import com.simplemobiletools.calendar.pro.extensions.updateListWidget +import com.simplemobiletools.calendar.pro.helpers.EVENT_ID +import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.helpers.REMINDER_NOTIFICATION +import com.simplemobiletools.commons.helpers.ensureBackgroundThread + +class NotificationReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "simplecalendar:notificationreceiver") + wakelock.acquire(3000) + + ensureBackgroundThread { + handleIntent(context, intent) + } + } + + private fun handleIntent(context: Context, intent: Intent) { + val id = intent.getLongExtra(EVENT_ID, -1L) + if (id == -1L) { + return + } + + context.updateListWidget() + val event = context.eventsDB.getEventWithId(id) + if (event == null || event.getReminders().none { it.type == REMINDER_NOTIFICATION } || event.repetitionExceptions.contains(Formatter.getTodayCode())) { + return + } + + if (!event.repetitionExceptions.contains(Formatter.getDayCodeFromTS(event.startTS))) { + context.notifyEvent(event) + } + context.scheduleNextEventReminder(event, false) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/SnoozeService.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/SnoozeService.kt new file mode 100644 index 000000000..344c6628b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/SnoozeService.kt @@ -0,0 +1,18 @@ +package com.simplemobiletools.calendar.pro.services + +import android.app.IntentService +import android.content.Intent +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 + +class SnoozeService : IntentService("Snooze") { + override fun onHandleIntent(intent: Intent?) { + if (intent != null) { + val eventId = intent.getLongExtra(EVENT_ID, 0L) + val event = eventsDB.getEventWithId(eventId) + rescheduleReminder(event, config.snoozeTime) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/services/WidgetService.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/WidgetService.kt similarity index 63% rename from app/src/main/kotlin/com/simplemobiletools/calendar/services/WidgetService.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/WidgetService.kt index d0f80454e..e2216733c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/services/WidgetService.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/WidgetService.kt @@ -1,8 +1,8 @@ -package com.simplemobiletools.calendar.services +package com.simplemobiletools.calendar.pro.services import android.content.Intent import android.widget.RemoteViewsService -import com.simplemobiletools.calendar.adapters.EventListWidgetAdapter +import com.simplemobiletools.calendar.pro.adapters.EventListWidgetAdapter class WidgetService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent) = EventListWidgetAdapter(applicationContext) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/WidgetServiceEmpty.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/WidgetServiceEmpty.kt new file mode 100644 index 000000000..f29ccd84a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/WidgetServiceEmpty.kt @@ -0,0 +1,9 @@ +package com.simplemobiletools.calendar.pro.services + +import android.content.Intent +import android.widget.RemoteViewsService +import com.simplemobiletools.calendar.pro.adapters.EventListWidgetAdapterEmpty + +class WidgetServiceEmpty : RemoteViewsService() { + override fun onGetViewFactory(intent: Intent) = EventListWidgetAdapterEmpty(applicationContext) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MonthView.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MonthView.kt new file mode 100644 index 000000000..9a1d52c60 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MonthView.kt @@ -0,0 +1,351 @@ +package com.simplemobiletools.calendar.pro.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.text.TextPaint +import android.text.TextUtils +import android.util.AttributeSet +import android.util.SparseIntArray +import android.view.View +import com.simplemobiletools.calendar.pro.R +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.LOW_ALPHA +import com.simplemobiletools.calendar.pro.helpers.MEDIUM_ALPHA +import com.simplemobiletools.calendar.pro.models.DayMonthly +import com.simplemobiletools.calendar.pro.models.Event +import com.simplemobiletools.calendar.pro.models.MonthViewEvent +import com.simplemobiletools.commons.extensions.adjustAlpha +import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor +import com.simplemobiletools.commons.extensions.getContrastColor +import com.simplemobiletools.commons.extensions.moveLastItemToFront +import org.joda.time.DateTime +import org.joda.time.Days + +// used in the Monthly view fragment, 1 view per screen +class MonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(context, attrs, defStyle) { + private val BG_CORNER_RADIUS = 8f + private val ROW_COUNT = 6 + + private var paint: Paint + private var eventTitlePaint: TextPaint + private var gridPaint: Paint + private var config = context.config + private var dayWidth = 0f + private var dayHeight = 0f + private var primaryColor = 0 + private var textColor = 0 + private var weekDaysLetterHeight = 0 + private var eventTitleHeight = 0 + private var currDayOfWeek = 0 + private var smallPadding = 0 + private var maxEventsPerDay = 0 + private var horizontalOffset = 0 + private var showWeekNumbers = false + private var dimPastEvents = true + private var allEvents = ArrayList() + private var bgRectF = RectF() + private var dayLetters = ArrayList() + private var days = ArrayList() + private var dayVerticalOffsets = SparseIntArray() + + constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) + + init { + primaryColor = context.getAdjustedPrimaryColor() + textColor = config.textColor + showWeekNumbers = config.showWeekNumbers + dimPastEvents = config.dimPastEvents + + smallPadding = resources.displayMetrics.density.toInt() + val normalTextSize = resources.getDimensionPixelSize(R.dimen.normal_text_size) + weekDaysLetterHeight = normalTextSize * 2 + + paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = textColor + textSize = normalTextSize.toFloat() + textAlign = Paint.Align.CENTER + } + + gridPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = textColor.adjustAlpha(LOW_ALPHA) + } + + val smallerTextSize = resources.getDimensionPixelSize(R.dimen.smaller_text_size) + eventTitleHeight = smallerTextSize + eventTitlePaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = textColor + textSize = smallerTextSize.toFloat() + textAlign = Paint.Align.LEFT + } + + initWeekDayLetters() + setupCurrentDayOfWeekIndex() + } + + fun updateDays(newDays: ArrayList) { + days = newDays + showWeekNumbers = config.showWeekNumbers + horizontalOffset = if (showWeekNumbers) eventTitleHeight * 2 else 0 + initWeekDayLetters() + setupCurrentDayOfWeekIndex() + groupAllEvents() + invalidate() + } + + private fun groupAllEvents() { + days.forEach { + val day = it + day.dayEvents.forEach { + val event = it + + // make sure we properly handle events lasting multiple days and repeating ones + val lastEvent = allEvents.lastOrNull { it.id == event.id } + val daysCnt = getEventLastingDaysCount(event) + val validDayEvent = isDayValid(event, day.code) + if ((lastEvent == null || lastEvent.startDayIndex + daysCnt <= day.indexOnMonthView) && !validDayEvent) { + val monthViewEvent = MonthViewEvent(event.id!!, event.title, event.startTS, event.color, day.indexOnMonthView, + daysCnt, day.indexOnMonthView, event.getIsAllDay(), event.isPastEvent) + allEvents.add(monthViewEvent) + } + } + } + + allEvents = allEvents.asSequence().sortedWith(compareBy({ -it.daysCnt }, { !it.isAllDay }, { it.startTS }, { it.startDayIndex }, { it.title })) + .toMutableList() as ArrayList + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + dayVerticalOffsets.clear() + measureDaySize(canvas) + + if (config.showGrid) { + drawGrid(canvas) + } + + addWeekDayLetters(canvas) + if (showWeekNumbers && days.isNotEmpty()) { + addWeekNumbers(canvas) + } + + var curId = 0 + for (y in 0 until ROW_COUNT) { + for (x in 0..6) { + val day = days.getOrNull(curId) + if (day != null) { + dayVerticalOffsets.put(day.indexOnMonthView, dayVerticalOffsets[day.indexOnMonthView] + weekDaysLetterHeight) + val verticalOffset = dayVerticalOffsets[day.indexOnMonthView] + val xPos = x * dayWidth + horizontalOffset + val yPos = y * dayHeight + verticalOffset + val xPosCenter = xPos + dayWidth / 2 + if (day.isToday) { + canvas.drawCircle(xPosCenter, yPos + paint.textSize * 0.7f, paint.textSize * 0.75f, getCirclePaint(day)) + } + + canvas.drawText(day.value.toString(), xPosCenter, yPos + paint.textSize, getTextPaint(day)) + dayVerticalOffsets.put(day.indexOnMonthView, (verticalOffset + paint.textSize * 2).toInt()) + } + curId++ + } + } + + for (event in allEvents) { + drawEvent(event, canvas) + } + } + + private fun drawGrid(canvas: Canvas) { + // vertical lines + for (i in 0..6) { + var lineX = i * dayWidth + if (showWeekNumbers) { + lineX += horizontalOffset + } + canvas.drawLine(lineX, 0f, lineX, canvas.height.toFloat(), gridPaint) + } + + // horizontal lines + canvas.drawLine(0f, 0f, canvas.width.toFloat(), 0f, gridPaint) + for (i in 0..5) { + canvas.drawLine(0f, i * dayHeight + weekDaysLetterHeight, canvas.width.toFloat(), i * dayHeight + weekDaysLetterHeight, gridPaint) + } + } + + private fun addWeekDayLetters(canvas: Canvas) { + for (i in 0..6) { + val xPos = horizontalOffset + (i + 1) * dayWidth - dayWidth / 2 + var weekDayLetterPaint = paint + if (i == currDayOfWeek) { + weekDayLetterPaint = getColoredPaint(primaryColor) + } + canvas.drawText(dayLetters[i], xPos, weekDaysLetterHeight * 0.7f, weekDayLetterPaint) + } + } + + private fun addWeekNumbers(canvas: Canvas) { + val weekNumberPaint = Paint(paint) + weekNumberPaint.textAlign = Paint.Align.RIGHT + + for (i in 0 until ROW_COUNT) { + val weekDays = days.subList(i * 7, i * 7 + 7) + weekNumberPaint.color = if (weekDays.any { it.isToday }) primaryColor else textColor + + // fourth day of the week determines the week of the year number + val weekOfYear = days.getOrNull(i * 7 + 3)?.weekOfYear ?: 1 + val id = "$weekOfYear:" + val yPos = i * dayHeight + weekDaysLetterHeight + canvas.drawText(id, horizontalOffset.toFloat() * 0.9f, yPos + paint.textSize, weekNumberPaint) + } + } + + private fun measureDaySize(canvas: Canvas) { + dayWidth = (canvas.width - horizontalOffset) / 7f + dayHeight = (canvas.height - weekDaysLetterHeight) / ROW_COUNT.toFloat() + val availableHeightForEvents = dayHeight.toInt() - weekDaysLetterHeight + maxEventsPerDay = availableHeightForEvents / eventTitleHeight + } + + private fun drawEvent(event: MonthViewEvent, canvas: Canvas) { + var verticalOffset = 0 + for (i in 0 until Math.min(event.daysCnt, 7 - event.startDayIndex % 7)) { + verticalOffset = Math.max(verticalOffset, dayVerticalOffsets[event.startDayIndex + i]) + } + val xPos = event.startDayIndex % 7 * dayWidth + horizontalOffset + val yPos = (event.startDayIndex / 7) * dayHeight + val xPosCenter = xPos + dayWidth / 2 + + if (verticalOffset - eventTitleHeight * 2 > dayHeight) { + canvas.drawText("...", xPosCenter, yPos + verticalOffset - eventTitleHeight / 2, getTextPaint(days[event.startDayIndex])) + return + } + + // event background rectangle + val backgroundY = yPos + verticalOffset + val bgLeft = xPos + smallPadding + val bgTop = backgroundY + smallPadding - eventTitleHeight + var bgRight = xPos - smallPadding + dayWidth * event.daysCnt + val bgBottom = backgroundY + smallPadding * 2 + if (bgRight > canvas.width.toFloat()) { + bgRight = canvas.width.toFloat() - smallPadding + val newStartDayIndex = (event.startDayIndex / 7 + 1) * 7 + if (newStartDayIndex < 42) { + val newEvent = event.copy(startDayIndex = newStartDayIndex, daysCnt = event.daysCnt - (newStartDayIndex - event.startDayIndex)) + drawEvent(newEvent, canvas) + } + } + + val startDayIndex = days[event.originalStartDayIndex] + val endDayIndex = days[Math.min(event.startDayIndex + event.daysCnt - 1, 41)] + bgRectF.set(bgLeft, bgTop, bgRight, bgBottom) + canvas.drawRoundRect(bgRectF, BG_CORNER_RADIUS, BG_CORNER_RADIUS, getEventBackgroundColor(event, startDayIndex, endDayIndex)) + + drawEventTitle(event, canvas, xPos, yPos + verticalOffset, bgRight - bgLeft - smallPadding, startDayIndex, endDayIndex) + + for (i in 0 until Math.min(event.daysCnt, 7 - event.startDayIndex % 7)) { + dayVerticalOffsets.put(event.startDayIndex + i, verticalOffset + eventTitleHeight + smallPadding * 2) + } + } + + private fun drawEventTitle(event: MonthViewEvent, canvas: Canvas, x: Float, y: Float, availableWidth: Float, startDay: DayMonthly, endDay: DayMonthly) { + val ellipsized = TextUtils.ellipsize(event.title, eventTitlePaint, availableWidth - smallPadding, TextUtils.TruncateAt.END) + canvas.drawText(event.title, 0, ellipsized.length, x + smallPadding * 2, y, getEventTitlePaint(event, startDay, endDay)) + } + + private fun getTextPaint(startDay: DayMonthly): Paint { + var paintColor = textColor + if (startDay.isToday) { + paintColor = primaryColor.getContrastColor() + } + + if (!startDay.isThisMonth) { + paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA) + } + + return getColoredPaint(paintColor) + } + + private fun getColoredPaint(color: Int): Paint { + val curPaint = Paint(paint) + curPaint.color = color + return curPaint + } + + private fun getEventBackgroundColor(event: MonthViewEvent, startDay: DayMonthly, endDay: DayMonthly): Paint { + var paintColor = event.color + if ((!startDay.isThisMonth && !endDay.isThisMonth) || (dimPastEvents && event.isPastEvent)) { + paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA) + } + + return getColoredPaint(paintColor) + } + + private fun getEventTitlePaint(event: MonthViewEvent, startDay: DayMonthly, endDay: DayMonthly): Paint { + var paintColor = event.color.getContrastColor() + if ((!startDay.isThisMonth && !endDay.isThisMonth) || (dimPastEvents && event.isPastEvent)) { + paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA) + } + + val curPaint = Paint(eventTitlePaint) + curPaint.color = paintColor + return curPaint + } + + private fun getCirclePaint(day: DayMonthly): Paint { + val curPaint = Paint(paint) + var paintColor = primaryColor + if (!day.isThisMonth) { + paintColor = paintColor.adjustAlpha(MEDIUM_ALPHA) + } + curPaint.color = paintColor + return curPaint + } + + private fun initWeekDayLetters() { + dayLetters = context.resources.getStringArray(R.array.week_day_letters).toMutableList() as ArrayList + if (config.isSundayFirst) { + dayLetters.moveLastItemToFront() + } + } + + private fun setupCurrentDayOfWeekIndex() { + if (days.firstOrNull { it.isToday && it.isThisMonth } == null) { + currDayOfWeek = -1 + return + } + + currDayOfWeek = DateTime().dayOfWeek + if (config.isSundayFirst) { + currDayOfWeek %= 7 + } else { + currDayOfWeek-- + } + } + + // take into account cases when an event starts on the previous screen, subtract those days + private fun getEventLastingDaysCount(event: Event): Int { + val startDateTime = Formatter.getDateTimeFromTS(event.startTS) + val endDateTime = Formatter.getDateTimeFromTS(event.endTS) + val code = days.first().code + val screenStartDateTime = Formatter.getDateTimeFromCode(code).toLocalDate() + var eventStartDateTime = Formatter.getDateTimeFromTS(startDateTime.seconds()).toLocalDate() + val eventEndDateTime = Formatter.getDateTimeFromTS(endDateTime.seconds()).toLocalDate() + val diff = Days.daysBetween(screenStartDateTime, eventStartDateTime).days + if (diff < 0) { + eventStartDateTime = screenStartDateTime + } + + val isMidnight = Formatter.getDateTimeFromTS(endDateTime.seconds()) == Formatter.getDateTimeFromTS(endDateTime.seconds()).withTimeAtStartOfDay() + val numDays = Days.daysBetween(eventStartDateTime, eventEndDateTime).days + val daysCnt = if (numDays == 1 && isMidnight) 0 else numDays + return daysCnt + 1 + } + + private fun isDayValid(event: Event, code: String): Boolean { + val date = Formatter.getDateTimeFromCode(code) + return event.startTS != event.endTS && Formatter.getDateTimeFromTS(event.endTS) == Formatter.getDateTimeFromTS(date.seconds()).withTimeAtStartOfDay() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MonthViewWrapper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MonthViewWrapper.kt new file mode 100644 index 000000000..de291cb25 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MonthViewWrapper.kt @@ -0,0 +1,99 @@ +package com.simplemobiletools.calendar.pro.views + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.models.DayMonthly +import com.simplemobiletools.commons.extensions.onGlobalLayout +import kotlinx.android.synthetic.main.month_view.view.* + +// used in the Monthly view fragment, 1 view per screen +class MonthViewWrapper(context: Context, attrs: AttributeSet, defStyle: Int) : FrameLayout(context, attrs, defStyle) { + private var dayWidth = 0f + private var dayHeight = 0f + private var weekDaysLetterHeight = 0 + private var horizontalOffset = 0 + private var wereViewsAdded = false + private var days = ArrayList() + private var inflater: LayoutInflater + private var monthView: MonthView + private var dayClickCallback: ((day: DayMonthly) -> Unit)? = null + + constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) + + init { + val normalTextSize = resources.getDimensionPixelSize(R.dimen.normal_text_size).toFloat() + weekDaysLetterHeight = 2 * normalTextSize.toInt() + + inflater = LayoutInflater.from(context) + monthView = inflater.inflate(R.layout.month_view, this).month_view + setupHorizontalOffset() + + onGlobalLayout { + if (!wereViewsAdded && days.isNotEmpty()) { + measureSizes() + addViews() + monthView.updateDays(days) + } + } + } + + fun updateDays(newDays: ArrayList, callback: ((DayMonthly) -> Unit)? = null) { + setupHorizontalOffset() + measureSizes() + dayClickCallback = callback + days = newDays + if (dayWidth != 0f && dayHeight != 0f) { + addViews() + } + monthView.updateDays(days) + } + + private fun setupHorizontalOffset() { + horizontalOffset = if (context.config.showWeekNumbers) resources.getDimensionPixelSize(R.dimen.smaller_text_size) * 2 else 0 + } + + private fun measureSizes() { + dayWidth = (width - horizontalOffset) / 7f + + // avoid updating the height when coming back from a new event screen, when the keyboard was visible + val newHeight = (height - weekDaysLetterHeight) / 6f + if (newHeight > dayHeight) { + dayHeight = (height - weekDaysLetterHeight) / 6f + } + } + + private fun addViews() { + removeAllViews() + monthView = inflater.inflate(R.layout.month_view, this).month_view + wereViewsAdded = true + var curId = 0 + for (y in 0..5) { + for (x in 0..6) { + val day = days.getOrNull(curId) + if (day != null) { + val xPos = x * dayWidth + horizontalOffset + val yPos = y * dayHeight + weekDaysLetterHeight + addViewBackground(xPos, yPos, day) + } + curId++ + } + } + } + + private fun addViewBackground(xPos: Float, yPos: Float, day: DayMonthly) { + inflater.inflate(R.layout.month_view_background, this, false).apply { + layoutParams.width = dayWidth.toInt() + layoutParams.height = dayHeight.toInt() + x = xPos + y = yPos + setOnClickListener { + dayClickCallback?.invoke(day) + } + addView(this) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/views/MyScrollView.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MyScrollView.kt similarity index 64% rename from app/src/main/kotlin/com/simplemobiletools/calendar/views/MyScrollView.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MyScrollView.kt index 54c850882..0d5246157 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/views/MyScrollView.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/MyScrollView.kt @@ -1,10 +1,13 @@ -package com.simplemobiletools.calendar.views +package com.simplemobiletools.calendar.pro.views import android.content.Context import android.util.AttributeSet +import android.view.MotionEvent import android.widget.ScrollView class MyScrollView : ScrollView { + var isScrollable = true + constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) @@ -22,6 +25,22 @@ class MyScrollView : ScrollView { scrollViewListener?.onScrollChanged(this, x, y, oldx, oldy) } + override fun onTouchEvent(event: MotionEvent): Boolean { + return if (isScrollable) { + super.onTouchEvent(event) + } else { + true + } + } + + override fun onInterceptTouchEvent(event: MotionEvent): Boolean { + return if (isScrollable) { + super.onInterceptTouchEvent(event) + } else { + false + } + } + interface ScrollViewListener { fun onScrollChanged(scrollView: MyScrollView, x: Int, y: Int, oldx: Int, oldy: Int) } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/views/SmallMonthView.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/SmallMonthView.kt similarity index 77% rename from app/src/main/kotlin/com/simplemobiletools/calendar/views/SmallMonthView.kt rename to app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/SmallMonthView.kt index 51467e1ac..117563411 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/views/SmallMonthView.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/SmallMonthView.kt @@ -1,4 +1,4 @@ -package com.simplemobiletools.calendar.views +package com.simplemobiletools.calendar.pro.views import android.content.Context import android.content.res.Configuration @@ -6,13 +6,15 @@ import android.graphics.Canvas import android.graphics.Paint import android.util.AttributeSet import android.view.View -import com.simplemobiletools.calendar.R -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.helpers.MEDIUM_ALPHA -import com.simplemobiletools.calendar.models.DayYearly +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.helpers.MEDIUM_ALPHA +import com.simplemobiletools.calendar.pro.models.DayYearly import com.simplemobiletools.commons.extensions.adjustAlpha +import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor import java.util.* +// used for displaying months at Yearly view class SmallMonthView(context: Context, attrs: AttributeSet, defStyle: Int) : View(context, attrs, defStyle) { private var paint: Paint private var todayCirclePaint: Paint @@ -39,9 +41,9 @@ class SmallMonthView(context: Context, attrs: AttributeSet, defStyle: Int) : Vie init { val attributes = context.theme.obtainStyledAttributes( - attrs, - R.styleable.SmallMonthView, - 0, 0) + attrs, + R.styleable.SmallMonthView, + 0, 0) try { days = attributes.getInt(R.styleable.SmallMonthView_days, 31) @@ -59,7 +61,7 @@ class SmallMonthView(context: Context, attrs: AttributeSet, defStyle: Int) : Vie } todayCirclePaint = Paint(paint) - todayCirclePaint.color = context.config.primaryColor.adjustAlpha(MEDIUM_ALPHA) + todayCirclePaint.color = context.getAdjustedPrimaryColor().adjustAlpha(MEDIUM_ALPHA) isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE } @@ -67,9 +69,9 @@ class SmallMonthView(context: Context, attrs: AttributeSet, defStyle: Int) : Vie super.onDraw(canvas) if (dayWidth == 0f) { dayWidth = if (isLandscape) { - (canvas.width / 9).toFloat() + width / 9f } else { - (canvas.width / 7).toFloat() + width / 7f } } @@ -77,11 +79,11 @@ class SmallMonthView(context: Context, attrs: AttributeSet, defStyle: Int) : Vie for (y in 1..6) { for (x in 1..7) { if (curId in 1..days) { - canvas.drawText(curId.toString(), x * dayWidth, y * dayWidth, getPaint(curId)) + canvas.drawText(curId.toString(), x * dayWidth - (dayWidth / 4), y * dayWidth, getPaint(curId)) if (curId == todaysId) { val dividerConstant = if (isLandscape) 6 else 4 - canvas.drawCircle(x * dayWidth - dayWidth / dividerConstant, y * dayWidth - dayWidth / dividerConstant, dayWidth * 0.41f, todayCirclePaint) + canvas.drawCircle(x * dayWidth - dayWidth / 2, y * dayWidth - dayWidth / dividerConstant, dayWidth * 0.41f, todayCirclePaint) } } curId++ diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/WeeklyViewGrid.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/WeeklyViewGrid.kt new file mode 100644 index 000000000..f4d1ee6a1 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/views/WeeklyViewGrid.kt @@ -0,0 +1,36 @@ +package com.simplemobiletools.calendar.pro.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.getWeeklyViewItemHeight + +class WeeklyViewGrid(context: Context, attrs: AttributeSet, defStyle: Int) : View(context, attrs, defStyle) { + private val ROWS_CNT = 24 + private val COLS_CNT = 7 + private var paint = Paint(Paint.ANTI_ALIAS_FLAG) + + constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) + + init { + paint.color = context.resources.getColor(R.color.divider_grey) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + val rowHeight = context.getWeeklyViewItemHeight() + for (i in 0 until ROWS_CNT) { + val y = rowHeight * i.toFloat() + canvas.drawLine(0f, y, width.toFloat(), y, paint) + } + + val rowWidth = width / COLS_CNT.toFloat() + for (i in 0 until COLS_CNT) { + val x = rowWidth * i.toFloat() + canvas.drawLine(x, 0f, x, height.toFloat(), paint) + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/BootCompletedReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/BootCompletedReceiver.kt deleted file mode 100644 index 96c163240..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/BootCompletedReceiver.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.simplemobiletools.calendar.receivers - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import com.simplemobiletools.calendar.extensions.notifyRunningEvents -import com.simplemobiletools.calendar.extensions.recheckCalDAVCalendars -import com.simplemobiletools.calendar.extensions.scheduleAllEvents - -class BootCompletedReceiver : BroadcastReceiver() { - - override fun onReceive(context: Context, arg1: Intent) { - context.apply { - scheduleAllEvents() - notifyRunningEvents() - recheckCalDAVCalendars {} - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/CalDAVSyncReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/CalDAVSyncReceiver.kt deleted file mode 100644 index 1bc2cbb96..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/CalDAVSyncReceiver.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.simplemobiletools.calendar.receivers - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import com.simplemobiletools.calendar.extensions.recheckCalDAVCalendars - -class CalDAVSyncReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - context.recheckCalDAVCalendars {} - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/NotificationReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/NotificationReceiver.kt deleted file mode 100644 index 69fd394ce..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/receivers/NotificationReceiver.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.simplemobiletools.calendar.receivers - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.os.PowerManager -import com.simplemobiletools.calendar.extensions.dbHelper -import com.simplemobiletools.calendar.extensions.notifyEvent -import com.simplemobiletools.calendar.extensions.scheduleAllEvents -import com.simplemobiletools.calendar.extensions.updateListWidget -import com.simplemobiletools.calendar.helpers.EVENT_ID -import com.simplemobiletools.calendar.helpers.Formatter - -class NotificationReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager - val wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Simple Calendar") - wakelock.acquire(5000) - - context.updateListWidget() - val id = intent.getIntExtra(EVENT_ID, -1) - if (id == -1) - return - - val event = context.dbHelper.getEventWithId(id) - if (event == null || event.getReminders().isEmpty()) - return - - if (!event.ignoreEventOccurrences.contains(Formatter.getDayCodeFromTS(event.startTS).toInt())) { - context.notifyEvent(event) - } - context.scheduleAllEvents() - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/services/SnoozeService.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/services/SnoozeService.kt deleted file mode 100644 index 03f06176b..000000000 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/services/SnoozeService.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.simplemobiletools.calendar.services - -import android.app.IntentService -import android.app.NotificationManager -import android.content.Context -import android.content.Intent -import com.simplemobiletools.calendar.extensions.config -import com.simplemobiletools.calendar.extensions.dbHelper -import com.simplemobiletools.calendar.extensions.scheduleEventIn -import com.simplemobiletools.calendar.helpers.EVENT_ID - -class SnoozeService : IntentService("Snooze") { - override fun onHandleIntent(intent: Intent) { - val eventId = intent.getIntExtra(EVENT_ID, 0) - val event = dbHelper.getEventWithId(eventId) - - if (eventId != 0 && event != null) { - applicationContext.scheduleEventIn(System.currentTimeMillis() + applicationContext.config.snoozeDelay * 60000, event) - val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - manager.cancel(eventId) - } - } -} diff --git a/app/src/main/res/drawable-hdpi/ic_bell.png b/app/src/main/res/drawable-hdpi/ic_bell.png deleted file mode 100644 index 7e2f86f20..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_bell.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_calendar.png b/app/src/main/res/drawable-hdpi/ic_calendar.png deleted file mode 100644 index fd7bdc0a5..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_calendar.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_change_view.png b/app/src/main/res/drawable-hdpi/ic_change_view.png deleted file mode 100644 index cc86415c0..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_change_view.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_check_green.png b/app/src/main/res/drawable-hdpi/ic_check_green.png new file mode 100644 index 000000000..cb997b53e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_check_green.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_clock.png b/app/src/main/res/drawable-hdpi/ic_clock.png deleted file mode 100644 index 4651478fc..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_clock.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_color.png b/app/src/main/res/drawable-hdpi/ic_color.png deleted file mode 100644 index 9470e79e1..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_color.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_cross_red.png b/app/src/main/res/drawable-hdpi/ic_cross_red.png new file mode 100644 index 000000000..cc3c03c5c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cross_red.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_pointer_left.png b/app/src/main/res/drawable-hdpi/ic_pointer_left.png deleted file mode 100644 index e8fdcb49c..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_pointer_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_pointer_right.png b/app/src/main/res/drawable-hdpi/ic_pointer_right.png deleted file mode 100644 index 95856c86c..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_pointer_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_question_yellow.png b/app/src/main/res/drawable-hdpi/ic_question_yellow.png new file mode 100644 index 000000000..923e656dd Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_question_yellow.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_repeat.png b/app/src/main/res/drawable-hdpi/ic_repeat.png deleted file mode 100644 index dc81e8535..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_repeat.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_snooze.png b/app/src/main/res/drawable-hdpi/ic_snooze.png deleted file mode 100644 index 43e170721..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_snooze.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_today.png b/app/src/main/res/drawable-hdpi/ic_today.png deleted file mode 100644 index 73a5f63d7..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_today.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/img_widget_date_preview.png b/app/src/main/res/drawable-hdpi/img_widget_date_preview.png new file mode 100644 index 000000000..3e354bb29 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/img_widget_date_preview.png differ diff --git a/app/src/main/res/drawable-hdpi/monthly_event_dot.png b/app/src/main/res/drawable-hdpi/monthly_event_dot.png deleted file mode 100644 index dab21e01b..000000000 Binary files a/app/src/main/res/drawable-hdpi/monthly_event_dot.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/monthly_today_circle.png b/app/src/main/res/drawable-hdpi/monthly_today_circle.png deleted file mode 100644 index cdc17dbb8..000000000 Binary files a/app/src/main/res/drawable-hdpi/monthly_today_circle.png and /dev/null differ diff --git a/app/src/main/res/drawable-v26/ic_launcher_foreground.xml b/app/src/main/res/drawable-v26/ic_launcher_foreground.xml deleted file mode 100644 index 4156652d3..000000000 --- a/app/src/main/res/drawable-v26/ic_launcher_foreground.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable-xhdpi/ic_bell.png b/app/src/main/res/drawable-xhdpi/ic_bell.png deleted file mode 100644 index 66df52290..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_bell.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_calendar.png b/app/src/main/res/drawable-xhdpi/ic_calendar.png deleted file mode 100644 index 2e3cd13c4..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_calendar.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_change_view.png b/app/src/main/res/drawable-xhdpi/ic_change_view.png deleted file mode 100644 index 6c49e9167..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_change_view.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_check_green.png b/app/src/main/res/drawable-xhdpi/ic_check_green.png new file mode 100644 index 000000000..d5e90a672 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_check_green.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_clock.png b/app/src/main/res/drawable-xhdpi/ic_clock.png deleted file mode 100644 index 52cf597b0..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_clock.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_color.png b/app/src/main/res/drawable-xhdpi/ic_color.png deleted file mode 100644 index 4af10a4d0..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_color.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cross_red.png b/app/src/main/res/drawable-xhdpi/ic_cross_red.png new file mode 100644 index 000000000..a1fc51b8d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cross_red.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pointer_left.png b/app/src/main/res/drawable-xhdpi/ic_pointer_left.png deleted file mode 100644 index 932903fa4..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_pointer_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pointer_right.png b/app/src/main/res/drawable-xhdpi/ic_pointer_right.png deleted file mode 100644 index 09bff9fc6..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_pointer_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_question_yellow.png b/app/src/main/res/drawable-xhdpi/ic_question_yellow.png new file mode 100644 index 000000000..1b1c9b1f8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_question_yellow.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_repeat.png b/app/src/main/res/drawable-xhdpi/ic_repeat.png deleted file mode 100644 index 61d33ffbf..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_repeat.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_snooze.png b/app/src/main/res/drawable-xhdpi/ic_snooze.png deleted file mode 100644 index e27af11a0..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_snooze.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_today.png b/app/src/main/res/drawable-xhdpi/ic_today.png deleted file mode 100644 index 76c89b9f9..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_today.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/img_widget_date_preview.png b/app/src/main/res/drawable-xhdpi/img_widget_date_preview.png new file mode 100644 index 000000000..a73237a63 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/img_widget_date_preview.png differ diff --git a/app/src/main/res/drawable-xhdpi/monthly_event_dot.png b/app/src/main/res/drawable-xhdpi/monthly_event_dot.png deleted file mode 100644 index bd2aa7f71..000000000 Binary files a/app/src/main/res/drawable-xhdpi/monthly_event_dot.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/monthly_today_circle.png b/app/src/main/res/drawable-xhdpi/monthly_today_circle.png deleted file mode 100644 index 7cf5ffb0f..000000000 Binary files a/app/src/main/res/drawable-xhdpi/monthly_today_circle.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_bell.png b/app/src/main/res/drawable-xxhdpi/ic_bell.png deleted file mode 100644 index 75fb54865..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_bell.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_calendar.png b/app/src/main/res/drawable-xxhdpi/ic_calendar.png deleted file mode 100644 index 0e3a7cf03..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_calendar.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_change_view.png b/app/src/main/res/drawable-xxhdpi/ic_change_view.png deleted file mode 100644 index 919d083ee..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_change_view.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_check_green.png b/app/src/main/res/drawable-xxhdpi/ic_check_green.png new file mode 100644 index 000000000..3c2c589fc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_check_green.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_clock.png b/app/src/main/res/drawable-xxhdpi/ic_clock.png deleted file mode 100644 index cf9551f91..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_clock.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_color.png b/app/src/main/res/drawable-xxhdpi/ic_color.png deleted file mode 100644 index 119b1fd8c..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_color.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cross_red.png b/app/src/main/res/drawable-xxhdpi/ic_cross_red.png new file mode 100644 index 000000000..5651f41ca Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cross_red.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_pointer_left.png b/app/src/main/res/drawable-xxhdpi/ic_pointer_left.png deleted file mode 100644 index fcdff76a2..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_pointer_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_pointer_right.png b/app/src/main/res/drawable-xxhdpi/ic_pointer_right.png deleted file mode 100644 index 75cf3776f..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_pointer_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_question_yellow.png b/app/src/main/res/drawable-xxhdpi/ic_question_yellow.png new file mode 100644 index 000000000..313832456 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_question_yellow.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_repeat.png b/app/src/main/res/drawable-xxhdpi/ic_repeat.png deleted file mode 100644 index 74eaa0c44..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_repeat.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_snooze.png b/app/src/main/res/drawable-xxhdpi/ic_snooze.png deleted file mode 100644 index 1761d04aa..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_snooze.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_today.png b/app/src/main/res/drawable-xxhdpi/ic_today.png deleted file mode 100644 index d5175f037..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_today.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/img_widget_date_preview.png b/app/src/main/res/drawable-xxhdpi/img_widget_date_preview.png new file mode 100644 index 000000000..5d1a63cb5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/img_widget_date_preview.png differ diff --git a/app/src/main/res/drawable-xxhdpi/monthly_event_dot.png b/app/src/main/res/drawable-xxhdpi/monthly_event_dot.png deleted file mode 100644 index e2fe43259..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/monthly_event_dot.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/monthly_today_circle.png b/app/src/main/res/drawable-xxhdpi/monthly_today_circle.png deleted file mode 100644 index 1fdd4fc7f..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/monthly_today_circle.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_bell.png b/app/src/main/res/drawable-xxxhdpi/ic_bell.png deleted file mode 100644 index 10f0c0523..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_bell.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_calendar.png b/app/src/main/res/drawable-xxxhdpi/ic_calendar.png deleted file mode 100644 index 47247db25..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_calendar.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_change_view.png b/app/src/main/res/drawable-xxxhdpi/ic_change_view.png deleted file mode 100644 index fa45db74c..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_change_view.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_check_green.png b/app/src/main/res/drawable-xxxhdpi/ic_check_green.png new file mode 100644 index 000000000..707b53598 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_check_green.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clock.png b/app/src/main/res/drawable-xxxhdpi/ic_clock.png deleted file mode 100644 index f156937ad..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_clock.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_color.png b/app/src/main/res/drawable-xxxhdpi/ic_color.png deleted file mode 100644 index dba40d4eb..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_color.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_cross_red.png b/app/src/main/res/drawable-xxxhdpi/ic_cross_red.png new file mode 100644 index 000000000..fb9f2e945 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_cross_red.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pointer_left.png b/app/src/main/res/drawable-xxxhdpi/ic_pointer_left.png deleted file mode 100644 index ef5bc95b0..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_pointer_left.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pointer_right.png b/app/src/main/res/drawable-xxxhdpi/ic_pointer_right.png deleted file mode 100644 index 6abf6dbfa..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_pointer_right.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_question_yellow.png b/app/src/main/res/drawable-xxxhdpi/ic_question_yellow.png new file mode 100644 index 000000000..175b6ebe6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_question_yellow.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat.png deleted file mode 100644 index f5beca251..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_repeat.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_snooze.png b/app/src/main/res/drawable-xxxhdpi/ic_snooze.png deleted file mode 100644 index c3cdb9288..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_snooze.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_today.png b/app/src/main/res/drawable-xxxhdpi/ic_today.png deleted file mode 100644 index 37e5996b6..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_today.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/img_widget_date_preview.png b/app/src/main/res/drawable-xxxhdpi/img_widget_date_preview.png new file mode 100644 index 000000000..5d1a63cb5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/img_widget_date_preview.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/monthly_event_dot.png b/app/src/main/res/drawable-xxxhdpi/monthly_event_dot.png deleted file mode 100644 index 76df61cba..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/monthly_event_dot.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/monthly_today_circle.png b/app/src/main/res/drawable-xxxhdpi/monthly_today_circle.png deleted file mode 100644 index bbd71f592..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/monthly_today_circle.png and /dev/null differ diff --git a/app/src/main/res/drawable/attendee_status_circular_background.xml b/app/src/main/res/drawable/attendee_status_circular_background.xml new file mode 100644 index 000000000..e540e6cca --- /dev/null +++ b/app/src/main/res/drawable/attendee_status_circular_background.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/circle_empty.xml b/app/src/main/res/drawable/circle_empty.xml deleted file mode 100644 index 767a935c0..000000000 --- a/app/src/main/res/drawable/circle_empty.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/divider_width.xml b/app/src/main/res/drawable/divider_width.xml index d0c567b33..04e97ef39 100644 --- a/app/src/main/res/drawable/divider_width.xml +++ b/app/src/main/res/drawable/divider_width.xml @@ -7,6 +7,6 @@ android:width="5000dp" android:height="1dp"/> - + diff --git a/app/src/main/res/drawable/event_list_color_bar.xml b/app/src/main/res/drawable/event_list_color_bar.xml new file mode 100644 index 000000000..05d2b9550 --- /dev/null +++ b/app/src/main/res/drawable/event_list_color_bar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_calendar_vector.xml b/app/src/main/res/drawable/ic_calendar_vector.xml new file mode 100644 index 000000000..f07e6b2c5 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_change_view_vector.xml b/app/src/main/res/drawable/ic_change_view_vector.xml new file mode 100644 index 000000000..4f0f114cd --- /dev/null +++ b/app/src/main/res/drawable/ic_change_view_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_color_vector.xml b/app/src/main/res/drawable/ic_color_vector.xml new file mode 100644 index 000000000..929631494 --- /dev/null +++ b/app/src/main/res/drawable/ic_color_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_globe_vector.xml b/app/src/main/res/drawable/ic_globe_vector.xml new file mode 100644 index 000000000..80757b91b --- /dev/null +++ b/app/src/main/res/drawable/ic_globe_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_today_vector.xml b/app/src/main/res/drawable/ic_today_vector.xml new file mode 100644 index 000000000..bac56e0ed --- /dev/null +++ b/app/src/main/res/drawable/ic_today_vector.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/monthly_day_dot.xml b/app/src/main/res/drawable/monthly_day_dot.xml deleted file mode 100644 index 1daa66144..000000000 --- a/app/src/main/res/drawable/monthly_day_dot.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/app/src/main/res/drawable/monthly_day_with_event.xml b/app/src/main/res/drawable/monthly_day_with_event.xml deleted file mode 100644 index 424e4a6dd..000000000 --- a/app/src/main/res/drawable/monthly_day_with_event.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/monthly_day_with_event_today.xml b/app/src/main/res/drawable/monthly_day_with_event_today.xml deleted file mode 100644 index 5871a7163..000000000 --- a/app/src/main/res/drawable/monthly_day_with_event_today.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/stroke_bottom.xml b/app/src/main/res/drawable/stroke_bottom.xml index cbd44ae6c..c229316f9 100644 --- a/app/src/main/res/drawable/stroke_bottom.xml +++ b/app/src/main/res/drawable/stroke_bottom.xml @@ -2,15 +2,15 @@ + android:left="-1px" + android:right="-1px" + android:top="-1px"> + android:color="@color/divider_grey"/> diff --git a/app/src/main/res/drawable/stroke_bottom_right.xml b/app/src/main/res/drawable/stroke_bottom_right.xml index a3ac94652..46574e93e 100644 --- a/app/src/main/res/drawable/stroke_bottom_right.xml +++ b/app/src/main/res/drawable/stroke_bottom_right.xml @@ -2,15 +2,15 @@ + android:top="-1px"> + android:color="@color/divider_grey"/> diff --git a/app/src/main/res/drawable/stroke_right.xml b/app/src/main/res/drawable/stroke_right.xml index 1269abf2b..ae1dbec32 100644 --- a/app/src/main/res/drawable/stroke_right.xml +++ b/app/src/main/res/drawable/stroke_right.xml @@ -1,16 +1,16 @@ + android:top="-1px"> + android:color="@color/divider_grey"/> diff --git a/app/src/main/res/layout-land/fragment_year.xml b/app/src/main/res/layout-land/fragment_year.xml index bf36cbba7..444c16bb8 100644 --- a/app/src/main/res/layout-land/fragment_year.xml +++ b/app/src/main/res/layout-land/fragment_year.xml @@ -1,6 +1,5 @@ - + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> @@ -40,8 +37,8 @@ android:id="@+id/month_2_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_half" - android:layout_marginRight="@dimen/yearly_padding_half" + android:layout_marginStart="@dimen/yearly_padding_half" + android:layout_marginEnd="@dimen/yearly_padding_half" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> @@ -67,7 +63,6 @@ android:id="@+id/month_3_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_full" android:layout_marginStart="@dimen/yearly_padding_full" android:layout_weight="1"> @@ -78,15 +73,14 @@ android:gravity="center_horizontal" android:text="@string/march" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> @@ -123,8 +115,8 @@ android:id="@+id/month_5_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_half" - android:layout_marginRight="@dimen/yearly_padding_half" + android:layout_marginStart="@dimen/yearly_padding_half" + android:layout_marginEnd="@dimen/yearly_padding_half" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> @@ -150,7 +141,6 @@ android:id="@+id/month_6_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_full" android:layout_marginStart="@dimen/yearly_padding_full" android:layout_weight="1"> @@ -161,16 +151,15 @@ android:gravity="center_horizontal" android:text="@string/june" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> @@ -233,7 +219,6 @@ android:id="@+id/month_9_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_full" android:layout_marginStart="@dimen/yearly_padding_full" android:layout_weight="1"> @@ -244,16 +229,15 @@ android:gravity="center_horizontal" android:text="@string/september" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> @@ -324,15 +304,14 @@ android:gravity="center_horizontal" android:text="@string/december" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + android:layout_marginStart="@dimen/yearly_month_padding" /> diff --git a/app/src/main/res/layout/activity_day.xml b/app/src/main/res/layout/activity_day.xml index b484be2ad..8f6045c82 100644 --- a/app/src/main/res/layout/activity_day.xml +++ b/app/src/main/res/layout/activity_day.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/activity_event.xml b/app/src/main/res/layout/activity_event.xml index ebb196339..7d34c0e94 100644 --- a/app/src/main/res/layout/activity_event.xml +++ b/app/src/main/res/layout/activity_event.xml @@ -14,14 +14,14 @@ + + + android:src="@drawable/ic_clock_vector"/> @@ -106,14 +113,12 @@ android:id="@+id/event_start_date" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignLeft="@+id/event_all_day" - android:layout_alignStart="@+id/event_all_day" android:layout_below="@+id/event_time_image" + android:layout_alignStart="@+id/event_all_day" android:background="?attr/selectableItemBackground" - android:paddingBottom="@dimen/activity_margin" - android:paddingEnd="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/activity_margin" + android:paddingBottom="@dimen/activity_margin" android:textSize="@dimen/day_text_size" tools:text="January 1 1970"/> @@ -121,9 +126,8 @@ android:id="@+id/event_start_time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_below="@+id/event_time_image" + android:layout_alignParentEnd="true" android:background="?attr/selectableItemBackground" android:padding="@dimen/activity_margin" android:textSize="@dimen/day_text_size" @@ -133,14 +137,12 @@ android:id="@+id/event_end_date" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignLeft="@+id/event_all_day" - android:layout_alignStart="@+id/event_all_day" android:layout_below="@+id/event_start_date" + android:layout_alignStart="@+id/event_all_day" android:background="?attr/selectableItemBackground" - android:paddingBottom="@dimen/activity_margin" - android:paddingEnd="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/activity_margin" + android:paddingBottom="@dimen/activity_margin" android:textSize="@dimen/day_text_size" tools:text="January 1 1970"/> @@ -148,125 +150,189 @@ android:id="@+id/event_end_time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_below="@+id/event_start_time" + android:layout_alignParentEnd="true" android:background="?attr/selectableItemBackground" android:padding="@dimen/activity_margin" android:textSize="@dimen/day_text_size" tools:text="00:00"/> + + + + + android:src="@drawable/ic_bell_vector"/> + android:paddingBottom="@dimen/activity_margin" + android:textSize="@dimen/day_text_size" + tools:text="@string/add_another_reminder"/> + + + android:visibility="gone" + tools:text="@string/add_another_reminder"/> + + + android:visibility="gone" + tools:text="@string/add_another_reminder"/> + + + android:src="@drawable/ic_repeat_vector"/> + android:paddingBottom="@dimen/normal_margin" + android:textSize="@dimen/day_text_size" + tools:text="@string/no_repetition"/> @@ -275,8 +341,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="false" - android:paddingBottom="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin" + android:paddingBottom="@dimen/activity_margin" android:text="@string/repeat_on" android:textSize="@dimen/day_text_size"/> @@ -285,13 +351,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_toEndOf="@+id/event_repetition_rule_label" - android:layout_toRightOf="@+id/event_repetition_rule_label" android:clickable="false" - android:ellipsize="end" android:gravity="end" - android:lines="1" android:padding="@dimen/activity_margin" android:text="@string/every_day" android:textSize="@dimen/day_text_size"/> @@ -301,9 +363,8 @@ android:id="@+id/event_repetition_limit_holder" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignLeft="@+id/event_repetition" - android:layout_alignStart="@+id/event_repetition" android:layout_below="@+id/event_repetition_rule_holder" + android:layout_alignStart="@+id/event_repetition" android:background="?attr/selectableItemBackground" android:visibility="gone"> @@ -311,11 +372,10 @@ android:id="@+id/event_repetition_limit_label" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_toLeftOf="@+id/event_repetition_limit" android:layout_toStartOf="@+id/event_repetition_limit" android:clickable="false" - android:paddingBottom="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin" + android:paddingBottom="@dimen/activity_margin" android:text="@string/repeat_till" android:textSize="@dimen/day_text_size"/> @@ -324,7 +384,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:clickable="false" android:padding="@dimen/activity_margin" android:text="@string/forever" @@ -337,32 +396,57 @@ android:layout_height="1px" android:layout_below="@+id/event_repetition_limit_holder" android:layout_marginTop="@dimen/medium_margin" - android:background="@color/darker_divider" + android:background="@color/divider_grey" + android:importantForAccessibility="no"/> + + + + + + @@ -370,12 +454,13 @@ android:id="@+id/event_caldav_calendar_name" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin" + android:layout_toStartOf="@+id/event_caldav_calendar_color" android:ellipsize="end" android:maxLines="1" - android:paddingBottom="@dimen/tiny_margin" android:paddingTop="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:paddingBottom="@dimen/tiny_margin" android:textSize="@dimen/day_text_size" tools:text="My calendar"/> @@ -384,14 +469,24 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/event_caldav_calendar_name" - android:layout_marginLeft="@dimen/small_margin" android:layout_marginStart="@dimen/small_margin" + android:layout_toStartOf="@+id/event_caldav_calendar_color" android:ellipsize="end" android:maxLines="1" + android:paddingEnd="@dimen/medium_margin" android:paddingBottom="@dimen/medium_margin" android:textSize="@dimen/meta_text_size" tools:text="hello@simplemobiletools.com"/> + + @@ -407,14 +502,12 @@ android:id="@+id/event_type_image" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_alignBottom="@+id/event_type_holder" - android:layout_alignTop="@+id/event_type_holder" android:layout_below="@+id/event_caldav_calendar_divider" - android:layout_marginLeft="@dimen/normal_margin" + android:layout_alignTop="@+id/event_type_holder" + android:layout_alignBottom="@+id/event_type_holder" android:layout_marginStart="@dimen/normal_margin" - android:alpha="0.8" android:padding="@dimen/medium_margin" - android:src="@drawable/ic_color"/> + android:src="@drawable/ic_color_vector"/> @@ -456,8 +546,7 @@ android:layout_width="match_parent" android:layout_height="1px" android:layout_below="@+id/event_type_holder" - android:layout_marginTop="@dimen/medium_margin" - android:background="@color/darker_divider" + android:background="@color/divider_grey" android:importantForAccessibility="no"/> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1978800a1..344bf2153 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,70 +1,21 @@ - - + android:layout_height="match_parent"> - + android:layout_height="match_parent" /> - - - - - - - - - - - - - - - - + + app:rippleColor="@color/pressed_item_foreground" /> - + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_manage_event_types.xml b/app/src/main/res/layout/activity_manage_event_types.xml index eb17f556f..c4b9c9f86 100644 --- a/app/src/main/res/layout/activity_manage_event_types.xml +++ b/app/src/main/res/layout/activity_manage_event_types.xml @@ -6,4 +6,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" - app:layoutManager="android.support.v7.widget.LinearLayoutManager"/> + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> diff --git a/app/src/main/res/layout/activity_select_time_zone.xml b/app/src/main/res/layout/activity_select_time_zone.xml new file mode 100644 index 000000000..f0c745c97 --- /dev/null +++ b/app/src/main/res/layout/activity_select_time_zone.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 0dc111729..54cc36913 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -1,6 +1,6 @@ - @@ -17,16 +17,18 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/customize_colors" /> @@ -36,16 +38,18 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/manage_event_types" /> @@ -55,7 +59,10 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/use_english_language" /> @@ -75,7 +81,10 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> - - - - - - + android:text="@string/use_24_hour_time_format" /> @@ -115,7 +103,10 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:padding="@dimen/activity_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/sunday_first" /> + + + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/vibrate" /> + + + + + + @@ -155,124 +179,130 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/medium_margin" android:background="?attr/selectableItemBackground" - android:paddingBottom="@dimen/bigger_margin" - android:paddingLeft="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" - android:paddingTop="@dimen/bigger_margin"> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/reminder_sound" /> + android:clickable="false" + android:gravity="end" + android:maxLines="3" + tools:text="None" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + android:layout_toStartOf="@+id/settings_reminder_audio_stream" + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/reminder_stream" /> + android:clickable="false" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> - - - + android:clickable="false" + android:paddingStart="@dimen/medium_margin" + android:text="@string/use_same_snooze" /> - + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/medium_margin" + android:background="?attr/selectableItemBackground" + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + + + + + + + android:textSize="@dimen/smaller_text_size" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/caldav_sync" /> + + + + + + @@ -299,10 +353,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" - android:paddingBottom="@dimen/bigger_margin" - android:paddingLeft="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" + android:paddingStart="@dimen/normal_margin" android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin" android:visibility="gone"> + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/manage_synced_calendars" /> - + + + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:textSize="@dimen/smaller_text_size" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/start_day_at" /> + android:clickable="false" /> - - - - - - - - - - + android:textSize="@dimen/smaller_text_size" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + android:text="@string/week_numbers" /> - + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/activity_margin"> + + + + + android:textSize="@dimen/smaller_text_size" /> + + + + + + + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/display_past_events" /> + android:clickable="false" /> - - + android:textSize="@dimen/smaller_text_size" /> + android:paddingStart="@dimen/normal_margin" + android:paddingTop="@dimen/bigger_margin" + android:paddingEnd="@dimen/normal_margin" + android:paddingBottom="@dimen/bigger_margin"> + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + android:text="@string/font_size" /> + android:clickable="false" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/calendar_item_account.xml b/app/src/main/res/layout/calendar_item_account.xml index 96c206a8d..568f5bdcb 100644 --- a/app/src/main/res/layout/calendar_item_account.xml +++ b/app/src/main/res/layout/calendar_item_account.xml @@ -1,16 +1,13 @@ - + android:textSize="@dimen/normal_text_size" + tools:text="Account" /> diff --git a/app/src/main/res/layout/calendar_item_calendar.xml b/app/src/main/res/layout/calendar_item_calendar.xml index 4aefba01c..e6feacf8a 100644 --- a/app/src/main/res/layout/calendar_item_calendar.xml +++ b/app/src/main/res/layout/calendar_item_calendar.xml @@ -14,8 +14,8 @@ android:background="@null" android:clickable="false" android:paddingBottom="@dimen/activity_margin" - android:paddingLeft="@dimen/big_margin" - android:paddingRight="@dimen/activity_margin" + android:paddingStart="@dimen/big_margin" + android:paddingEnd="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin" tools:text="Calendar name"/> diff --git a/app/src/main/res/layout/day_monthly_event_view.xml b/app/src/main/res/layout/day_monthly_event_view.xml index 1f703c368..bada70437 100644 --- a/app/src/main/res/layout/day_monthly_event_view.xml +++ b/app/src/main/res/layout/day_monthly_event_view.xml @@ -10,8 +10,8 @@ android:gravity="start" android:hyphenationFrequency="none" android:maxLines="1" - android:paddingLeft="@dimen/tiny_margin" - android:paddingRight="@dimen/tiny_margin" + android:paddingStart="@dimen/tiny_margin" + android:paddingEnd="@dimen/tiny_margin" android:textSize="@dimen/day_monthly_text_size" tools:targetApi="m" tools:text="1"/> diff --git a/app/src/main/res/layout/day_monthly_number_view.xml b/app/src/main/res/layout/day_monthly_number_view.xml index 57954f1ad..1ba84466e 100644 --- a/app/src/main/res/layout/day_monthly_number_view.xml +++ b/app/src/main/res/layout/day_monthly_number_view.xml @@ -5,12 +5,12 @@ android:id="@+id/day_monthly_number_id" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/tiny_margin" - android:layout_marginRight="@dimen/tiny_margin" + android:layout_marginStart="@dimen/tiny_margin" + android:layout_marginEnd="@dimen/tiny_margin" android:ellipsize="none" android:gravity="center_horizontal" android:maxLines="1" - android:paddingLeft="@dimen/tiny_margin" - android:paddingRight="@dimen/tiny_margin" + android:paddingStart="@dimen/tiny_margin" + android:paddingEnd="@dimen/tiny_margin" android:textSize="@dimen/normal_text_size" tools:text="1"/> diff --git a/app/src/main/res/layout/dialog_custom_event_reminder.xml b/app/src/main/res/layout/dialog_custom_event_reminder.xml deleted file mode 100644 index dd7d7d4ea..000000000 --- a/app/src/main/res/layout/dialog_custom_event_reminder.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/dialog_custom_event_repeat_interval.xml b/app/src/main/res/layout/dialog_custom_event_repeat_interval.xml index f4320996a..941b1d1e1 100644 --- a/app/src/main/res/layout/dialog_custom_event_repeat_interval.xml +++ b/app/src/main/res/layout/dialog_custom_event_repeat_interval.xml @@ -10,8 +10,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:paddingLeft="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" + android:paddingStart="@dimen/activity_margin" + android:paddingEnd="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin"> + + + android:paddingStart="@dimen/activity_margin" + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/activity_margin"> + android:paddingTop="@dimen/small_margin" + android:paddingEnd="@dimen/small_margin" + android:paddingBottom="@dimen/small_margin"/> - + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> diff --git a/app/src/main/res/layout/dialog_filter_event_types.xml b/app/src/main/res/layout/dialog_filter_event_types.xml index a77264d7b..5c55d9105 100644 --- a/app/src/main/res/layout/dialog_filter_event_types.xml +++ b/app/src/main/res/layout/dialog_filter_event_types.xml @@ -1,5 +1,5 @@ - + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> diff --git a/app/src/main/res/layout/dialog_import_events.xml b/app/src/main/res/layout/dialog_import_events.xml index fad9689b3..e555ba7b8 100644 --- a/app/src/main/res/layout/dialog_import_events.xml +++ b/app/src/main/res/layout/dialog_import_events.xml @@ -1,18 +1,19 @@ + android:paddingTop="@dimen/activity_margin" + android:paddingEnd="@dimen/activity_margin"> @@ -20,16 +21,19 @@ android:id="@+id/import_event_type_holder" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginStart="@dimen/activity_margin" android:background="?attr/selectableItemBackground" android:padding="@dimen/small_margin"> + android:layout_toStartOf="@+id/import_event_type_color" + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" + tools:text="@string/regular_event"/> + + + diff --git a/app/src/main/res/layout/dialog_repeat_limit_type_picker.xml b/app/src/main/res/layout/dialog_repeat_limit_type_picker.xml index 21d9a65e1..7f6662a54 100644 --- a/app/src/main/res/layout/dialog_repeat_limit_type_picker.xml +++ b/app/src/main/res/layout/dialog_repeat_limit_type_picker.xml @@ -33,7 +33,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/small_margin" - android:text="@string/repeat_x_times"/> + android:text="@string/stop_repeating_after_x"/> - - + android:layout_height="wrap_content"> - + + + + + diff --git a/app/src/main/res/layout/dialog_select_event_type_color.xml b/app/src/main/res/layout/dialog_select_event_type_color.xml new file mode 100644 index 000000000..f994eb843 --- /dev/null +++ b/app/src/main/res/layout/dialog_select_event_type_color.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_select_radio_group.xml b/app/src/main/res/layout/dialog_select_radio_group.xml index 0da0e43d9..67d4642f8 100644 --- a/app/src/main/res/layout/dialog_select_radio_group.xml +++ b/app/src/main/res/layout/dialog_select_radio_group.xml @@ -10,8 +10,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="@dimen/normal_margin" - android:paddingLeft="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" + android:paddingStart="@dimen/activity_margin" + android:paddingEnd="@dimen/activity_margin" android:paddingTop="@dimen/normal_margin"/> diff --git a/app/src/main/res/layout/dialog_set_reminders.xml b/app/src/main/res/layout/dialog_set_reminders.xml new file mode 100644 index 000000000..83295b799 --- /dev/null +++ b/app/src/main/res/layout/dialog_set_reminders.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_snooze_picker.xml b/app/src/main/res/layout/dialog_snooze_picker.xml deleted file mode 100644 index 84792d64e..000000000 --- a/app/src/main/res/layout/dialog_snooze_picker.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/dialog_vertical_linear_layout.xml b/app/src/main/res/layout/dialog_vertical_linear_layout.xml index 7fc649528..3e674324f 100644 --- a/app/src/main/res/layout/dialog_vertical_linear_layout.xml +++ b/app/src/main/res/layout/dialog_vertical_linear_layout.xml @@ -5,6 +5,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:paddingLeft="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" + android:paddingStart="@dimen/activity_margin" + android:paddingEnd="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin"/> diff --git a/app/src/main/res/layout/event_item_day_view.xml b/app/src/main/res/layout/event_item_day_view.xml index fb9cea008..58d767b35 100644 --- a/app/src/main/res/layout/event_item_day_view.xml +++ b/app/src/main/res/layout/event_item_day_view.xml @@ -9,68 +9,67 @@ android:clickable="true" android:focusable="true" android:foreground="@drawable/selector" - android:paddingLeft="@dimen/activity_margin"> + android:paddingStart="@dimen/activity_margin"> + android:paddingBottom="@dimen/normal_margin" + android:paddingTop="@dimen/normal_margin"> - + + - - - - - diff --git a/app/src/main/res/layout/event_item_day_view_simple.xml b/app/src/main/res/layout/event_item_day_view_simple.xml new file mode 100644 index 000000000..7a04b4ade --- /dev/null +++ b/app/src/main/res/layout/event_item_day_view_simple.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/event_list_item.xml b/app/src/main/res/layout/event_list_item.xml index e84ff8241..6e2b89743 100644 --- a/app/src/main/res/layout/event_list_item.xml +++ b/app/src/main/res/layout/event_list_item.xml @@ -9,70 +9,70 @@ android:clickable="true" android:focusable="true" android:foreground="@drawable/selector" - android:paddingLeft="@dimen/activity_margin"> + android:paddingStart="@dimen/activity_margin"> + android:paddingEnd="@dimen/activity_margin" + android:paddingTop="@dimen/medium_margin"> - + + - - - - - diff --git a/app/src/main/res/layout/event_list_item_simple.xml b/app/src/main/res/layout/event_list_item_simple.xml new file mode 100644 index 000000000..7a04b4ade --- /dev/null +++ b/app/src/main/res/layout/event_list_item_simple.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/event_list_item_widget.xml b/app/src/main/res/layout/event_list_item_widget.xml index f173999de..fa7b640ac 100644 --- a/app/src/main/res/layout/event_list_item_widget.xml +++ b/app/src/main/res/layout/event_list_item_widget.xml @@ -4,35 +4,47 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/event_item_holder" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:paddingBottom="@dimen/medium_margin"> + + + android:layout_toEndOf="@+id/event_item_color_bar" + android:text="13:00" + android:textSize="@dimen/day_text_size"/> @@ -40,26 +52,14 @@ android:id="@+id/event_item_description" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@+id/event_section_title" - android:layout_marginLeft="@dimen/big_margin" - android:layout_toLeftOf="@+id/event_item_color" - android:layout_toRightOf="@+id/event_item_end" - android:alpha=".4" + android:layout_below="@+id/event_item_title" + android:layout_marginStart="@dimen/normal_margin" + android:layout_toEndOf="@+id/event_item_end" android:ellipsize="end" + android:includeFontPadding="false" android:maxLines="1" - android:paddingRight="@dimen/activity_margin" + android:paddingEnd="@dimen/small_margin" android:textSize="@dimen/day_text_size" tools:text="Event description"/> - - diff --git a/app/src/main/res/layout/event_list_item_widget_simple.xml b/app/src/main/res/layout/event_list_item_widget_simple.xml new file mode 100644 index 000000000..2b5f30f4a --- /dev/null +++ b/app/src/main/res/layout/event_list_item_widget_simple.xml @@ -0,0 +1,41 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/event_list_section.xml b/app/src/main/res/layout/event_list_section.xml index 34106cde1..b154aff06 100644 --- a/app/src/main/res/layout/event_list_section.xml +++ b/app/src/main/res/layout/event_list_section.xml @@ -5,10 +5,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="false" - android:drawablePadding="1dp" android:drawableTop="@drawable/divider_width" + android:drawablePadding="@dimen/medium_margin" android:focusable="false" - android:paddingLeft="@dimen/activity_margin" + android:paddingStart="@dimen/activity_margin" android:paddingTop="@dimen/medium_margin" + android:textAllCaps="true" android:textSize="@dimen/normal_text_size" - android:textStyle="bold"/> + android:textStyle="bold" /> diff --git a/app/src/main/res/layout/event_list_section_widget.xml b/app/src/main/res/layout/event_list_section_widget.xml index 0a8f45071..fcfe6ad6c 100644 --- a/app/src/main/res/layout/event_list_section_widget.xml +++ b/app/src/main/res/layout/event_list_section_widget.xml @@ -6,6 +6,7 @@ android:layout_height="wrap_content" android:drawablePadding="1dp" android:drawableTop="@drawable/divider_width" - android:paddingTop="@dimen/medium_margin" + android:paddingBottom="@dimen/small_margin" + android:paddingTop="@dimen/small_margin" android:textSize="@dimen/normal_text_size" android:textStyle="bold"/> diff --git a/app/src/main/res/layout/event_list_widget.xml b/app/src/main/res/layout/event_list_widget.xml deleted file mode 100644 index 0fb1221e9..000000000 --- a/app/src/main/res/layout/event_list_widget.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/filter_event_type_view.xml b/app/src/main/res/layout/filter_event_type_view.xml index c4c1a1c34..fa1f5de8e 100644 --- a/app/src/main/res/layout/filter_event_type_view.xml +++ b/app/src/main/res/layout/filter_event_type_view.xml @@ -11,10 +11,10 @@ android:id="@+id/filter_event_type_checkbox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginRight="@dimen/medium_margin" - android:layout_toLeftOf="@+id/filter_event_type_color" + android:layout_marginEnd="@dimen/medium_margin" + android:layout_toStartOf="@+id/filter_event_type_color" android:clickable="false" - android:paddingLeft="@dimen/small_margin"/> + android:paddingStart="@dimen/small_margin"/> + android:layout_height="wrap_content" + android:paddingTop="@dimen/medium_margin"> + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> diff --git a/app/src/main/res/layout/stroke_horizontal_divider.xml b/app/src/main/res/layout/fragment_days_holder.xml similarity index 54% rename from app/src/main/res/layout/stroke_horizontal_divider.xml rename to app/src/main/res/layout/fragment_days_holder.xml index 924faf4a3..686ac134e 100644 --- a/app/src/main/res/layout/stroke_horizontal_divider.xml +++ b/app/src/main/res/layout/fragment_days_holder.xml @@ -1,7 +1,8 @@ - + android:clickable="true" + android:focusable="true"/> diff --git a/app/src/main/res/layout/fragment_event_list.xml b/app/src/main/res/layout/fragment_event_list.xml index b0a654c2d..12c629e6c 100644 --- a/app/src/main/res/layout/fragment_event_list.xml +++ b/app/src/main/res/layout/fragment_event_list.xml @@ -1,6 +1,5 @@ - + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager" /> + android:textSize="@dimen/bigger_text_size" + android:textStyle="italic" + android:visibility="gone" /> + + diff --git a/app/src/main/res/layout/fragment_month.xml b/app/src/main/res/layout/fragment_month.xml index 00aeee173..ae21a7f0a 100644 --- a/app/src/main/res/layout/fragment_month.xml +++ b/app/src/main/res/layout/fragment_month.xml @@ -1,505 +1,17 @@ + android:paddingTop="@dimen/medium_margin"> - - - + android:layout_below="@+id/top_left_arrow"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_month_widget.xml b/app/src/main/res/layout/fragment_month_widget.xml index 6a1677961..91fd1a898 100644 --- a/app/src/main/res/layout/fragment_month_widget.xml +++ b/app/src/main/res/layout/fragment_month_widget.xml @@ -1,13 +1,81 @@ - - + + + + + + + + + + + + tools:ignore="UnknownIdInLayout" /> + android:visibility="gone" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:visibility="gone" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:visibility="gone" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:visibility="gone" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:visibility="gone" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:visibility="gone" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> + android:orientation="vertical" /> diff --git a/app/src/main/res/layout/fragment_month_widget_config.xml b/app/src/main/res/layout/fragment_month_widget_config.xml new file mode 100644 index 000000000..a52b5393d --- /dev/null +++ b/app/src/main/res/layout/fragment_month_widget_config.xml @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_months_holder.xml b/app/src/main/res/layout/fragment_months_holder.xml new file mode 100644 index 000000000..6f11ba514 --- /dev/null +++ b/app/src/main/res/layout/fragment_months_holder.xml @@ -0,0 +1,8 @@ + + diff --git a/app/src/main/res/layout/fragment_week.xml b/app/src/main/res/layout/fragment_week.xml index 0ec204391..f352f0495 100644 --- a/app/src/main/res/layout/fragment_week.xml +++ b/app/src/main/res/layout/fragment_week.xml @@ -5,13 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - + android:layout_height="wrap_content"/> @@ -82,7 +75,7 @@ - + diff --git a/app/src/main/res/layout/fragment_week_holder.xml b/app/src/main/res/layout/fragment_week_holder.xml new file mode 100644 index 000000000..925869dad --- /dev/null +++ b/app/src/main/res/layout/fragment_week_holder.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_year.xml b/app/src/main/res/layout/fragment_year.xml index eda476cf2..c193376b0 100644 --- a/app/src/main/res/layout/fragment_year.xml +++ b/app/src/main/res/layout/fragment_year.xml @@ -1,6 +1,5 @@ - + android:textSize="@dimen/normal_text_size" /> - + android:layout_centerInParent="true" /> @@ -39,8 +37,8 @@ android:id="@+id/month_2_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_half" - android:layout_marginRight="@dimen/yearly_padding_half" + android:layout_marginStart="@dimen/yearly_padding_half" + android:layout_marginEnd="@dimen/yearly_padding_half" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + android:layout_below="@+id/month_2_label" /> @@ -64,7 +62,6 @@ android:id="@+id/month_3_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_full" android:layout_marginStart="@dimen/yearly_padding_full" android:layout_weight="1"> @@ -75,13 +72,13 @@ android:gravity="center_horizontal" android:text="@string/march" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + android:layout_below="@+id/month_3_label" /> @@ -93,7 +90,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/yearly_padding_full" - android:layout_marginRight="@dimen/yearly_padding_full" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> @@ -118,8 +114,8 @@ android:id="@+id/month_5_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_half" - android:layout_marginRight="@dimen/yearly_padding_half" + android:layout_marginStart="@dimen/yearly_padding_half" + android:layout_marginEnd="@dimen/yearly_padding_half" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + android:layout_below="@+id/month_5_label" /> @@ -143,7 +139,6 @@ android:id="@+id/month_6_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_full" android:layout_marginStart="@dimen/yearly_padding_full" android:layout_weight="1"> @@ -154,14 +149,14 @@ android:gravity="center_horizontal" android:text="@string/june" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> @@ -172,7 +167,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/yearly_padding_full" - android:layout_marginRight="@dimen/yearly_padding_full" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + android:layout_below="@+id/month_7_label" /> + android:textSize="@dimen/normal_text_size" /> - + android:layout_below="@+id/month_8_label" /> @@ -220,7 +214,6 @@ android:id="@+id/month_9_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_full" android:layout_marginStart="@dimen/yearly_padding_full" android:layout_weight="1"> @@ -231,14 +224,14 @@ android:gravity="center_horizontal" android:text="@string/september" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> @@ -250,7 +243,6 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginEnd="@dimen/yearly_padding_full" - android:layout_marginRight="@dimen/yearly_padding_full" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + android:layout_below="@+id/month_10_label" /> @@ -274,8 +266,8 @@ android:id="@+id/month_11_holder" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_marginLeft="@dimen/yearly_padding_half" - android:layout_marginRight="@dimen/yearly_padding_half" + android:layout_marginStart="@dimen/yearly_padding_half" + android:layout_marginEnd="@dimen/yearly_padding_half" android:layout_weight="1"> + android:textSize="@dimen/normal_text_size" /> - + app:days="30" /> @@ -312,13 +302,13 @@ android:gravity="center_horizontal" android:text="@string/december" android:textAllCaps="true" - android:textSize="@dimen/normal_text_size"/> + android:textSize="@dimen/normal_text_size" /> - + android:layout_below="@+id/month_12_label" /> diff --git a/app/src/main/res/layout/stroke_vertical_divider.xml b/app/src/main/res/layout/fragment_years_holder.xml similarity index 53% rename from app/src/main/res/layout/stroke_vertical_divider.xml rename to app/src/main/res/layout/fragment_years_holder.xml index afca637d1..c5ed08cd6 100644 --- a/app/src/main/res/layout/stroke_vertical_divider.xml +++ b/app/src/main/res/layout/fragment_years_holder.xml @@ -1,7 +1,8 @@ - + android:clickable="true" + android:focusable="true"/> diff --git a/app/src/main/res/layout/item_attendee.xml b/app/src/main/res/layout/item_attendee.xml new file mode 100644 index 000000000..99850f6e1 --- /dev/null +++ b/app/src/main/res/layout/item_attendee.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_autocomplete_email.xml b/app/src/main/res/layout/item_autocomplete_email.xml new file mode 100644 index 000000000..6545ebc38 --- /dev/null +++ b/app/src/main/res/layout/item_autocomplete_email.xml @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_autocomplete_email_name.xml b/app/src/main/res/layout/item_autocomplete_email_name.xml new file mode 100644 index 000000000..a98d0dfe1 --- /dev/null +++ b/app/src/main/res/layout/item_autocomplete_email_name.xml @@ -0,0 +1,57 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_event_type.xml b/app/src/main/res/layout/item_event_type.xml index a8ea05de1..dd6be586e 100644 --- a/app/src/main/res/layout/item_event_type.xml +++ b/app/src/main/res/layout/item_event_type.xml @@ -22,9 +22,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" - android:layout_toLeftOf="@+id/event_type_color" - android:paddingLeft="@dimen/medium_margin" - android:paddingRight="@dimen/medium_margin" + android:layout_toStartOf="@+id/event_type_color" + android:paddingStart="@dimen/medium_margin" + android:paddingEnd="@dimen/medium_margin" tools:text="Event type"/> + + + + + + + diff --git a/app/src/main/res/layout/month_view.xml b/app/src/main/res/layout/month_view.xml new file mode 100644 index 000000000..4e2e595b5 --- /dev/null +++ b/app/src/main/res/layout/month_view.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/month_view_background.xml b/app/src/main/res/layout/month_view_background.xml new file mode 100644 index 000000000..f7eb272d5 --- /dev/null +++ b/app/src/main/res/layout/month_view_background.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/layout/radio_button_with_color.xml b/app/src/main/res/layout/radio_button_with_color.xml index 581daf2a9..b97e51aa5 100644 --- a/app/src/main/res/layout/radio_button_with_color.xml +++ b/app/src/main/res/layout/radio_button_with_color.xml @@ -9,8 +9,8 @@ android:id="@+id/dialog_radio_button" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginRight="@dimen/medium_margin" - android:layout_toLeftOf="@+id/dialog_radio_color" + android:layout_marginEnd="@dimen/medium_margin" + android:layout_toStartOf="@+id/dialog_radio_color" android:clickable="false" android:paddingBottom="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin"/> diff --git a/app/src/main/res/layout/top_navigation.xml b/app/src/main/res/layout/top_navigation.xml index a3ada46ad..96eece1d7 100644 --- a/app/src/main/res/layout/top_navigation.xml +++ b/app/src/main/res/layout/top_navigation.xml @@ -8,22 +8,23 @@ style="@style/ArrowStyle" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_alignBottom="@+id/top_value" android:layout_alignTop="@+id/top_value" + android:layout_alignBottom="@+id/top_value" + android:autoMirrored="true" android:paddingLeft="@dimen/activity_margin" android:paddingRight="@dimen/activity_margin" - android:src="@drawable/ic_pointer_left"/> + android:src="@drawable/ic_chevron_left_vector"/> + android:src="@drawable/ic_chevron_right_vector"/> diff --git a/app/src/main/res/layout/top_navigation_widget.xml b/app/src/main/res/layout/top_navigation_widget.xml deleted file mode 100644 index 727ea8020..000000000 --- a/app/src/main/res/layout/top_navigation_widget.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/week_event_marker.xml b/app/src/main/res/layout/week_event_marker.xml index 7763a4c27..4e20dc2c0 100644 --- a/app/src/main/res/layout/week_event_marker.xml +++ b/app/src/main/res/layout/week_event_marker.xml @@ -5,7 +5,7 @@ android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="3" - android:paddingLeft="@dimen/tiny_margin" - android:paddingRight="@dimen/tiny_margin" + android:paddingStart="@dimen/tiny_margin" + android:paddingEnd="@dimen/tiny_margin" android:textColor="@android:color/white" android:textSize="@dimen/small_text_size"/> diff --git a/app/src/main/res/layout/week_grid_item.xml b/app/src/main/res/layout/week_grid_item.xml index 5c7925168..6abb0697c 100644 --- a/app/src/main/res/layout/week_grid_item.xml +++ b/app/src/main/res/layout/week_grid_item.xml @@ -5,4 +5,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerInside" - android:src="@drawable/ic_plus"/> + android:src="@drawable/ic_plus_vector"/> diff --git a/app/src/main/res/layout/weekly_view_hour_textview.xml b/app/src/main/res/layout/weekly_view_hour_textview.xml index eee8a9950..5cca0cdaa 100644 --- a/app/src/main/res/layout/weekly_view_hour_textview.xml +++ b/app/src/main/res/layout/weekly_view_hour_textview.xml @@ -1,10 +1,10 @@ + android:paddingStart="@dimen/small_margin" + android:paddingEnd="@dimen/small_margin" + android:textSize="@dimen/normal_text_size" /> diff --git a/app/src/main/res/layout/widget_config_date.xml b/app/src/main/res/layout/widget_config_date.xml new file mode 100644 index 000000000..7106281dc --- /dev/null +++ b/app/src/main/res/layout/widget_config_date.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + +