├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── .gitignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── capacitor.build.gradle │ ├── proguard-rules.pro │ ├── release │ │ └── output-metadata.json │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── getcapacitor │ │ │ └── myapp │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ ├── capacitor.config.json │ │ │ └── capacitor.plugins.json │ │ ├── java │ │ │ └── cyou │ │ │ │ └── sk5s │ │ │ │ └── app │ │ │ │ └── countdate │ │ │ │ ├── CountdateWidget.java │ │ │ │ ├── CountdateWidgetConfig.java │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-land-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-land-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-port-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── branding.png │ │ │ ├── ic_launcher_background.xml │ │ │ ├── icon.png │ │ │ ├── splash.png │ │ │ └── widget_preview_1.jpg │ │ │ ├── layout │ │ │ ├── activity_countdate_widget_config.xml │ │ │ ├── activity_main.xml │ │ │ ├── countdate_widget.xml │ │ │ └── row_layout.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ ├── color.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── config.xml │ │ │ ├── countdate_widget_info.xml │ │ │ └── file_paths.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── getcapacitor │ │ └── myapp │ │ └── ExampleUnitTest.java ├── build.gradle ├── capacitor.settings.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── variables.gradle ├── capacitor.config.ts ├── codemagic.yaml ├── countdateapp-logo-concentrate.png ├── fastlane └── metadata │ └── android │ └── en-US │ ├── full_description.txt │ ├── images │ ├── featureGraphic.png │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ └── 6.jpg │ ├── short_description.txt │ ├── title.txt │ └── video.txt ├── index.html ├── ionic.config.json ├── ios ├── .gitignore └── App │ ├── App.xcodeproj │ └── project.pbxproj │ ├── App.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── App │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon-512@2x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Splash.imageset │ │ │ ├── Contents.json │ │ │ ├── Default@1x~universal~anyany-dark.png │ │ │ ├── Default@1x~universal~anyany.png │ │ │ ├── Default@2x~universal~anyany-dark.png │ │ │ ├── Default@2x~universal~anyany.png │ │ │ ├── Default@3x~universal~anyany-dark.png │ │ │ └── Default@3x~universal~anyany.png │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ └── Info.plist │ ├── Podfile │ ├── Podfile.lock │ └── PrivacyInfo.xcprivacy ├── package-lock.json ├── package.json ├── public ├── 404.html ├── assets │ ├── icon │ │ ├── favicon.png │ │ └── icon.png │ └── tour │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ └── 8.jpg └── manifest.json ├── resources ├── android │ ├── icon │ │ ├── drawable-hdpi-icon.png │ │ ├── drawable-ldpi-icon.png │ │ ├── drawable-mdpi-icon.png │ │ ├── drawable-xhdpi-icon.png │ │ ├── drawable-xxhdpi-icon.png │ │ └── drawable-xxxhdpi-icon.png │ └── splash │ │ ├── drawable-land-hdpi-screen.png │ │ ├── drawable-land-ldpi-screen.png │ │ ├── drawable-land-mdpi-screen.png │ │ ├── drawable-land-xhdpi-screen.png │ │ ├── drawable-land-xxhdpi-screen.png │ │ ├── drawable-land-xxxhdpi-screen.png │ │ ├── drawable-port-hdpi-screen.png │ │ ├── drawable-port-ldpi-screen.png │ │ ├── drawable-port-mdpi-screen.png │ │ ├── drawable-port-xhdpi-screen.png │ │ ├── drawable-port-xxhdpi-screen.png │ │ └── drawable-port-xxxhdpi-screen.png ├── icon.png └── splash.png ├── src ├── .prettierignore ├── App.tsx ├── assets │ ├── countdate-count-tour.jpg │ ├── countdateapp-logo-background.png │ ├── countdateapp-logo-foreground-.png │ ├── countdateapp-logo-foreground.png │ ├── countdateapp-logo.png │ ├── countdateapp-splash.png │ └── sk5s-project-bar.png ├── components │ ├── AccentColorSelectModal.tsx │ ├── AppTour.tsx │ ├── CountCard.tsx │ ├── CountCards.tsx │ ├── CountDownUpSwitcher.tsx │ ├── CountdownItem.css │ ├── CountdownItem.tsx │ ├── DescriptionEditor.css │ ├── DescriptionEditor.tsx │ ├── EventDetailModal.tsx │ ├── LanguageSelectModal.tsx │ ├── LocalizeBackButton.tsx │ ├── TextColorSelectModal.css │ ├── TextColorSelectModal.tsx │ ├── TitleCard.tsx │ └── markdown-styles.module.css ├── constants │ └── Constants.ts ├── i18n │ ├── data │ │ ├── de.json │ │ ├── en.json │ │ ├── th.json │ │ ├── zh-CN.json │ │ └── zh-TW.json │ └── i18n.js ├── lib │ ├── Capitalize.js │ ├── Clipboard.js │ ├── Countdate.js │ ├── Darkmode.js │ ├── Events.js │ ├── LocalNotification.js │ ├── Toast.js │ └── storageKey.json ├── main.tsx ├── pages │ ├── About.tsx │ ├── Add.css │ ├── Add.tsx │ ├── AppUrlListener.tsx │ ├── Backup.tsx │ ├── Edit.tsx │ ├── Home.css │ ├── Home.tsx │ ├── Settings.css │ ├── Settings.tsx │ └── Share.tsx ├── theme │ └── variables.css └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: samko5sam 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | # https://raw.githubusercontent.com/ionic-team/ionic-framework/main/.github/ISSUE_TEMPLATE/bug_report.yml 2 | name: 🐛 Bug Report 3 | description: Create a report to help us improve the app 4 | title: "bug: " 5 | labels: ["bug"] 6 | body: 7 | - type: checkboxes 8 | attributes: 9 | label: Prerequisites 10 | description: Please ensure you have completed all of the following. 11 | options: 12 | - label: I have read the [Contributing Guidelines](https://github.com/sk5s/countdateapp#contributing). 13 | required: true 14 | - label: I agree to follow the [Code of Conduct](https://github.com/sk5s/countdateapp/blob/main/CODE_OF_CONDUCT.md). 15 | required: true 16 | - label: I have searched for [existing issues](https://github.com/sk5s/countdateapp/issues) that already include this feature request, without success. 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Countdate version 21 | description: Please enter which versions of Countdate this issue impacts. 22 | validations: 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Current Behavior 27 | description: A clear description of what the bug is and how it manifests. 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Expected Behavior 33 | description: A clear description of what you expected to happen. 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: Steps to Reproduce 39 | description: Please explain the steps required to duplicate this issue. 40 | validations: 41 | required: false 42 | - type: textarea 43 | attributes: 44 | label: Additional Information 45 | description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: 📢 Prefer English 4 | url: https://sk5s.com/ 5 | about: The preferred language for issues is English. Please use it if possible. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | # https://raw.githubusercontent.com/ionic-team/ionic-framework/main/.github/ISSUE_TEMPLATE/feature_request.yml 2 | name: 💡 Feature Request 3 | description: Suggest an idea for the project 4 | title: "feat: " 5 | labels: ["enhancement"] 6 | body: 7 | - type: checkboxes 8 | attributes: 9 | label: Prerequisites 10 | description: Please ensure you have completed all of the following. 11 | options: 12 | - label: I have read the [Contributing Guidelines](https://github.com/sk5s/countdateapp#contributing). 13 | required: true 14 | - label: I agree to follow the [Code of Conduct](https://github.com/sk5s/countdateapp/blob/main/CODE_OF_CONDUCT.md). 15 | required: true 16 | - label: I have searched for [existing issues](https://github.com/sk5s/countdateapp/issues) that already include this feature request, without success. 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Describe the Feature Request 21 | description: A clear and concise description of what the feature does. 22 | validations: 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Describe the Use Case 27 | description: A clear and concise use case for what problem this feature would solve. 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Describe Preferred Solution 33 | description: A clear and concise description of what you how you want this feature to be added to Ionic Framework. 34 | - type: textarea 35 | attributes: 36 | label: Describe Alternatives 37 | description: A clear and concise description of any alternative solutions or features you have considered. 38 | - type: textarea 39 | attributes: 40 | label: Related Code 41 | description: If you are able to illustrate the feature request with an example, please provide a sample code. 42 | - type: textarea 43 | attributes: 44 | label: Additional Information 45 | description: List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to implement, Stack Overflow links, forum links, etc. 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /dist 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .vscode 21 | .idea 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Optional eslint cache 28 | .eslintcache 29 | 30 | *.jks 31 | 32 | resources/android/**/* 33 | 34 | .firebaserc 35 | firebase.json 36 | .firebase -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | samko5sam@tutanota.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | 4 | 5 | 6 | 7 | Made with [contrib.rocks](https://contrib.rocks). 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 sk5s 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore 2 | 3 | # Built application files 4 | *.apk 5 | *.aar 6 | *.ap_ 7 | *.aab 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | # Uncomment the following line in case you need and you don't have the release build type files in your app 20 | # release/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | baselineProfiles/ 26 | 27 | # Local configuration file (sdk path, etc) 28 | local.properties 29 | 30 | # Proguard folder generated by Eclipse 31 | proguard/ 32 | 33 | # Log Files 34 | *.log 35 | 36 | # Android Studio Navigation editor temp files 37 | .navigation/ 38 | 39 | # Android Studio captures folder 40 | captures/ 41 | 42 | # IntelliJ 43 | *.iml 44 | .idea/workspace.xml 45 | .idea/tasks.xml 46 | .idea/gradle.xml 47 | .idea/assetWizardSettings.xml 48 | .idea/dictionaries 49 | .idea/libraries 50 | # Android Studio 3 in .gitignore file. 51 | .idea/caches 52 | .idea/modules.xml 53 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 54 | .idea/navEditor.xml 55 | 56 | # Keystore files 57 | # Uncomment the following lines if you do not want to check your keystore files in. 58 | #*.jks 59 | #*.keystore 60 | 61 | # External native build folder generated in Android Studio 2.2 and later 62 | .externalNativeBuild 63 | .cxx/ 64 | 65 | # Google Services (e.g. APIs or Firebase) 66 | # google-services.json 67 | 68 | # Freeline 69 | freeline.py 70 | freeline/ 71 | freeline_project_description.json 72 | 73 | # fastlane 74 | fastlane/report.xml 75 | fastlane/Preview.html 76 | fastlane/screenshots 77 | fastlane/test_output 78 | fastlane/readme.md 79 | 80 | # Version control 81 | vcs.xml 82 | 83 | # lint 84 | lint/intermediates/ 85 | lint/generated/ 86 | lint/outputs/ 87 | lint/tmp/ 88 | # lint/reports/ 89 | 90 | # Android Profiling 91 | *.hprof 92 | 93 | # Cordova plugins for Capacitor 94 | capacitor-cordova-android-plugins 95 | 96 | # Copied web assets 97 | app/src/main/assets/public 98 | # Generated Config files 99 | app/src/main/assets/capacitor.config.json 100 | app/src/main/assets/capacitor.plugins.json 101 | app/src/main/res/xml/config.xml 102 | -------------------------------------------------------------------------------- /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | namespace "cyou.sk5s.app.countdate" 5 | compileSdk rootProject.ext.compileSdkVersion 6 | defaultConfig { 7 | applicationId "cyou.sk5s.app.countdate" 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 36 11 | versionName "1.5.11" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | aaptOptions { 14 | // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. 15 | // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 16 | ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' 17 | } 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | dependenciesInfo { 26 | // Disables dependency metadata when building APKs. 27 | includeInApk = false 28 | // Disables dependency metadata when building Android App Bundles. 29 | includeInBundle = false 30 | } 31 | } 32 | 33 | repositories { 34 | flatDir{ 35 | dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation fileTree(include: ['*.jar'], dir: 'libs') 41 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" 42 | implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" 43 | implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" 44 | implementation project(':capacitor-android') 45 | // implementation 'com.google.android.material:material:1.5.0' 46 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 47 | testImplementation "junit:junit:$junitVersion" 48 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" 49 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" 50 | implementation project(':capacitor-cordova-android-plugins') 51 | } 52 | 53 | apply from: 'capacitor.build.gradle' 54 | 55 | try { 56 | def servicesJSON = file('google-services.json') 57 | if (servicesJSON.text) { 58 | apply plugin: 'com.google.gms.google-services' 59 | } 60 | } catch(Exception e) { 61 | logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") 62 | } 63 | -------------------------------------------------------------------------------- /android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_21 6 | targetCompatibility JavaVersion.VERSION_21 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | implementation project(':capacitor-app') 13 | implementation project(':capacitor-clipboard') 14 | implementation project(':capacitor-device') 15 | implementation project(':capacitor-filesystem') 16 | implementation project(':capacitor-keyboard') 17 | implementation project(':capacitor-local-notifications') 18 | implementation project(':capacitor-preferences') 19 | implementation project(':capacitor-share') 20 | implementation project(':capacitor-toast') 21 | 22 | } 23 | 24 | 25 | if (hasProperty('postBuildExtras')) { 26 | postBuildExtras() 27 | } 28 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android/app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "cyou.sk5s.app.countdate", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 36, 15 | "versionName": "1.5.11", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File", 20 | "baselineProfiles": [ 21 | { 22 | "minApi": 28, 23 | "maxApi": 30, 24 | "baselineProfiles": [ 25 | "baselineProfiles/1/app-release.dm" 26 | ] 27 | }, 28 | { 29 | "minApi": 31, 30 | "maxApi": 2147483647, 31 | "baselineProfiles": [ 32 | "baselineProfiles/0/app-release.dm" 33 | ] 34 | } 35 | ], 36 | "minSdkVersionForDexing": 23 37 | } -------------------------------------------------------------------------------- /android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /android/app/src/main/assets/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "cyou.sk5s.app.countdate", 3 | "appName": "Countdate", 4 | "webDir": "dist", 5 | "server": { 6 | "androidScheme": "https" 7 | }, 8 | "plugins": { 9 | "LocalNotifications": { 10 | "smallIcon": "countdateapp-logo", 11 | "iconColor": "#488AFF" 12 | }, 13 | "SplashScreen": { 14 | "backgroundColor": "#03989e" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /android/app/src/main/assets/capacitor.plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pkg": "@capacitor/app", 4 | "classpath": "com.capacitorjs.plugins.app.AppPlugin" 5 | }, 6 | { 7 | "pkg": "@capacitor/clipboard", 8 | "classpath": "com.capacitorjs.plugins.clipboard.ClipboardPlugin" 9 | }, 10 | { 11 | "pkg": "@capacitor/device", 12 | "classpath": "com.capacitorjs.plugins.device.DevicePlugin" 13 | }, 14 | { 15 | "pkg": "@capacitor/filesystem", 16 | "classpath": "com.capacitorjs.plugins.filesystem.FilesystemPlugin" 17 | }, 18 | { 19 | "pkg": "@capacitor/keyboard", 20 | "classpath": "com.capacitorjs.plugins.keyboard.KeyboardPlugin" 21 | }, 22 | { 23 | "pkg": "@capacitor/local-notifications", 24 | "classpath": "com.capacitorjs.plugins.localnotifications.LocalNotificationsPlugin" 25 | }, 26 | { 27 | "pkg": "@capacitor/preferences", 28 | "classpath": "com.capacitorjs.plugins.preferences.PreferencesPlugin" 29 | }, 30 | { 31 | "pkg": "@capacitor/share", 32 | "classpath": "com.capacitorjs.plugins.share.SharePlugin" 33 | }, 34 | { 35 | "pkg": "@capacitor/toast", 36 | "classpath": "com.capacitorjs.plugins.toast.ToastPlugin" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/cyou/sk5s/app/countdate/CountdateWidget.java: -------------------------------------------------------------------------------- 1 | package cyou.sk5s.app.countdate; 2 | 3 | import static cyou.sk5s.app.countdate.CountdateWidgetConfig.KEY_DATA; 4 | import static cyou.sk5s.app.countdate.CountdateWidgetConfig.KEY_DATE_PREFIX; 5 | import static cyou.sk5s.app.countdate.CountdateWidgetConfig.KEY_ID_PREFIX; 6 | import static cyou.sk5s.app.countdate.CountdateWidgetConfig.KEY_NAME_PREFIX; 7 | import static cyou.sk5s.app.countdate.CountdateWidgetConfig.SHARED_PREFS; 8 | 9 | import android.app.PendingIntent; 10 | import android.appwidget.AppWidgetManager; 11 | import android.appwidget.AppWidgetProvider; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.SharedPreferences; 15 | import android.widget.RemoteViews; 16 | 17 | import java.text.ParseException; 18 | import java.text.SimpleDateFormat; 19 | import java.util.Date; 20 | import java.util.Locale; 21 | 22 | public class CountdateWidget extends AppWidgetProvider { 23 | @Override 24 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){ 25 | for (int appWidgetId : appWidgetIds){ 26 | Intent intent = new Intent(context, MainActivity.class); 27 | PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); 28 | 29 | Intent intent2 = new Intent(context, MainActivity.class); 30 | PendingIntent pendingIntent2 = PendingIntent.getActivity(context, 0, intent2, PendingIntent.FLAG_IMMUTABLE); 31 | 32 | SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS,Context.MODE_PRIVATE); 33 | //String eventId = prefs.getString(KEY_ID_PREFIX + appWidgetId,"0"); 34 | String eventName = prefs.getString(KEY_NAME_PREFIX + appWidgetId,"No Data"); 35 | String eventDate = prefs.getString(KEY_DATE_PREFIX + appWidgetId,"No Data"); 36 | 37 | String numOfDays = "Retry"; 38 | String countdownup = "⬇️ Countdown"; 39 | String[] parts = eventDate.split("T"); 40 | try { 41 | Date today = new Date(); 42 | Date anotherDate = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(parts[0]); 43 | //numOfDays = String.valueOf(today.getTime()) + String.valueOf(anotherDate.getTime()); 44 | if (anotherDate.getTime() > 0){ 45 | long difference = anotherDate.getTime() - today.getTime(); 46 | if (difference > 0){ 47 | numOfDays = String.valueOf(Math.abs((int) (difference / (1000 * 60 * 60 * 24))+1)); 48 | } else if (difference < 0 && difference > -86400000) { 49 | numOfDays = "0"; 50 | } else { 51 | numOfDays = String.valueOf(Math.abs((int) (difference / (1000 * 60 * 60 * 24))+1)); 52 | countdownup = "⬆️ Countup"; 53 | } 54 | } 55 | } catch (ParseException e) { 56 | e.printStackTrace(); 57 | } 58 | 59 | RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.countdate_widget); 60 | views.setOnClickPendingIntent(R.id.countdate_widget_text1, pendingIntent); 61 | views.setOnClickPendingIntent(R.id.countdate_widget_text2, pendingIntent2); 62 | // Intent updateIntent = new Intent(); 63 | // updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 64 | // PendingIntent updatePendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT); 65 | // views.setOnClickPendingIntent(R.id.countdate_widget_text2, updatePendingIntent); 66 | 67 | // Intent intentUpdate = new Intent(context, CountdateWidget.class); 68 | // intentUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 69 | // int[] idArray = new int[]{appWidgetId}; 70 | // intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray); 71 | // PendingIntent pendingUpdate = PendingIntent.getBroadcast( 72 | // context, appWidgetId, intentUpdate, 73 | // PendingIntent.FLAG_UPDATE_CURRENT); 74 | // views.setOnClickPendingIntent(R.id.countdate_widget_text2, pendingUpdate); 75 | 76 | views.setCharSequence(R.id.countdate_widget_text1,"setText", eventName+" "+countdownup); 77 | views.setCharSequence(R.id.countdate_widget_text2,"setText", numOfDays); 78 | 79 | appWidgetManager.updateAppWidget(appWidgetId, views); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /android/app/src/main/java/cyou/sk5s/app/countdate/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cyou.sk5s.app.countdate; 2 | 3 | import android.os.Bundle; 4 | import android.webkit.WebView; 5 | 6 | import com.getcapacitor.BridgeActivity; 7 | 8 | public class MainActivity extends BridgeActivity { 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | } 13 | @Override 14 | public void onStart() { 15 | super.onStart(); 16 | WebView webview = getBridge().getWebView(); 17 | webview.setOverScrollMode(WebView.OVER_SCROLL_NEVER); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/branding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable/branding.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/widget_preview_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/drawable/widget_preview_1.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_countdate_widget_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/countdate_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 36 | 37 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/row_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 33 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #03989e 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Countdate 4 | Countdate 5 | cyou.sk5s.app.countdate 6 | countdateapp 7 | 0 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 20 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/countdate_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:8.7.2' 11 | classpath 'com.google.gms:google-services:4.4.2' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | apply from: "variables.gradle" 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | 31 | -------------------------------------------------------------------------------- /android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | 5 | include ':capacitor-app' 6 | project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android') 7 | 8 | include ':capacitor-clipboard' 9 | project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android') 10 | 11 | include ':capacitor-device' 12 | project(':capacitor-device').projectDir = new File('../node_modules/@capacitor/device/android') 13 | 14 | include ':capacitor-filesystem' 15 | project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android') 16 | 17 | include ':capacitor-keyboard' 18 | project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android') 19 | 20 | include ':capacitor-local-notifications' 21 | project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android') 22 | 23 | include ':capacitor-preferences' 24 | project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android') 25 | 26 | include ':capacitor-share' 27 | project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android') 28 | 29 | include ':capacitor-toast' 30 | project(':capacitor-toast').projectDir = new File('../node_modules/@capacitor/toast/android') 31 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | android.defaults.buildfeatures.buildconfig=true 24 | android.nonTransitiveRClass=false 25 | android.nonFinalResIds=false 26 | 27 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 23 3 | compileSdkVersion = 35 4 | targetSdkVersion = 35 5 | androidxActivityVersion = '1.8.0' 6 | androidxAppCompatVersion = '1.6.1' 7 | androidxCoordinatorLayoutVersion = '1.2.0' 8 | androidxCoreVersion = '1.12.0' 9 | androidxFragmentVersion = '1.6.2' 10 | junitVersion = '4.13.2' 11 | androidxJunitVersion = '1.1.5' 12 | androidxEspressoCoreVersion = '3.5.1' 13 | cordovaAndroidVersion = '10.1.1' 14 | coreSplashScreenVersion='1.0.0' 15 | androidxWebkitVersion='1.4.0' 16 | androidxMaterialVersion='1.6.1' 17 | androidxBrowserVersion='1.4.0' 18 | firebaseMessagingVersion='23.0.5' 19 | playServicesLocationVersion='20.0.0' 20 | androidxExifInterfaceVersion='1.3.3' 21 | coreSplashScreenVersion = '1.0.1' 22 | androidxWebkitVersion = '1.9.0' 23 | } -------------------------------------------------------------------------------- /capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'cyou.sk5s.app.countdate', 5 | appName: 'Countdate', 6 | webDir: 'dist', 7 | server: { 8 | androidScheme: 'https' 9 | }, 10 | plugins: { 11 | LocalNotifications: { 12 | smallIcon: "countdateapp-logo", 13 | iconColor: "#488AFF" 14 | }, 15 | SplashScreen: { 16 | backgroundColor: "#03989e", 17 | } 18 | }, 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /codemagic.yaml: -------------------------------------------------------------------------------- 1 | workflows: 2 | ionic-capacitor-ios-workflow: 3 | name: Ionic Capacitor iOS Workflow 4 | max_build_duration: 120 5 | instance_type: mac_mini_m2 6 | integrations: 7 | app_store_connect: sk5s_appstoreconnect 8 | environment: 9 | ios_signing: 10 | distribution_type: app_store 11 | bundle_identifier: cyou.sk5s.app.countdate 12 | vars: 13 | # Ionic Xcode worskspace and scheme 14 | XCODE_WORKSPACE: "App.xcworkspace" 15 | XCODE_SCHEME: "App" 16 | APP_STORE_APP_ID: 6553989325 17 | node: v20.11.1 18 | xcode: latest # <-- set to specific version e.g. 14.3, 15.0 to avoid unexpected updates. 19 | cocoapods: default 20 | scripts: 21 | - name: Install npm dependencies for Ionic project 22 | script: | 23 | npm install 24 | - name: Build Ionic project 25 | script: | 26 | npm run build 27 | - name: Cocoapods installation 28 | script: | 29 | cd ios/App && rm ./Podfile.lock && pod install --repo-update 30 | - name: Update dependencies and copy web assets to native project 31 | script: | 32 | npx cap sync 33 | - name: Set up code signing settings on Xcode project 34 | script: | 35 | xcode-project use-profiles 36 | - name: Build ipa for distribution 37 | script: | 38 | cd $CM_BUILD_DIR/ios/App 39 | xcode-project build-ipa \ 40 | --workspace "$XCODE_WORKSPACE" \ 41 | --scheme "$XCODE_SCHEME" 42 | artifacts: 43 | - $CM_BUILD_DIR/ios/App/build/ios/ipa/*.ipa 44 | publishing: 45 | app_store_connect: 46 | auth: integration 47 | submit_to_testflight: true 48 | beta_groups: 49 | - me 50 | submit_to_app_store: false -------------------------------------------------------------------------------- /countdateapp-logo-concentrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/countdateapp-logo-concentrate.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Countdate lets you know how many days are left to a specific date. Next to the native app, there's also a web version available. 2 | 3 | Countdate is and will be free to download and use. As it's open source and under a free license, you can even take the code and modify it! 4 | 5 | * Android、iOS: Ready to use native app. 6 | * Web: Web version of countdate app is also available. 7 | * Free to Use: As always, this app is free to download and use! 8 | * Open Source: Since this app is MIT licensed, you can modify it! 9 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/featureGraphic.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Date Countdown: Know How Many Days Left To a Specific Date. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Countdate -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/video.txt: -------------------------------------------------------------------------------- 1 | https://sk5s.cyou/countdate-landing/assets/portrait-3000.mp4 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Countdate app 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "countdateapp", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "react-vite" 7 | } 8 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | App/build 2 | App/Pods 3 | App/output 4 | App/App/public 5 | DerivedData 6 | xcuserdata 7 | 8 | # Cordova plugins for Capacitor 9 | capacitor-cordova-ios-plugins 10 | 11 | # Generated Config files 12 | App/App/capacitor.config.json 13 | App/App/config.xml 14 | -------------------------------------------------------------------------------- /ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Capacitor 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | // Override point for customization after application launch. 11 | return true 12 | } 13 | 14 | func applicationWillResignActive(_ application: UIApplication) { 15 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 16 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 17 | } 18 | 19 | func applicationDidEnterBackground(_ application: UIApplication) { 20 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 21 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 22 | } 23 | 24 | func applicationWillEnterForeground(_ application: UIApplication) { 25 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 26 | } 27 | 28 | func applicationDidBecomeActive(_ application: UIApplication) { 29 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 30 | } 31 | 32 | func applicationWillTerminate(_ application: UIApplication) { 33 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 34 | } 35 | 36 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { 37 | // Called when the app was launched with a url. Feel free to add additional processing here, 38 | // but if you want the App API to support tracking app url opens, make sure to keep this call 39 | return ApplicationDelegateProxy.shared.application(app, open: url, options: options) 40 | } 41 | 42 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 43 | // Called when the app was launched with an activity, including Universal Links. 44 | // Feel free to add additional processing here, but if you want the App API to support 45 | // tracking app url opens, make sure to keep this call 46 | return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "size": "1024x1024", 6 | "filename": "AppIcon-512@2x.png", 7 | "platform": "ios" 8 | } 9 | ], 10 | "info": { 11 | "author": "xcode", 12 | "version": 1 13 | } 14 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "Default@1x~universal~anyany.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "Default@2x~universal~anyany.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "Default@3x~universal~anyany.png", 16 | "scale": "3x" 17 | }, 18 | { 19 | "appearances": [ 20 | { 21 | "appearance": "luminosity", 22 | "value": "dark" 23 | } 24 | ], 25 | "idiom": "universal", 26 | "scale": "1x", 27 | "filename": "Default@1x~universal~anyany-dark.png" 28 | }, 29 | { 30 | "appearances": [ 31 | { 32 | "appearance": "luminosity", 33 | "value": "dark" 34 | } 35 | ], 36 | "idiom": "universal", 37 | "scale": "2x", 38 | "filename": "Default@2x~universal~anyany-dark.png" 39 | }, 40 | { 41 | "appearances": [ 42 | { 43 | "appearance": "luminosity", 44 | "value": "dark" 45 | } 46 | ], 47 | "idiom": "universal", 48 | "scale": "3x", 49 | "filename": "Default@3x~universal~anyany-dark.png" 50 | } 51 | ], 52 | "info": { 53 | "version": 1, 54 | "author": "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png -------------------------------------------------------------------------------- /ios/App/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ios/App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ios/App/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | zh-Hant-TW 7 | CFBundleLocalizations 8 | 9 | zh-Hant-TW 10 | en 11 | 12 | CFBundleDisplayName 13 | Countdate 14 | CFBundleExecutable 15 | $(EXECUTABLE_NAME) 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | $(PRODUCT_NAME) 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | $(MARKETING_VERSION) 26 | CFBundleURLTypes 27 | 28 | 29 | CFBundleTypeRole 30 | Editor 31 | CFBundleURLName 32 | cyou.sk5s.app.countdate 33 | CFBundleURLSchemes 34 | 35 | countdate 36 | 37 | 38 | 39 | CFBundleVersion 40 | $(CURRENT_PROJECT_VERSION) 41 | ITSAppUsesNonExemptEncryption 42 | 43 | LSRequiresIPhoneOS 44 | 45 | UILaunchStoryboardName 46 | LaunchScreen 47 | UIMainStoryboardFile 48 | Main 49 | UIRequiredDeviceCapabilities 50 | 51 | armv7 52 | 53 | UISupportedInterfaceOrientations 54 | 55 | UIInterfaceOrientationPortrait 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | UISupportedInterfaceOrientations~ipad 60 | 61 | UIInterfaceOrientationPortrait 62 | UIInterfaceOrientationPortraitUpsideDown 63 | UIInterfaceOrientationLandscapeLeft 64 | UIInterfaceOrientationLandscapeRight 65 | 66 | UIViewControllerBasedStatusBarAppearance 67 | 68 | 69 | -------------------------------------------------------------------------------- /ios/App/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' 2 | 3 | platform :ios, '14.0' 4 | use_frameworks! 5 | 6 | # workaround to avoid Xcode caching of Pods that requires 7 | # Product -> Clean Build Folder after new Cordova plugins installed 8 | # Requires CocoaPods 1.6 or newer 9 | install! 'cocoapods', :disable_input_output_paths => true 10 | 11 | def capacitor_pods 12 | pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' 13 | pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' 14 | pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' 15 | pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard' 16 | pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device' 17 | pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem' 18 | pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard' 19 | pod 'CapacitorLocalNotifications', :path => '../../node_modules/@capacitor/local-notifications' 20 | pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences' 21 | pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share' 22 | pod 'CapacitorToast', :path => '../../node_modules/@capacitor/toast' 23 | end 24 | 25 | target 'App' do 26 | capacitor_pods 27 | # Add your Pods here 28 | end 29 | 30 | post_install do |installer| 31 | assertDeploymentTarget(installer) 32 | end 33 | -------------------------------------------------------------------------------- /ios/App/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Capacitor (6.1.1): 3 | - CapacitorCordova 4 | - CapacitorApp (6.0.0): 5 | - Capacitor 6 | - CapacitorClipboard (6.0.0): 7 | - Capacitor 8 | - CapacitorCordova (6.1.1) 9 | - CapacitorDevice (6.0.0): 10 | - Capacitor 11 | - CapacitorKeyboard (6.0.1): 12 | - Capacitor 13 | - CapacitorLocalNotifications (6.0.0): 14 | - Capacitor 15 | - CapacitorPreferences (6.0.1): 16 | - Capacitor 17 | - CapacitorToast (6.0.1): 18 | - Capacitor 19 | 20 | DEPENDENCIES: 21 | - "Capacitor (from `../../node_modules/@capacitor/ios`)" 22 | - "CapacitorApp (from `../../node_modules/@capacitor/app`)" 23 | - "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)" 24 | - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" 25 | - "CapacitorDevice (from `../../node_modules/@capacitor/device`)" 26 | - "CapacitorKeyboard (from `../../node_modules/@capacitor/keyboard`)" 27 | - "CapacitorLocalNotifications (from `../../node_modules/@capacitor/local-notifications`)" 28 | - "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)" 29 | - "CapacitorToast (from `../../node_modules/@capacitor/toast`)" 30 | 31 | EXTERNAL SOURCES: 32 | Capacitor: 33 | :path: "../../node_modules/@capacitor/ios" 34 | CapacitorApp: 35 | :path: "../../node_modules/@capacitor/app" 36 | CapacitorClipboard: 37 | :path: "../../node_modules/@capacitor/clipboard" 38 | CapacitorCordova: 39 | :path: "../../node_modules/@capacitor/ios" 40 | CapacitorDevice: 41 | :path: "../../node_modules/@capacitor/device" 42 | CapacitorKeyboard: 43 | :path: "../../node_modules/@capacitor/keyboard" 44 | CapacitorLocalNotifications: 45 | :path: "../../node_modules/@capacitor/local-notifications" 46 | CapacitorPreferences: 47 | :path: "../../node_modules/@capacitor/preferences" 48 | CapacitorToast: 49 | :path: "../../node_modules/@capacitor/toast" 50 | 51 | SPEC CHECKSUMS: 52 | Capacitor: 8941aba4364ba9d1b22188569001f2ce45cc2b00 53 | CapacitorApp: 9d53aec7101f7b030a950c5bdc4df8612576b279 54 | CapacitorClipboard: 80282f684154124b9019ebf401235b70b0cf4994 55 | CapacitorCordova: 8f2cc8d8d3619c566e9418fe8772064a94266106 56 | CapacitorDevice: f8fd88f9edd1261c55a109f32015b09bbbfdc4a0 57 | CapacitorKeyboard: 5f32a712adf41e07a61caafb82cf29fb6d8ba123 58 | CapacitorLocalNotifications: 4ab68f0be5f697a579558fadd307d823a9ec1c26 59 | CapacitorPreferences: 72909b165bc7807103778ddbb86d5d8ce06abf71 60 | CapacitorToast: d4c42ea810e223bdd66b95a7c661f5467666896d 61 | 62 | PODFILE CHECKSUM: bbe882cbfe18182d0299a9a35c711989e0f681e6 63 | 64 | COCOAPODS: 1.15.2 65 | -------------------------------------------------------------------------------- /ios/App/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTracking 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | 10 | NSPrivacyAccessedAPIType 11 | NSPrivacyAccessedAPICategoryUserDefaults 12 | NSPrivacyAccessedAPITypeReasons 13 | 14 | CA92.1 15 | 16 | 17 | 18 | NSPrivacyAccessedAPIType 19 | NSPrivacyAccessedAPICategoryDiskSpace 20 | NSPrivacyAccessedAPITypeReasons 21 | 22 | 85F4.1 23 | 24 | 25 | 26 | NSPrivacyAccessedAPIType 27 | NSPrivacyAccessedAPICategoryFileTimestamp 28 | NSPrivacyAccessedAPITypeReasons 29 | 30 | C617.1 31 | 32 | 33 | 34 | NSPrivacyCollectedDataTypes 35 | 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "countdateapp", 3 | "version": "1.5.11", 4 | "type": "module", 5 | "private": true, 6 | "dependencies": { 7 | "@capacitor/android": "^7.0.0", 8 | "@capacitor/app": "^7.0.0", 9 | "@capacitor/clipboard": "^7.0.0", 10 | "@capacitor/core": "^7.0.0", 11 | "@capacitor/device": "^7.0.0", 12 | "@capacitor/filesystem": "^7.0.0", 13 | "@capacitor/ios": "^7.0.0", 14 | "@capacitor/keyboard": "^7.0.0", 15 | "@capacitor/local-notifications": "^7.0.0", 16 | "@capacitor/preferences": "^7.0.0", 17 | "@capacitor/share": "^7.0.0", 18 | "@capacitor/toast": "^7.0.0", 19 | "@ionic/core": "8.5.0", 20 | "@ionic/react": "^8.5.0", 21 | "@ionic/react-router": "^8.5.0", 22 | "@types/react": "18.3.3", 23 | "@types/react-router": "5.1.20", 24 | "@types/react-router-dom": "^5.1.7", 25 | "date-fns": "^2.29.3", 26 | "i18next": "21.10.0", 27 | "i18next-browser-languagedetector": "6.1.8", 28 | "ionicons": "7.2.2", 29 | "qrcode.react": "^3.1.0", 30 | "react": "^18.3.1", 31 | "react-color": "^2.19.3", 32 | "react-countdown": "2.3.5", 33 | "react-dom": "^18.3.1", 34 | "react-i18next": "11.18.6", 35 | "react-markdown": "9.0.1", 36 | "react-router": "5.3.4", 37 | "react-router-dom": "5.3.4", 38 | "react-swipeable": "7.0.1", 39 | "react-transition-group": "4.4.5", 40 | "rehype-external-links": "^3.0.0", 41 | "remark-gfm": "4.0.0", 42 | "swiper": "^9.4.1", 43 | "typescript": "4.9.5", 44 | "uuid": "^11.1.0" 45 | }, 46 | "scripts": { 47 | "dev": "vite", 48 | "build": "npx ionic build && npx cap copy", 49 | "preview": "vite preview", 50 | "surgeold": "surge build --domain https://countdate.surge.sh", 51 | "prettier": "npx prettier --write ./src", 52 | "android": "ionic cap run android -l --external", 53 | "deploy": "firebase deploy" 54 | }, 55 | "devDependencies": { 56 | "@capacitor/cli": "^7.0.0", 57 | "@ionic/cli": "7.2.0", 58 | "@types/node": "^22.13.10", 59 | "@types/react-color": "3.0.12", 60 | "@types/react-dom": "18.3.0", 61 | "@vitejs/plugin-legacy": "^5.4.3", 62 | "@vitejs/plugin-react": "^4.3.4", 63 | "cordova-res": "0.15.4", 64 | "prettier": "2.8.8", 65 | "vite": "^5.4.14" 66 | }, 67 | "description": "Date Countdown: Know How Many Days Left To a Specific Date." 68 | } 69 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | Single Page Apps for GitHub Pages -------------------------------------------------------------------------------- /public/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/icon/favicon.png -------------------------------------------------------------------------------- /public/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/icon/icon.png -------------------------------------------------------------------------------- /public/assets/tour/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/1.jpg -------------------------------------------------------------------------------- /public/assets/tour/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/2.jpg -------------------------------------------------------------------------------- /public/assets/tour/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/3.jpg -------------------------------------------------------------------------------- /public/assets/tour/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/4.jpg -------------------------------------------------------------------------------- /public/assets/tour/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/5.jpg -------------------------------------------------------------------------------- /public/assets/tour/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/6.jpg -------------------------------------------------------------------------------- /public/assets/tour/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/7.jpg -------------------------------------------------------------------------------- /public/assets/tour/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/public/assets/tour/8.jpg -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Countdate", 3 | "name": "Countdate app", 4 | "orientation": "any", 5 | "dir": "ltr", 6 | "icons": [ 7 | { 8 | "src": "assets/icon/icon.png", 9 | "type": "image/png", 10 | "sizes": "512x512" 11 | } 12 | ], 13 | "start_url": "/", 14 | "display": "standalone", 15 | "theme_color": "#000000", 16 | "background_color": "#01989f" 17 | } 18 | -------------------------------------------------------------------------------- /resources/android/icon/drawable-hdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/icon/drawable-hdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-ldpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/icon/drawable-ldpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-mdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/icon/drawable-mdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/icon/drawable-xhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/icon/drawable-xxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/icon/drawable-xxxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-land-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-land-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-land-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-land-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-land-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-land-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-port-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-port-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-port-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-port-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-port-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/android/splash/drawable-port-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/icon.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/resources/splash.png -------------------------------------------------------------------------------- /src/.prettierignore: -------------------------------------------------------------------------------- 1 | assets -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Route } from 'react-router-dom'; 2 | import { IonApp, IonRouterOutlet, setupIonicReact } from "@ionic/react"; 3 | import { IonReactRouter } from '@ionic/react-router'; 4 | import { App as NativeApp } from "@capacitor/app"; 5 | import { useHistory } from "react-router"; 6 | import Home from "./pages/Home"; 7 | import Add from "./pages/Add"; 8 | import Settings from "./pages/Settings"; 9 | import Edit from "./pages/Edit"; 10 | import About from "./pages/About"; 11 | 12 | import "./lib/Darkmode"; 13 | import AppTour from "./components/AppTour"; 14 | 15 | /* Core CSS required for Ionic components to work properly */ 16 | import "@ionic/react/css/core.css"; 17 | 18 | /* Basic CSS for apps built with Ionic */ 19 | import "@ionic/react/css/normalize.css"; 20 | import "@ionic/react/css/structure.css"; 21 | import "@ionic/react/css/typography.css"; 22 | 23 | /* Optional CSS utils that can be commented out */ 24 | import "@ionic/react/css/padding.css"; 25 | import "@ionic/react/css/float-elements.css"; 26 | import "@ionic/react/css/text-alignment.css"; 27 | import "@ionic/react/css/text-transformation.css"; 28 | import "@ionic/react/css/flex-utils.css"; 29 | import "@ionic/react/css/display.css"; 30 | 31 | /* Theme variables */ 32 | import "./theme/variables.css"; 33 | import { Preferences } from "@capacitor/preferences"; 34 | import key from "./lib/storageKey.json"; 35 | import { useState } from "react"; 36 | import { on } from "./lib/Events"; 37 | import { useTranslation } from "react-i18next"; 38 | import Share from "./pages/Share"; 39 | import AppUrlListener from './pages/AppUrlListener'; 40 | import Backup from "./pages/Backup"; 41 | 42 | // Ionic setup 43 | setupIonicReact({ 44 | mode: "ios", 45 | }); 46 | 47 | function App() { 48 | const history = useHistory(); 49 | const { t, i18n } = useTranslation(); 50 | const getCountdownOrUp = () => { 51 | let local = localStorage.getItem("countdownOrUp") 52 | console.log() 53 | if (local !== null){ 54 | return "countup" 55 | } else { 56 | return "countdown" 57 | } 58 | } 59 | const [countdownOrUp, setCountdownOrUp] = useState(() => getCountdownOrUp()); 60 | const [view, setView] = useState("days"); 61 | const [accentColor, setAccentColor] = useState("primary"); 62 | const [textColor, setTextColor] = useState(""); 63 | const [firstTime, setFirstTime] = useState(false); 64 | const [relative, setRelative] = useState(false) 65 | // Hardware back button 66 | NativeApp.addListener("backButton", ({ canGoBack }) => { 67 | if (canGoBack) { 68 | history.goBack(); 69 | } 70 | }); 71 | 72 | const getRelativeMode = async () => { 73 | const { value } = await Preferences.get({ key: key.relative }); 74 | if (value === "true") { 75 | setRelative(true); 76 | } else { 77 | setRelative(false); 78 | } 79 | } 80 | 81 | // Restore accent color 82 | const getAccentColor = async () => { 83 | const { value } = await Preferences.get({ key: key.accent }); 84 | if (value != null) { 85 | setAccentColor(value); 86 | } 87 | }; 88 | // Restore text color 89 | const getTextColor = async () => { 90 | const { value } = await Preferences.get({ key: key.textColor }); 91 | if (value != null) { 92 | setTextColor(value); 93 | } else { 94 | setTextColor(""); 95 | } 96 | }; 97 | 98 | // Show welcome modal if it's first time 99 | let count = 0; 100 | const getFirstTime = async () => { 101 | if (count < 1) { 102 | const { value } = await Preferences.get({ key: key.firstTime }); 103 | if (value == null) { 104 | setFirstTime(true); 105 | await Preferences.set({ 106 | key: key.firstTime, 107 | value: "false", 108 | }); 109 | count += 1; 110 | } 111 | } 112 | }; 113 | // Restore settings 114 | getAccentColor(); 115 | getTextColor(); 116 | getFirstTime(); 117 | getRelativeMode() 118 | on("countdate_accent:change", () => { 119 | getAccentColor(); 120 | }); 121 | on("countdate_text:change", () => { 122 | getTextColor(); 123 | }); 124 | on("countdate_first:change", () => { 125 | getFirstTime(); 126 | }); 127 | on("countdate_relative:change", () => { 128 | getRelativeMode(); 129 | }) 130 | 131 | return ( 132 | 133 | 134 | 135 | 136 | 137 | 138 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | ); 169 | }; 170 | 171 | export default App; 172 | -------------------------------------------------------------------------------- /src/assets/countdate-count-tour.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/src/assets/countdate-count-tour.jpg -------------------------------------------------------------------------------- /src/assets/countdateapp-logo-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/src/assets/countdateapp-logo-background.png -------------------------------------------------------------------------------- /src/assets/countdateapp-logo-foreground-.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/src/assets/countdateapp-logo-foreground-.png -------------------------------------------------------------------------------- /src/assets/countdateapp-logo-foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/src/assets/countdateapp-logo-foreground.png -------------------------------------------------------------------------------- /src/assets/countdateapp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/src/assets/countdateapp-logo.png -------------------------------------------------------------------------------- /src/assets/countdateapp-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/src/assets/countdateapp-splash.png -------------------------------------------------------------------------------- /src/assets/sk5s-project-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sk5s/countdateapp/9955d3010453e33e0208e20393f71773aed3d5f0/src/assets/sk5s-project-bar.png -------------------------------------------------------------------------------- /src/components/AccentColorSelectModal.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | IonIcon, 4 | IonLabel, 5 | IonModal, 6 | IonHeader, 7 | IonToolbar, 8 | IonTitle, 9 | IonButtons, 10 | IonButton, 11 | IonContent, 12 | IonList, 13 | IonRadioGroup, 14 | IonItem, 15 | IonRadio, 16 | } from "@ionic/react"; 17 | import { useTranslation } from "react-i18next"; 18 | import { Preferences } from "@capacitor/preferences"; 19 | import key from "../lib/storageKey.json"; 20 | import { trigger } from "../lib/Events"; 21 | import { colorPalette, ellipse } from "ionicons/icons"; 22 | 23 | export default function AccentColorSelectModal() { 24 | const {t} = useTranslation(); 25 | const [modalIsOpen, setModalIsOpen] = useState(false); 26 | const [radioSelected, setRadioSelected] = useState("primary"); 27 | const changeAccentColorTo = async (colorName: string) => { 28 | await Preferences.set({ 29 | key: key.accent, 30 | value: colorName, 31 | }); 32 | console.log("change accent color to: " + colorName); 33 | trigger("countdate_accent:change"); 34 | setModalIsOpen(false); 35 | }; 36 | const allColorName = [ 37 | "primary", 38 | "secondary", 39 | "tertiary", 40 | "success", 41 | "warning", 42 | "danger", 43 | "medium", 44 | "dark", 45 | ]; 46 | const realColorName = { 47 | primary: "Navy Blue (Default)", 48 | secondary: "Teal", 49 | tertiary: "Violet", 50 | success: "Green", 51 | warning: "Yellow", 52 | danger: "Red", 53 | medium: "Grey", 54 | dark: "Black", 55 | }; 56 | const openPicker = async () => { 57 | setModalIsOpen(true); 58 | }; 59 | 60 | return ( 61 | <> 62 | 63 | {t("c.settings.changeAccent")} 64 | 65 | 66 | setModalIsOpen(false)}> 67 | 68 | 69 | {t("c.settings.changeAccent")} 70 | 71 | setModalIsOpen(false)}> 72 | {t("g.close")} 73 | 74 | 75 | 76 | changeAccentColorTo(radioSelected)}> 77 | {t("g.confirm")} 78 | 79 | 80 | 81 | 82 | 83 | 84 | { 86 | setRadioSelected(e.detail.value); 87 | }} 88 | value={radioSelected} 89 | > 90 | {(() => { 91 | let rows: any = []; 92 | allColorName.forEach((element) => { 93 | rows.push( 94 | 95 | 96 | {" "} 97 | {realColorName[element as keyof typeof realColorName]} 98 | 99 | 100 | ); 101 | }); 102 | return rows; 103 | })()} 104 | 105 | 106 | 107 | 108 | 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /src/components/AppTour.tsx: -------------------------------------------------------------------------------- 1 | import { Navigation, Pagination, Scrollbar, A11y, Mousewheel } from "swiper"; 2 | import { Swiper, SwiperSlide } from "swiper/react"; 3 | 4 | import "swiper/css"; 5 | import "swiper/css/navigation"; 6 | import "swiper/css/pagination"; 7 | import "swiper/css/scrollbar"; 8 | import { 9 | IonButton, 10 | IonButtons, 11 | IonChip, 12 | IonContent, 13 | IonHeader, 14 | IonIcon, 15 | IonImg, 16 | IonLabel, 17 | IonModal, 18 | IonText, 19 | IonTitle, 20 | IonToolbar, 21 | } from "@ionic/react"; 22 | import { useTranslation } from "react-i18next"; 23 | import { informationCircle, link } from "ionicons/icons"; 24 | 25 | // TODO: Update screenshots 26 | 27 | export default function AppTour({ 28 | modal, 29 | setModal, 30 | color, 31 | }: { 32 | modal: boolean; 33 | setModal: any; 34 | color: string; 35 | }) { 36 | const { t } = useTranslation(); 37 | const imgPath = (num: number) => { 38 | return "assets/tour/" + (num + 1).toString() + ".jpg"; 39 | }; 40 | return ( 41 | 42 | 43 | 44 | {t("c.tour.title")} 45 | 46 | setModal(false)}> 47 | {t("g.close")} 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {t("c.tour.can_review")} 56 | 57 | 65 | {(() => { 66 | let rows = []; 67 | for (let i = 0; i < 8; i++) { 68 | rows.push( 69 | 70 | 71 |

72 | {t("c.tour.step_" + (i + 1).toString())} 73 |

74 |
75 | 76 |
77 | ); 78 | } 79 | return rows; 80 | })()} 81 |
82 | 89 | {t("c.tour.learn_more")} 90 | 91 |
92 |
93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/CountCard.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonCard, 3 | IonCardSubtitle, 4 | IonCardTitle, 5 | IonCardContent, 6 | IonItem, 7 | } from "@ionic/react"; 8 | 9 | import { on } from "../lib/Events"; 10 | import { useTranslation } from "react-i18next"; 11 | import { useEffect, useState } from "react"; 12 | import EventDetailModal from "./EventDetailModal"; 13 | import { countDownFromTime, countUpFromTime, countFromTime } from "../lib/Countdate"; 14 | 15 | export default function CountCard(props: { 16 | type: string; 17 | date: string; 18 | event: string; 19 | editable: boolean; 20 | id: string; 21 | view: string; 22 | accent: string; 23 | textColor: string; 24 | description?: string; 25 | relative: boolean; 26 | }): JSX.Element { 27 | const [t] = useTranslation(); 28 | const [isOpen, setIsOpen] = useState(false); 29 | const [contentEditable, setContentEditable] = useState(false); 30 | const [days, setDays] = useState(countFromTime(props.date)) 31 | const convertDays = (days) => { 32 | // Convert days into total years, months, and days 33 | const years = Math.floor(days / 365); 34 | days -= years * 365; 35 | const months = Math.floor(days / 30); 36 | days -= months * 30; 37 | return [years, months, days]; 38 | } 39 | const getTimeStr = (ndays:number) => { 40 | // console.log(props.relative) 41 | if (props.view === "days"){ 42 | return ndays.toString() + " " + t("c.card.days") 43 | } else if (props.view === "months") { 44 | let relative = convertDays(ndays) 45 | if (props.relative){ 46 | let mystr = "" 47 | if (relative[0]){ 48 | mystr += (relative[0]).toString() + " " + t("c.card.years") + " " 49 | } 50 | if (relative[1]){ 51 | mystr += (relative[1]).toString() + " " + t("c.card.months") + " " 52 | } 53 | if (relative[2]){ 54 | mystr += (relative[2]).toString() + " " + t("c.card.days") 55 | } 56 | return mystr 57 | } 58 | return (Math.round((ndays / 30 + Number.EPSILON) * 100 ) / 100).toString() + " " + t("c.card.months") 59 | } else { 60 | if (props.relative){ 61 | return Math.floor(ndays / 7).toString() + " " + t("c.card.weeks") + " " + (ndays % 7).toString() + " " + t("c.card.days") 62 | } 63 | return (Math.round((ndays / 7 + Number.EPSILON) * 10 ) / 10).toString() + " " + t("c.card.weeks") 64 | } 65 | } 66 | const [timeStr, setTimeStr] = useState(() => getTimeStr(days)) 67 | useEffect(() => { 68 | on("countdate_data:change", (data: any) => { 69 | if (data.detail === "delete") setIsOpen(false); 70 | // setDays(countFromTime(props.date)) 71 | // setTimeStr(() => getTimeStr(countFromTime(props.date))) 72 | }); 73 | }, []); 74 | useEffect(() => { 75 | setTimeStr(() => getTimeStr(days)) 76 | setDays(countFromTime(props.date)) 77 | setTimeStr(() => getTimeStr(countFromTime(props.date))) 78 | }, [props.date]) 79 | useEffect(() => { 80 | setTimeStr(() => getTimeStr(days)) 81 | }, [props.view]) 82 | if (props.type === "countup") { 83 | if (countUpFromTime(props.date) < 0){ 84 | return <> 85 | } 86 | } 87 | if (props.type === "countdown") { 88 | if (countDownFromTime(props.date) < 0){ 89 | return <> 90 | } 91 | } 92 | return ( 93 | <> 94 | setIsOpen(true)} style={{ cursor: "pointer" }}> 95 | 96 | 97 | {props.type === "countdown" ? 98 | t("c.card.countdownEvent",{eventName: props.event}): 99 | props.event} 100 | 101 | 102 | 103 | 107 | {timeStr} 108 | 109 | 110 | 111 | 119 | 120 | ); 121 | } -------------------------------------------------------------------------------- /src/components/CountDownUpSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { IonLabel, IonSegment, IonSegmentButton, IonToolbar } from "@ionic/react"; 2 | import { useTranslation } from "react-i18next"; 3 | 4 | export default function CountDownUpSwitcher({ 5 | accent, 6 | count, 7 | setCount 8 | }) { 9 | const { t } = useTranslation(); 10 | return ( 11 | <> 12 | {/* Countdown countup switcher */} 13 | 14 | { 18 | setCount(`${e.detail.value}`) 19 | if (e.detail.value === "countup"){ 20 | localStorage.setItem("countdownOrUp", "up") 21 | } else { 22 | localStorage.removeItem("countdownOrUp") 23 | } 24 | }} 25 | > 26 | 27 | {t("p.home.countdown")} 28 | 29 | 30 | {t("p.home.countup")} 31 | 32 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/CountdownItem.css: -------------------------------------------------------------------------------- 1 | ion-popover.dateselect { 2 | --min-width: 350px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/CountdownItem.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonReorder, 3 | IonItem, 4 | IonIcon, 5 | useIonAlert, 6 | IonLabel, 7 | IonDatetime, 8 | IonPopover, 9 | IonChip, 10 | IonText, 11 | useIonToast, 12 | } from "@ionic/react"; 13 | import { Preferences } from "@capacitor/preferences"; 14 | 15 | import { reorderThree, trash } from "ionicons/icons"; 16 | import { on, trigger } from "../lib/Events"; 17 | import { useTranslation } from "react-i18next"; 18 | import key from "../lib/storageKey.json"; 19 | import format from "date-fns/format"; 20 | import { useState } from "react"; 21 | import "./CountdownItem.css"; 22 | import { EXTEND_YEARS } from "../constants/Constants"; 23 | 24 | export default function CountdateItem(props: { 25 | date: string; 26 | event: string; 27 | editable: boolean; 28 | id: string; 29 | accent: string; 30 | textColor: string; 31 | }): JSX.Element { 32 | const [t] = useTranslation(); 33 | const [presentAlert] = useIonAlert(); 34 | const [presentToast] = useIonToast(); 35 | const [modalIsOpen, setModalIsOpen] = useState(false); 36 | const [years,setYears] = useState(2); 37 | let countdate_events_data = []; 38 | const handleDelete = () => { 39 | presentAlert({ 40 | header: t("c.cards.delete!"), 41 | buttons: [ 42 | t("g.cancel"), 43 | { 44 | text: t("g.confirm"), 45 | role: "confirm", 46 | handler: () => { 47 | remove_this_countdate_item(); 48 | }, 49 | }, 50 | ], 51 | onDidDismiss: (e: CustomEvent) => console.log(e.detail.role), 52 | }); 53 | }; 54 | const remove_this_countdate_item = async () => { 55 | const { value } = await Preferences.get({ key: key.data }); 56 | if (value) { 57 | countdate_events_data = JSON.parse(value); 58 | countdate_events_data = countdate_events_data.filter((item: any) => { 59 | console.log(item.id); 60 | console.log(String(item.id) !== String(props.id)); 61 | return String(item.id) !== String(props.id); 62 | }); 63 | let content = JSON.stringify(countdate_events_data); 64 | await Preferences.set({ 65 | key: key.data, 66 | value: content, 67 | }); 68 | trigger("countdate_data:change", "delete"); 69 | presentToast({ 70 | message: t("c.cards.deleted"), 71 | duration: 1500, 72 | position: "bottom", 73 | color: props.accent, 74 | }); 75 | } 76 | }; 77 | const edit_this_countdate_item_name_handler = () => { 78 | presentAlert({ 79 | buttons: [ 80 | { text: t("g.cancel"), role: "cancel" }, 81 | { 82 | text: t("g.confirm"), 83 | handler: (data) => { 84 | if (data.name !== "") edit_this_countdate_item_name(data.name); 85 | }, 86 | }, 87 | ], 88 | inputs: [ 89 | { 90 | name: "name", 91 | value: props.event, 92 | placeholder: 93 | t("p.add.eventName.placeholder"), 94 | }, 95 | ], 96 | }); 97 | }; 98 | const edit_this_countdate_item_name = async (newName: string) => { 99 | const { value } = await Preferences.get({ key: key.data }); 100 | if (value) { 101 | countdate_events_data = JSON.parse(value); 102 | for (const i of countdate_events_data) { 103 | if (String(i.id) === String(props.id)) { 104 | i.event_name = newName; 105 | } 106 | } 107 | let content = JSON.stringify(countdate_events_data); 108 | await Preferences.set({ 109 | key: key.data, 110 | value: content, 111 | }); 112 | trigger("countdate_data:change"); 113 | } 114 | }; 115 | const edit_this_countdate_item_date = async (newDate: string) => { 116 | const { value } = await Preferences.get({ key: key.data }); 117 | if (value) { 118 | countdate_events_data = JSON.parse(value); 119 | for (const i of countdate_events_data) { 120 | if (String(i.id) === String(props.id)) { 121 | let olddate = i.date; 122 | i.date = newDate + "T" + olddate.split("T")[1]; 123 | } 124 | } 125 | let content = JSON.stringify(countdate_events_data); 126 | await Preferences.set({ 127 | key: key.data, 128 | value: content, 129 | }); 130 | trigger("countdate_data:change"); 131 | } 132 | }; 133 | const getExtendMode = async () => { 134 | const { value } = await Preferences.get({ key: key.extend }); 135 | if (value === "true") { 136 | setYears(EXTEND_YEARS); 137 | } else { 138 | setYears(2); 139 | } 140 | }; 141 | on("countdate_extend:change", () => { 142 | getExtendMode(); 143 | }); 144 | getExtendMode(); 145 | return ( 146 | 147 | 148 | 149 |

153 | {props.event} 154 |

155 |
156 | setModalIsOpen(!modalIsOpen)} 159 | > 160 | {format(new Date(props.date), "yyyy / MM / dd")} 161 | 162 |
163 | 169 | 170 | 171 | 172 | setModalIsOpen(!modalIsOpen)} size="cover" keepContentsMounted={false}> 173 | 181 | edit_this_countdate_item_date( 182 | format(new Date(`${e.detail.value}`), "yyyy-MM-dd") 183 | ) 184 | } 185 | value={props.date} 186 | id="datetime" 187 | cancelText={t("g.cancel")} 188 | doneText={t("g.confirm")} 189 | > 190 | 191 |
192 | ); 193 | } 194 | -------------------------------------------------------------------------------- /src/components/DescriptionEditor.css: -------------------------------------------------------------------------------- 1 | th, 2 | td { 3 | border: 1px solid black; 4 | border-collapse: collapse; 5 | } 6 | table { 7 | width: 100%; 8 | display: block; 9 | overflow-x: scroll !important; 10 | white-space: nowrap; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/DescriptionEditor.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonButton, 3 | IonChip, 4 | IonContent, 5 | IonIcon, 6 | IonItem, 7 | IonLabel, 8 | IonPopover, 9 | IonTextarea, 10 | } from "@ionic/react"; 11 | 12 | import { useTranslation } from "react-i18next"; 13 | import { Preferences } from "@capacitor/preferences"; 14 | import key from "../lib/storageKey.json"; 15 | import { trigger } from "../lib/Events"; 16 | import ReactMarkdown from "react-markdown"; 17 | import remarkGfm from "remark-gfm"; 18 | import rehypeExternalLinks from 'rehype-external-links' 19 | import { informationCircle, save } from "ionicons/icons"; 20 | 21 | import "./DescriptionEditor.css"; 22 | import style from "./markdown-styles.module.css" 23 | 24 | export default function DescriptionEditor({ 25 | id, 26 | description, 27 | setDescription, 28 | editable, 29 | accent, 30 | needToSave, 31 | setNeedToSave, 32 | }: { 33 | id: string; 34 | description: string; 35 | setDescription: any; 36 | editable: boolean; 37 | accent: string; 38 | needToSave: boolean; 39 | setNeedToSave: any; 40 | }) { 41 | const {t} = useTranslation(); 42 | let countdate_events_data = []; 43 | const edit_this_countdate_item_description = async ( 44 | newDescription: string | undefined | null 45 | ) => { 46 | if (newDescription === undefined || newDescription === null) return; 47 | const { value } = await Preferences.get({ key: key.data }); 48 | if (value) { 49 | countdate_events_data = JSON.parse(value); 50 | for (const i of countdate_events_data) { 51 | if (String(i.id) === String(id)) { 52 | i.description = newDescription; 53 | } 54 | } 55 | let content = JSON.stringify(countdate_events_data); 56 | await Preferences.set({ 57 | key: key.data, 58 | value: content, 59 | }); 60 | trigger("countdate_data:change"); 61 | } 62 | }; 63 | return ( 64 | <> 65 | {editable ? ( 66 | <> 67 | 68 | 69 | {t("c.editor.description")} 70 | 71 | 72 | 73 | { 81 | setDescription(e.detail.value!); 82 | setNeedToSave(true); 83 | }} 84 | > 85 | 86 |
87 | 93 | {" "} 94 | {" "} 95 | 96 | 97 | {t("c.editor.tips")} 98 | 99 | 100 | { 104 | edit_this_countdate_item_description(description); 105 | setNeedToSave(false); 106 | }} 107 | size="small" 108 | fill="clear" 109 | > 110 | 111 | 112 |
113 | 114 | ) : ( 115 | <> 116 | {needToSave ? ( 117 | 118 | 119 | {t("c.editor.needToSave")} 120 | 121 | ) : ( 122 | <> 123 | )} 124 | 125 | {description ? ( 126 | <> 127 | 133 | 134 | ) : ( 135 |
136 | {t("c.editor.noDescriptionData")} 137 |
138 | )} 139 | 140 | )} 141 | 142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /src/components/LanguageSelectModal.tsx: -------------------------------------------------------------------------------- 1 | import { IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonItem, IonLabel, IonList, IonModal, IonRadio, IonRadioGroup, IonTitle, IonToolbar } from "@ionic/react"; 2 | import { language } from "ionicons/icons"; 3 | import { useState } from "react"; 4 | import { useTranslation } from "react-i18next"; 5 | import { trigger } from "../lib/Events"; 6 | 7 | export default function LanguageSelectModal({accent}:{accent: string;}) { 8 | const {t,i18n} = useTranslation() 9 | 10 | const [modalIsOpen, setModalIsOpen] = useState(false); 11 | const [radioSelected, setRadioSelected] = useState("zh-TW"); 12 | 13 | const allLangName = ["zh-TW", "de", "en", "th", "zh-CN" ]; 14 | const langAndDetail:any = { 15 | "zh-TW": { 16 | name: "繁體中文", 17 | by: "sk5s" 18 | }, 19 | "de": { 20 | name: "Deutsch", 21 | by: "natowi" 22 | }, 23 | "en": { 24 | name: "English (US)", 25 | by: "sk5s, Nqtural" 26 | }, 27 | "th": { 28 | name: "Thai ไทย", 29 | by: "nutsupra" 30 | }, 31 | "zh-CN": { 32 | name: "简体中文", 33 | by: "sk5s" 34 | } 35 | } 36 | return ( 37 | <> 38 | setModalIsOpen(true)}> 39 | {t("c.settings.changeLang")} 40 | 41 | 42 | setModalIsOpen(false)}> 43 | 44 | 45 | {t("c.settings.changeLang")} 46 | 47 | setModalIsOpen(false)}> 48 | {t("g.close")} 49 | 50 | 51 | 52 | { 53 | console.log(`You selected: ${radioSelected}`); 54 | i18n.changeLanguage(radioSelected); 55 | setModalIsOpen(false) 56 | trigger("countdate_data:change") 57 | }}> 58 | {t("g.confirm")} 59 | 60 | 61 | 62 | 63 | 64 | 65 | { 67 | setRadioSelected(e.detail.value); 68 | }} 69 | value={radioSelected} 70 | > 71 | {(() => { 72 | let rows: any = []; 73 | allLangName.forEach((element,i) => { 74 | rows.push( 75 | 76 | 77 |
{langAndDetail[element].name}
78 | {langAndDetail[element].by} 79 |
80 |
81 | ); 82 | }); 83 | return rows; 84 | })()} 85 |
86 |
87 |
88 |
89 | 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /src/components/LocalizeBackButton.tsx: -------------------------------------------------------------------------------- 1 | import { IonButtons, IonBackButton } from "@ionic/react"; 2 | import { useTranslation } from "react-i18next"; 3 | 4 | export default function LocalizeBackButton({ color }: { color: string }) { 5 | const { t } = useTranslation(); 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/TextColorSelectModal.css: -------------------------------------------------------------------------------- 1 | .chrome-picker { 2 | width: 100% !important; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/TextColorSelectModal.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | IonIcon, 4 | IonLabel, 5 | IonModal, 6 | IonHeader, 7 | IonToolbar, 8 | IonTitle, 9 | IonButtons, 10 | IonButton, 11 | IonContent, 12 | } from "@ionic/react"; 13 | import { refresh, save, text } from "ionicons/icons"; 14 | import { useTranslation } from "react-i18next"; 15 | import { Preferences } from "@capacitor/preferences"; 16 | import key from "../lib/storageKey.json"; 17 | import { trigger } from "../lib/Events"; 18 | import { ChromePicker } from "react-color"; 19 | import "./TextColorSelectModal.css"; 20 | 21 | export default function TextColorSelectModal({ accent }: { accent: string }) { 22 | const {t} = useTranslation(); 23 | const [modalIsOpen, setModalIsOpen] = useState(false); 24 | const [hex, setHex] = useState(""); 25 | const changeTextColor = async (color: string) => { 26 | await Preferences.set({ 27 | key: key.textColor, 28 | value: color, 29 | }); 30 | console.log("change text color to: " + color); 31 | trigger("countdate_text:change"); 32 | setModalIsOpen(false); 33 | }; 34 | const removeTextColor = async () => { 35 | await Preferences.remove({ 36 | key: key.textColor, 37 | }); 38 | console.log("remove text color"); 39 | trigger("countdate_text:change"); 40 | setModalIsOpen(false); 41 | trigger("countdate_text:change"); 42 | }; 43 | const openPicker = async () => { 44 | setModalIsOpen(true); 45 | }; 46 | 47 | const handleChangeComplete = (color: any) => { 48 | console.log(color.hex); 49 | setHex(color.hex); 50 | }; 51 | 52 | return ( 53 | <> 54 | 55 | {t("c.settings.changeTextColor")} 56 | 57 | 58 | setModalIsOpen(false)}> 59 | 60 | 61 | {t("c.settings.changeTextColor")} 62 | 63 | setModalIsOpen(false)} color={accent}> 64 | {t("g.close")} 65 | 66 | 67 | 68 | 69 | 70 | 76 | {" "} 77 | {t("c.settings.followAccent")} 78 | 79 | changeTextColor(hex)} 81 | expand="full" 82 | shape="round" 83 | color={accent} 84 | > 85 | {t("c.settings.saveColor")} 86 | 87 |
88 |

89 | Text Color 文字顏色 90 |

91 | 92 |
93 |
94 |
95 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /src/components/TitleCard.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonCard, 3 | IonCardHeader, 4 | IonCardSubtitle, 5 | IonCardTitle, 6 | } from "@ionic/react"; 7 | 8 | export default function TitleCard(props: { 9 | title: string; 10 | subtitle: string; 11 | }): JSX.Element { 12 | return ( 13 |
14 | 15 | 16 | {props.subtitle} 17 | {props.title} 18 | 19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/markdown-styles.module.css: -------------------------------------------------------------------------------- 1 | .reactMarkDown p, 2 | .reactMarkDown ul, 3 | .reactMarkDown ol { 4 | font-size: 1.2rem; 5 | } -------------------------------------------------------------------------------- /src/constants/Constants.ts: -------------------------------------------------------------------------------- 1 | export const EXTEND_YEARS = 200 -------------------------------------------------------------------------------- /src/i18n/data/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "g": { 3 | "back": "Zurück", 4 | "close": "Schließen", 5 | "cancel": "Abbrechen", 6 | "confirm": "Übernehmen", 7 | "discard": "Verwerfen" 8 | }, 9 | "p": { 10 | "about": { 11 | "title": "Über Countdate", 12 | "version": "App Version: {{versionName}}", 13 | "platform": "Plattform:", 14 | "reload": "Neu laden", 15 | "slogan": "Datums-Countdown: Wissen, wie viele Tage noch bis zu einem bestimmten Datum verbleiben.", 16 | "link": { 17 | "landing": "Projekt Webseite", 18 | "github": "Projekt auf Github", 19 | "bug": "Fehler melden", 20 | "feature": "Neue Funktion vorschlagen" 21 | }, 22 | "more": { 23 | "title": "Weitere sk5s Apps: ", 24 | "weread": "Weread", 25 | "onea4paperyourlife": "OneA4PaperYourLife" 26 | } 27 | }, 28 | "add": { 29 | "title": "Zähldatum hinzufügen", 30 | "add": "Hinzufügen", 31 | "eventName": { 32 | "label": "Ereignisname", 33 | "placeholder": "Ereignisname hier eingeben" 34 | }, 35 | "tips": "Wählen Sie ein Datum vor dem heutigen Tag, um einen Vorwärtszähler hinzuzufügen. Wählen Sie ein Datum nach dem heutigen Tag, um einen Rückwärtszähler hinzuzufügen. Eine Beschreibung kann nach dem Hinzufügen des Datums hinzugefügt werden.", 36 | "toast": "Hinzugefügt!", 37 | "extend": "Wenn Sie ein Datum hinzufügen möchten, das lange zurückliegt oder lange in der Zukunft liegt, aktivieren Sie bitte in den Einstellungen den Modus 'Datumsverlängerung'." 38 | }, 39 | "edit": { 40 | "title": "Zähldatum bearbeiten", 41 | "description": "Klicken Sie zum Bearbeiten auf den Titel oder das Datum", 42 | "noData": "Kein Zähldatum vorhanden" 43 | }, 44 | "home": { 45 | "title": "Zähldatum", 46 | "countdown": "Herunterzählen", 47 | "countup": "Hochzählen", 48 | "daysView": "Tage", 49 | "weeksView": "Wochen", 50 | "monthsView": "Monate" 51 | }, 52 | "settings": { 53 | "title": "Einstellungen", 54 | "general": { 55 | "title": "Allgemein", 56 | "toggleDevMode": "Entwicklermodus", 57 | "testLocalNotification": "Lokale Benachrictigungen testen", 58 | "reviewTour": "Einführung wiederholen", 59 | "about": "Über Countdate", 60 | "toggleExtendMode": "Datumserweiterungsmodus umschalten (den Bereich der Datumsauswahl erweitern)", 61 | "toggleRelativeMode": "Umschalten des relativen Zeitmodus (30 Tage pro Monat)", 62 | "backup": "Sicherung" 63 | }, 64 | "theme": { 65 | "title": "Thema", 66 | "toggleDarkMode": "Dunkelmodus umschalten", 67 | "followSystem": "Dunkelmodus des Systems übernehmen" 68 | } 69 | }, 70 | "share": { 71 | "title": "Teilen", 72 | "date": "Datum", 73 | "openApp": "Mit Countdate App öffnen" 74 | }, 75 | "backup": { 76 | "title": "Daten sichern", 77 | "confirm": { 78 | "title": "Alle vorhandenen Daten überschreiben?" 79 | }, 80 | "message": { 81 | "failed": "Fehlgeschlagen", 82 | "import": { 83 | "successful": "Daten erfolgreich importiert" 84 | }, 85 | "export": { 86 | "successful": "Daten erfolgreich exportiert" 87 | } 88 | }, 89 | "export": "Export", 90 | "import": "Import", 91 | "share": { 92 | "title": "Countdate Sicherung", 93 | "text": "Countdate Sicherungsdatei teilen" 94 | } 95 | } 96 | }, 97 | "c": { 98 | "card": { 99 | "days": "Tage", 100 | "weeks": "Wochen", 101 | "months": "Monate", 102 | "years": "Jahre", 103 | "countdownEvent": "{{eventName}}" 104 | }, 105 | "cards": { 106 | "noData": "Keine Daten", 107 | "toCountup": "Zum Hochzählen", 108 | "toCountdown": "Zum Herunterzählen", 109 | "addCountdate": "Zähldatum hinzufügen", 110 | "addCountdate!": "Zähldatum hinzufügen!", 111 | "edit": "Bearbeiten", 112 | "settings": "Einstellungen", 113 | "checkData": "Daten prüfen", 114 | "delete!": "Löschen!", 115 | "deleted": "Gelöscht", 116 | "deleteMessage": "Dieser Vorgang kann nicht rückgängig gemacht werden!", 117 | "deleteAllData": "Alle Daten löschen", 118 | "copyData": "Ereignisdaten kopieren", 119 | "copied!": "Kopiert!" 120 | }, 121 | "settings": { 122 | "changeTextColor": "Textfarbe ändern", 123 | "changeAccent": "Akzentfarbe ändern", 124 | "followAccent": "Akzentfarbe übernehmen", 125 | "saveColor": "Farbe speichern", 126 | "changeLang": "Sprache ändern" 127 | }, 128 | "tour": { 129 | "title": "Countdate Einführung", 130 | "step_1": "Wie man ein neues Ereignis hinzufügt: Ein neues Zähldatum hinzufügen", 131 | "step_2": "Name des Ereignisses eingeben", 132 | "step_3": "Wählen Sie das Datum der Veranstaltung (entweder vor oder nach dem heutigen Tag)", 133 | "step_4": "Hinzufügen klicken", 134 | "step_5": "Wie man Ereignisse bearbeitet: Klicken Sie auf die Schaltfläche Bearbeiten", 135 | "step_6": "Klicken Sie zum Bearbeiten auf den Titel oder das Datum", 136 | "step_7": "Klicken Sie auf diese Schaltfläche, um das Ereignis zu löschen", 137 | "step_8": "Drücken Sie diese Taste und ziehen Sie das Ereignis, um es neu zu ordnen", 138 | "can_review": "Sie können die Seite 'Einstellungen' aufrufen, um die Einführung zu wiederholen", 139 | "learn_more": "Mehr über die Verwendung erfahren" 140 | }, 141 | "editor": { 142 | "description": "Beschreibung", 143 | "needToSave": "Speichern erforderlich", 144 | "noDescriptionData": "Keine Beschreibungsdaten", 145 | "tips": "Der Beschreibungstext unterstützt eine einfache Markdown-Syntax", 146 | "quickEdit": { 147 | "title": "Schnellbearbeitung", 148 | "confirm": "Änderung speichern?", 149 | "addOneMonth": "+1 Monat", 150 | "minusOneDay": "-1 Tag", 151 | "addOneDay": "+1 Tag" 152 | }, 153 | "edit": "Bearbeiten", 154 | "completeEdit": "Bearbeitung abschließen", 155 | "shareDescription": "Ereignisname und Datum teilen" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/i18n/data/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "g": { 3 | "back": "Back", 4 | "close": "Close", 5 | "cancel": "Cancel", 6 | "confirm": "Confirm", 7 | "discard": "Discard" 8 | }, 9 | "p": { 10 | "about": { 11 | "title": "About Countdate", 12 | "version": "App version: {{versionName}}", 13 | "platform": "Platform:", 14 | "reload": "Reload", 15 | "slogan": "Date Countdown: Know How Many Days Left To a Specific Date.", 16 | "link": { 17 | "landing": "Landing page", 18 | "github": "Project Github", 19 | "bug": "Report Bug", 20 | "feature": "Request feature" 21 | }, 22 | "more": { 23 | "title": "More sk5s apps: ", 24 | "weread": "Weread", 25 | "onea4paperyourlife": "OneA4PaperYourLife" 26 | } 27 | }, 28 | "add": { 29 | "title": "Add Countdate", 30 | "add": "Add", 31 | "eventName": { 32 | "label": "Event name", 33 | "placeholder": "Input event name" 34 | }, 35 | "tips": "Select date before today add a countup counter, select date after today add a countdown counter. Description can be added after adding the countdate.", 36 | "toast": "Added!", 37 | "extend": "If you want to add a date that's a long time ago or a long time in the future, please turn on the date extension mode in the settings." 38 | }, 39 | "edit": { 40 | "title": "Edit Countdate", 41 | "description": "Click on title or date to edit", 42 | "noData": "No Countdate Data" 43 | }, 44 | "home": { 45 | "title": "Countdate", 46 | "countdown": "Countdown", 47 | "countup": "Countup", 48 | "daysView": "Days", 49 | "weeksView": "Weeks", 50 | "monthsView": "Months" 51 | }, 52 | "settings": { 53 | "title": "Settings", 54 | "general": { 55 | "title": "General", 56 | "toggleDevMode": "Toggle dev mode", 57 | "testLocalNotification": "Test local notification", 58 | "reviewTour": "Review tour", 59 | "about": "About Countdate", 60 | "toggleExtendMode": "Toggle date extension mode (extend the range of the date selection)", 61 | "toggleRelativeMode": "Toggle relative time mode (30 days a month)", 62 | "backup": "Backup" 63 | }, 64 | "theme": { 65 | "title": "Theme", 66 | "toggleDarkMode": "Toggle dark mode", 67 | "followSystem": "Follow system dark mode" 68 | } 69 | }, 70 | "share": { 71 | "title": "Share", 72 | "date": "Date", 73 | "openApp": "Open with Countdate app" 74 | }, 75 | "backup": { 76 | "title": "Backup data", 77 | "confirm": { 78 | "title": "Overwrite all existing data?" 79 | }, 80 | "message": { 81 | "failed": "Failed", 82 | "import": { 83 | "successful": "Data imported successfully" 84 | }, 85 | "export": { 86 | "successful": "Data exported successfully" 87 | } 88 | }, 89 | "export": "Export", 90 | "import": "Import", 91 | "share": { 92 | "title": "Countdate Backup", 93 | "text": "Share your Countdate backup file" 94 | } 95 | } 96 | }, 97 | "c": { 98 | "card": { 99 | "days": "Days", 100 | "weeks": "Weeks", 101 | "months": "Months", 102 | "years": "Years", 103 | "countdownEvent": "{{eventName}}" 104 | }, 105 | "cards": { 106 | "noData": "No Data", 107 | "toCountup": "To countup", 108 | "toCountdown": "To countdown", 109 | "addCountdate": "Add", 110 | "addCountdate!": "Add Countdate!", 111 | "edit": "Edit", 112 | "settings": "Settings", 113 | "checkData": "Check Data", 114 | "delete!": "Delete!", 115 | "deleted": "Deleted", 116 | "deleteMessage": "This action cannot be undone", 117 | "deleteAllData": "Delete all data", 118 | "copyData": "Copy event data", 119 | "copied!": "Copied!" 120 | }, 121 | "settings": { 122 | "changeTextColor": "Change text color", 123 | "changeAccent": "Change accent color", 124 | "followAccent": "Follow accent", 125 | "saveColor": "Save color", 126 | "changeLang": "Change language" 127 | }, 128 | "tour": { 129 | "title": "Countdate app tour", 130 | "step_1": "How to add new event: Add a new Countdate", 131 | "step_2": "Input event name", 132 | "step_3": "Choose event date (either before or after today)", 133 | "step_4": "Click Add", 134 | "step_5": "How to edit events: Click on edit button", 135 | "step_6": "Click on title or date to edit", 136 | "step_7": "Click on this button to delete event", 137 | "step_8": "Press this button and drag the event to reorder", 138 | "can_review": "You can go to the settings page to review tour", 139 | "learn_more": "Learn more about how to use" 140 | }, 141 | "editor": { 142 | "description": "Description", 143 | "needToSave": "Need to save", 144 | "noDescriptionData": "No description data", 145 | "tips": "The description text supports some simple Markdown syntax", 146 | "quickEdit": { 147 | "title": "Quick edit", 148 | "confirm": "Confirm change?", 149 | "addOneMonth": "+1 month", 150 | "minusOneDay": "-1 day", 151 | "addOneDay": "+1 day" 152 | }, 153 | "edit": "Edit", 154 | "completeEdit": "Complete edit", 155 | "shareDescription": "Share event name and event date" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/i18n/data/th.json: -------------------------------------------------------------------------------- 1 | { 2 | "g": { 3 | "back": "กลับ", 4 | "close": "ปิด", 5 | "cancel": "ยกเลิก", 6 | "confirm": "ยืนยัน", 7 | "discard": "ทิ้ง" 8 | }, 9 | "p": { 10 | "about": { 11 | "title": "เกี่ยวกับ Countdate", 12 | "version": "App version: {{versionName}}", 13 | "platform": "Platform:", 14 | "reload": "Reload", 15 | "slogan": "Date Countdown: Know How Many Days Left To a Specific Date.", 16 | "link": { 17 | "landing": "Landing page", 18 | "github": "Project Github", 19 | "bug": "Report Bug", 20 | "feature": "Request feature" 21 | }, 22 | "more": { 23 | "title": "แอปอื่น ๆ จาก sk5s: ", 24 | "weread": "Weread", 25 | "onea4paperyourlife": "OneA4PaperYourLife" 26 | } 27 | }, 28 | "add": { 29 | "title": "เพิ่ม Countdate", 30 | "add": "เพิ่ม", 31 | "eventName": { 32 | "label": "กิจกรรมชื่อ", 33 | "placeholder": "ป้อนข้อมูลกิจกรรมชื่อ" 34 | }, 35 | "tips": "1. เลือกวันที่ในอดีตเพื่อนับไปข้างหน้า 2. เลือกวันที่ในอนาคตเพื่อนับถอยหลัง 3. สามารถเขียนคำอธิบายกิจกรรมได้หลังจากเพิ่มกิจกรรมเรียบร้อยแล้ว", 36 | "toast": "Added!", 37 | "extend": "If you want to add a date that's a long time ago or a long time in the future, please turn on the date extension mode in the settings." 38 | }, 39 | "edit": { 40 | "title": "แก้ไข Countdate", 41 | "description": "แตะที่ชื่อกิจกรรมหรือวันเดือนปีเพื่อแก้ไข", 42 | "noData": "No Countdate Data" 43 | }, 44 | "home": { 45 | "title": "Countdate", 46 | "countdown": "นับถอยหลัง", 47 | "countup": "นับไปข้างหน้า", 48 | "daysView": "วัน", 49 | "weeksView": "สัปดาห์", 50 | "monthsView": "เดือน" 51 | }, 52 | "settings": { 53 | "title": "การตั้งค่า", 54 | "general": { 55 | "title": "ทั่วไป", 56 | "toggleDevMode": "สลับโหมดนักพัฒนา", 57 | "testLocalNotification": "Test local notification", 58 | "reviewTour": "วิธีการใช้งาน", 59 | "about": "เกี่ยวกับ Countdate", 60 | "toggleExtendMode": "Toggle date extension mode (extend the range of the date selection)", 61 | "toggleRelativeMode": "Toggle relative time mode (30 days a month)", 62 | "backup": "Backup" 63 | }, 64 | "theme": { 65 | "title": "ธีม", 66 | "toggleDarkMode": "สลับเป็นโหมดมืด", 67 | "followSystem": "ติดตามระบบเป็นโหมดมืด" 68 | } 69 | }, 70 | "share": { 71 | "date": "Date", 72 | "openApp": "Open with Countdate app", 73 | "title": "Share" 74 | }, 75 | "backup": { 76 | "title": "Backup data", 77 | "confirm": { 78 | "title": "Overwrite all existing data?" 79 | }, 80 | "message": { 81 | "failed": "Failed", 82 | "import": { 83 | "successful": "Data imported successfully" 84 | }, 85 | "export": { 86 | "successful": "Data exported successfully" 87 | } 88 | }, 89 | "export": "Export", 90 | "import": "Import", 91 | "share": { 92 | "title": "Countdate Backup", 93 | "text": "Share your Countdate backup file" 94 | } 95 | } 96 | }, 97 | "c": { 98 | "card": { 99 | "days": "วัน", 100 | "weeks": "สัปดาห์", 101 | "months": "เดือน", 102 | "years": "Years", 103 | "countdownEvent": "{{eventName}} : เหลืออีก" 104 | }, 105 | "cards": { 106 | "noData": "ไม่พบข้อมูล", 107 | "toCountup": "To countup", 108 | "toCountdown": "To countdown", 109 | "addCountdate": "เพิ่ม Countdate", 110 | "addCountdate!": "เพิ่ม Countdate!", 111 | "edit": "แก้ไข", 112 | "settings": "การตั้งค่า", 113 | "checkData": "Check Data", 114 | "delete!": "Delete!", 115 | "deleted": "Deleted", 116 | "deleteMessage": "This action cannot be undone", 117 | "deleteAllData": "Delete all data", 118 | "copyData": "Copy event data", 119 | "copied!": "Copied!" 120 | }, 121 | "settings": { 122 | "changeTextColor": "เปลี่ยนสีตัวอักษร", 123 | "changeAccent": "เปลี่ยนโทนสี", 124 | "followAccent": "Follow accent", 125 | "saveColor": "Save color", 126 | "changeLang": "เลือกภาษาที่ใช้" 127 | }, 128 | "tour": { 129 | "title": "วิธีการใช้งาน Countdate", 130 | "step_1": "เพิ่มกิจกรรม", 131 | "step_2": "ระบุชื่อกิจกรรม", 132 | "step_3": "เลือกวันที่เพื่อเพิ่มกิจกรรม", 133 | "step_4": "แตะปุ่มเพิ่ม", 134 | "step_5": "แก้ไขกิจกรรมด้วยปุ่มแก้ไข", 135 | "step_6": "แตะที่ชื่อกิจกรรมหรือวันเดือนปีเพื่อแก้ไข", 136 | "step_7": "แตะที่ไอคอนถังขยะเพื่อลบกิจกรรม", 137 | "step_8": "แตะปุ่มนี้แล้วเลื่อนนิ้วเพื่อเรียงลำดับกิจกรรม", 138 | "can_review": "ไปที่การตั้งค่าเพื่อดูวิธีการใช้งานได้ตลอด", 139 | "learn_more": " เรียนรู้เพิ่มเติมเกี่ยววิธีการใช้งาน" 140 | }, 141 | "editor": { 142 | "description": "คำอธิบาย", 143 | "needToSave": "จำเป็นต้องบันทึก", 144 | "noDescriptionData": "No description data", 145 | "tips": "The description text supports some simple Markdown syntax", 146 | "quickEdit": { 147 | "title": "Quick edit", 148 | "confirm": "ยืนยันที่จะแก้ไข?", 149 | "addOneMonth": "+1 เดือน", 150 | "minusOneDay": "-1 วัน", 151 | "addOneDay": "+1 วัน" 152 | }, 153 | "edit": "แก้ไข", 154 | "completeEdit": "เสร็จสมบูรณ์แก้ไข", 155 | "shareDescription": "Share event name and event date" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/i18n/data/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "g": { 3 | "back": "返回", 4 | "close": "关闭", 5 | "cancel": "取消", 6 | "confirm": "确认", 7 | "discard": "舍弃" 8 | }, 9 | "p": { 10 | "about": { 11 | "title": "关于 Countdate", 12 | "version": "应用版本: {{versionName}}", 13 | "platform": "平台:", 14 | "reload": "重新整理", 15 | "slogan": "Countdate - 會考、學測、分科、檢定倒數,日期正數", 16 | "link": { 17 | "landing": "专案网页", 18 | "github": "专案 Github", 19 | "bug": "回报问题", 20 | "feature": "功能提议" 21 | }, 22 | "more": { 23 | "title": "更多 sk5s 应用程式:", 24 | "weread": "Weread", 25 | "onea4paperyourlife": "一張紙的人生 OneA4PaperYourLife" 26 | } 27 | }, 28 | "add": { 29 | "title": "新增 Countdate", 30 | "add": "新增", 31 | "eventName": { 32 | "label": "事件名称", 33 | "placeholder": "输入事件名称" 34 | }, 35 | "tips": "选择今天之前的日期可以添加一个正数计数器,选择今天之后的日期可以添加一个倒计时计数器。添加 Countdate 后可以再添加说明。", 36 | "toast": "已新增!", 37 | "extend": "如果要新增较久以前或很久以后的日期,请于设定中打开日期扩充模式。" 38 | }, 39 | "edit": { 40 | "title": "编辑 Countdate", 41 | "description": "点击标题或日期来编辑事件", 42 | "noData": "没有 Countdate 资料" 43 | }, 44 | "home": { 45 | "title": "Countdate", 46 | "countdown": "倒数", 47 | "countup": "正数", 48 | "daysView": "几天", 49 | "weeksView": "几周", 50 | "monthsView": "几月" 51 | }, 52 | "settings": { 53 | "title": "设置", 54 | "general": { 55 | "title": "一般", 56 | "toggleDevMode": "切换开发模式", 57 | "testLocalNotification": "测试本地通知", 58 | "reviewTour": "重看导览", 59 | "about": "关于 Countdate", 60 | "toggleExtendMode": "切换日期扩充模式(可选更久远的日期)", 61 | "toggleRelativeMode": "切换相對時間模式(30天一个月)", 62 | "backup": "备份" 63 | }, 64 | "theme": { 65 | "title": "主题", 66 | "toggleDarkMode": "切换黑暗模式", 67 | "followSystem": "跟随系统黑暗模式" 68 | } 69 | }, 70 | "share": { 71 | "title": "分享", 72 | "date": "日期", 73 | "openApp": "用 Countdate app 开启" 74 | }, 75 | "backup": { 76 | "title": "备份数据", 77 | "confirm": { 78 | "title": "覆盖所有现有资料?" 79 | }, 80 | "message": { 81 | "failed": "失败", 82 | "import": { 83 | "successful": "数据导入成功" 84 | }, 85 | "export": { 86 | "successful": "数据导出成功" 87 | } 88 | }, 89 | "export": "导出", 90 | "import": "导入", 91 | "share": { 92 | "title": "Countdate 备份", 93 | "text": "分享您的 Countdate 备份文件" 94 | } 95 | } 96 | }, 97 | "c": { 98 | "card": { 99 | "days": "天", 100 | "weeks": "周", 101 | "months": "月", 102 | "years": "年", 103 | "countdownEvent": "离{{eventName}}剩下" 104 | }, 105 | "cards": { 106 | "noData": "没有资料", 107 | "toCountup": "到正数", 108 | "toCountdown": "到倒数", 109 | "addCountdate": "新增", 110 | "addCountdate!": "新增 Countdate!", 111 | "edit": "编辑", 112 | "settings": "设置", 113 | "checkData": "檢查資料", 114 | "delete!": "刪除!", 115 | "deleted": "已刪除", 116 | "deleteMessage": "此操作無法回復", 117 | "deleteAllData": "刪除所有資料", 118 | "copyData": "複製事件資料", 119 | "copied!": "已複製!" 120 | }, 121 | "settings": { 122 | "changeTextColor": "选择字颜色", 123 | "changeAccent": "选择主题颜色", 124 | "followAccent": "跟随主题颜色", 125 | "saveColor": "储存颜色", 126 | "changeLang": "切换语言" 127 | }, 128 | "tour": { 129 | "title": "Countdate 应用导览", 130 | "step_1": "如何新增一个事件:新增一个 Countdate", 131 | "step_2": "输入事件名称", 132 | "step_3": "点选事件日期(在今天之前或之后皆可)", 133 | "step_4": "点击新增", 134 | "step_5": "如何编辑事件:点击编辑按钮", 135 | "step_6": "点击标题或日期来编辑事件", 136 | "step_7": "点击这个按钮来删除事件", 137 | "step_8": "按住按钮来重新排序事件", 138 | "can_review": "您可以进入设置页面重新查看游览", 139 | "learn_more": "了解更多使用方法" 140 | }, 141 | "editor": { 142 | "description": "说明", 143 | "needToSave": "尚未储存变更", 144 | "noDescriptionData": "没有说明资料", 145 | "tips": "说明栏文字支援一些简易的 Markdown 语法", 146 | "quickEdit": { 147 | "title": "快速编辑", 148 | "confirm": "确定更改?", 149 | "addOneMonth": "+1 月", 150 | "minusOneDay": "-1 天", 151 | "addOneDay": "+1 天" 152 | }, 153 | "edit": "编辑", 154 | "completeEdit": "完成编辑", 155 | "shareDescription": "分享事件名称和事件日期" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/i18n/data/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "g": { 3 | "back": "返回", 4 | "close": "關閉", 5 | "cancel": "取消", 6 | "confirm": "確認", 7 | "discard": "捨棄" 8 | }, 9 | "p": { 10 | "about": { 11 | "title": "關於 Countdate", 12 | "version": "應用版本: {{versionName}}", 13 | "platform": "平台:", 14 | "reload": "重新整理", 15 | "slogan": "Countdate - 會考、學測、分科、檢定倒數,日期正數", 16 | "link": { 17 | "landing": "專案網頁", 18 | "github": "專案 Github", 19 | "bug": "回報問題", 20 | "feature": "功能提議" 21 | }, 22 | "more": { 23 | "title": "更多 sk5s 應用程式:", 24 | "weread": "Weread", 25 | "onea4paperyourlife": "一張紙的人生 OneA4PaperYourLife" 26 | } 27 | }, 28 | "add": { 29 | "title": "新增 Countdate", 30 | "add": "新增", 31 | "eventName": { 32 | "label": "事件名稱", 33 | "placeholder": "輸入事件名稱" 34 | }, 35 | "tips": "選擇今天之前的日期可以新增一個正數計數器,選擇今天之後的日期可以新增一個倒計時計數器。新增 Countdate 後可以再增加說明。", 36 | "toast": "已新增!", 37 | "extend": "如果要新增較久以前或很久以後的日期,請於設定中打開日期擴充模式。" 38 | }, 39 | "edit": { 40 | "title": "編輯 Countdate", 41 | "description": "點擊標題或日期來編輯事件", 42 | "noData": "沒有 Countdate 資料" 43 | }, 44 | "home": { 45 | "title": "Countdate", 46 | "countdown": "倒數", 47 | "countup": "正數", 48 | "daysView": "幾天", 49 | "weeksView": "幾周", 50 | "monthsView": "幾月" 51 | }, 52 | "settings": { 53 | "title": "設定", 54 | "general": { 55 | "title": "一般", 56 | "toggleDevMode": "切換開發模式", 57 | "testLocalNotification": "測試本地通知", 58 | "reviewTour": "重看導覽", 59 | "about": "關於 Countdate", 60 | "toggleExtendMode": "切換日期擴充模式(可選更久遠的日期)", 61 | "toggleRelativeMode": "切換相對時間模式(30天一個月)", 62 | "backup": "備份" 63 | }, 64 | "theme": { 65 | "title": "主題", 66 | "toggleDarkMode": "切換黑暗模式", 67 | "followSystem": "跟隨系統黑暗模式" 68 | } 69 | }, 70 | "share": { 71 | "title": "分享", 72 | "date": "日期", 73 | "openApp": "用 Countdate app 開啟" 74 | }, 75 | "backup": { 76 | "title": "備份資料", 77 | "confirm": { 78 | "title": "覆蓋所有現有資料?" 79 | }, 80 | "message": { 81 | "failed": "失敗", 82 | "import": { 83 | "successful": "資料匯入成功" 84 | }, 85 | "export": { 86 | "successful": "資料匯出成功" 87 | } 88 | }, 89 | "export": "匯出", 90 | "import": "匯入", 91 | "share": { 92 | "title": "Countdate 備份", 93 | "text": "分享您的 Countdate 備份文件" 94 | } 95 | } 96 | }, 97 | "c": { 98 | "card": { 99 | "days": "天", 100 | "weeks": "周", 101 | "months": "月", 102 | "years": "年", 103 | "countdownEvent": "離{{eventName}}剩下" 104 | }, 105 | "cards": { 106 | "noData": "沒有資料", 107 | "toCountup": "到正數", 108 | "toCountdown": "到倒數", 109 | "addCountdate": "新增", 110 | "addCountdate!": "新增 Countdate!", 111 | "edit": "編輯", 112 | "settings": "設定", 113 | "checkData": "檢查資料", 114 | "delete!": "刪除!", 115 | "deleted": "已刪除", 116 | "deleteMessage": "此操作無法回復", 117 | "deleteAllData": "刪除所有資料", 118 | "copyData": "複製事件資料", 119 | "copied!": "已複製!" 120 | }, 121 | "settings": { 122 | "changeTextColor": "選擇字顏色", 123 | "changeAccent": "選擇主題顏色", 124 | "followAccent": "跟隨主題顏色", 125 | "saveColor": "儲存顏色", 126 | "changeLang": "切換語言" 127 | }, 128 | "tour": { 129 | "title": "Countdate 應用導覽", 130 | "step_1": "如何新增一個事件:新增一個 Countdate", 131 | "step_2": "輸入事件名稱", 132 | "step_3": "點選事件日期(在今天之前或之後皆可)", 133 | "step_4": "點擊新增", 134 | "step_5": "如何編輯事件:點擊編輯按鈕", 135 | "step_6": "點擊標題或日期來編輯事件", 136 | "step_7": "點擊這個按鈕來刪除事件", 137 | "step_8": "按住按鈕來重新排序事件", 138 | "can_review": "您可以進入設置頁面重新查看導覽", 139 | "learn_more": "了解更多使用方法" 140 | }, 141 | "editor": { 142 | "description": "說明", 143 | "needToSave": "尚未儲存變更", 144 | "noDescriptionData": "沒有說明資料", 145 | "tips": "說明欄文字支援一些簡易的 Markdown 語法", 146 | "quickEdit": { 147 | "title": "快速編輯", 148 | "confirm": "確定更改?", 149 | "addOneMonth": "+1 月", 150 | "minusOneDay": "-1 天", 151 | "addOneDay": "+1 天" 152 | }, 153 | "edit": "編輯", 154 | "completeEdit": "完成編輯", 155 | "shareDescription": "分享事件名稱和事件日期" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/i18n/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | import LanguageDetector from "i18next-browser-languagedetector"; 4 | import { Device } from "@capacitor/device"; 5 | 6 | // Languages codes: https://developers.google.com/admin-sdk/directory/v1/languages 7 | 8 | import de from "./data/de.json"; 9 | import en from "./data/en.json"; 10 | import tw from "./data/zh-TW.json"; 11 | import th from "./data/th.json"; 12 | import cn from "./data/zh-CN.json"; 13 | 14 | let device_language_code = ""; 15 | const getDeviceLanguageCode = async () => { 16 | device_language_code = await Device.getLanguageCode(); 17 | if (device_language_code === "zh") device_language_code = "zh-TW"; 18 | }; 19 | getDeviceLanguageCode(); 20 | 21 | const resources = { 22 | "en": { 23 | translation: en, 24 | }, 25 | "zh-TW": { 26 | translation: tw, 27 | }, 28 | "zh-CN": { 29 | translation: cn, 30 | }, 31 | "th": { 32 | translation: th, 33 | }, 34 | "de": { 35 | translation: de, 36 | } 37 | }; 38 | 39 | i18n 40 | .use(LanguageDetector) 41 | .use(initReactI18next) 42 | .init({ 43 | resources, 44 | lng: device_language_code, // Default lan 45 | fallbackLng: "en", // Fall back 46 | interpolation: { 47 | escapeValue: false, 48 | }, 49 | }); 50 | 51 | export default i18n; -------------------------------------------------------------------------------- /src/lib/Capitalize.js: -------------------------------------------------------------------------------- 1 | export function capitalize(word) { 2 | const firstLetter = word.charAt(0); 3 | const firstLetterCap = firstLetter.toUpperCase(); 4 | const remainingLetters = word.slice(1); 5 | return firstLetterCap + remainingLetters; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/Clipboard.js: -------------------------------------------------------------------------------- 1 | import { Clipboard } from "@capacitor/clipboard"; 2 | 3 | export async function copy(string) { 4 | await Clipboard.write({ 5 | string, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/Countdate.js: -------------------------------------------------------------------------------- 1 | let secondsInADay = 60 * 60 * 1000 * 24; 2 | 3 | function toIsoString(date) { 4 | var tzo = -date.getTimezoneOffset(), 5 | dif = tzo >= 0 ? "+" : "-", 6 | pad = function (num) { 7 | return (num < 10 ? "0" : "") + num; 8 | }; 9 | 10 | return ( 11 | date.getFullYear() + 12 | "-" + 13 | pad(date.getMonth() + 1) + 14 | "-" + 15 | pad(date.getDate()) + 16 | "T" + 17 | pad(date.getHours()) + 18 | ":" + 19 | pad(date.getMinutes()) + 20 | ":" + 21 | pad(date.getSeconds()) + 22 | dif + 23 | pad(Math.floor(Math.abs(tzo) / 60)) + 24 | ":" + 25 | pad(Math.abs(tzo) % 60) 26 | ); 27 | } 28 | 29 | function appendCorrectTimezone(date) { 30 | let now = new Date(); 31 | let tzo = -now.getTimezoneOffset(), 32 | dif = tzo >= 0 ? "+" : "-", 33 | pad = function (num) { 34 | return (num < 10 ? "0" : "") + num; 35 | }; 36 | 37 | return ( 38 | date.split("+")[0] + 39 | dif + 40 | pad(Math.floor(Math.abs(tzo) / 60)) + 41 | ":" + 42 | pad(Math.abs(tzo) % 60) 43 | ); 44 | } 45 | 46 | export function countUpFromTime(date) { 47 | // let nowStr = toIsoString(new Date()) 48 | let now = new Date(); 49 | let countFrom = new Date(appendCorrectTimezone(date)); 50 | let timeDifference = now.getTime() - countFrom.getTime(); 51 | let days = Math.floor((timeDifference / secondsInADay) * 1); 52 | return days; 53 | } 54 | 55 | export function countDownFromTime(date) { 56 | // let nowStr = toIsoString(new Date()) 57 | let now = new Date(); 58 | let countFrom = new Date(appendCorrectTimezone(date)); 59 | let timeDifference = countFrom.getTime() - now.getTime(); 60 | let days = Math.floor((timeDifference / secondsInADay) * 1); 61 | return days; 62 | } 63 | 64 | export function countFromTime(date) { 65 | let now = new Date(); 66 | let countFrom = new Date(appendCorrectTimezone(date)); 67 | let timeDifference = countFrom.getTime() - now.getTime(); 68 | if (timeDifference > 0) { 69 | // countdown 70 | return Math.floor((timeDifference / secondsInADay) * 1) 71 | } else { 72 | // countup 73 | return Math.floor(((now.getTime() - countFrom.getTime()) / secondsInADay) * 1) 74 | } 75 | } -------------------------------------------------------------------------------- /src/lib/Darkmode.js: -------------------------------------------------------------------------------- 1 | import { Preferences as Storage } from "@capacitor/preferences"; 2 | import key from "./storageKey.json"; 3 | import { trigger } from "./Events"; 4 | 5 | const matchMedia = window.matchMedia( 6 | "(prefers-color-scheme: dark)" 7 | ) 8 | export const prefersDark = matchMedia.matches; 9 | const preferDarkChange = () => { 10 | console.log("Prefer dark change", matchMedia.matches) 11 | get_user_theme_preference() 12 | } 13 | 14 | matchMedia.addEventListener("change", () => preferDarkChange()); 15 | 16 | get_user_theme_preference(); 17 | // trigger("countdate_darkmode:toggle"); 18 | let darkmodeEnable; 19 | function dark_enable(value) { 20 | if (value === "dark") { 21 | darkmodeEnable = true; 22 | document.body.classList.add("dark"); 23 | } else if (value === "light") { 24 | darkmodeEnable = false; 25 | document.body.classList.remove("dark") 26 | } else if (matchMedia.matches) { 27 | darkmodeEnable = matchMedia.matches; 28 | document.body.classList.add("dark"); 29 | } else { 30 | darkmodeEnable = matchMedia.matches; 31 | document.body.classList.remove("dark") 32 | } 33 | } 34 | export function set_dark_mode_toggle_to() { 35 | get_user_theme_preference(); 36 | return darkmodeEnable; 37 | } 38 | 39 | async function get_user_theme_preference() { 40 | const { value } = await Storage.get({ 41 | key: key.theme, 42 | }); 43 | console.log(value); 44 | dark_enable(value); 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/Events.js: -------------------------------------------------------------------------------- 1 | function on(eventType, listener) { 2 | document.addEventListener(eventType, listener); 3 | } 4 | 5 | function off(eventType, listener) { 6 | document.removeEventListener(eventType, listener); 7 | } 8 | 9 | function once(eventType, listener) { 10 | on(eventType, handleEventOnce); 11 | 12 | function handleEventOnce(event) { 13 | listener(event); 14 | off(eventType, handleEventOnce); 15 | } 16 | } 17 | 18 | function trigger(eventType, data) { 19 | const event = new CustomEvent(eventType, { detail: data }); 20 | document.dispatchEvent(event); 21 | } 22 | 23 | export { on, once, off, trigger }; 24 | -------------------------------------------------------------------------------- /src/lib/LocalNotification.js: -------------------------------------------------------------------------------- 1 | import { LocalNotifications } from "@capacitor/local-notifications"; 2 | 3 | // const day = (day) => { 4 | // return day * 86400 * 1000; 5 | // }; 6 | // const hour = (hour) => { 7 | // return hour * 3600 * 1000; 8 | // }; 9 | // const min = (min) => { 10 | // return min * 60 * 1000; 11 | // }; 12 | const sec = (sec) => { 13 | return sec * 1000; 14 | }; 15 | 16 | export async function Schedule(props) { 17 | await LocalNotifications.schedule({ 18 | notifications: [ 19 | { 20 | title: props.title, 21 | body: props.body, 22 | id: props.id, 23 | silent: true, 24 | schedule: { 25 | at: new Date(Date.now() + sec(1)), 26 | allowWhileIdle: true, 27 | }, 28 | }, 29 | ], 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/Toast.js: -------------------------------------------------------------------------------- 1 | import { Toast } from "@capacitor/toast"; 2 | 3 | export default async function show(text) { 4 | await Toast.show({ 5 | text, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/storageKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": "countdate_events_data", 3 | "theme": "countdate_prefer_theme", 4 | "dev": "countdate_dev_mode", 5 | "accent": "countdate_accent_color", 6 | "textColor": "countdate_text_color", 7 | "firstTime": "countdate_first", 8 | "extend": "countdate_extend_mode", 9 | "relative": "countdate_relative_mode" 10 | } 11 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import "./i18n/i18n"; 5 | 6 | // TODO: Update react render syntax 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | 14 | // const container = document.getElementById('root'); 15 | // const root = ReactDOM.createRoot(container!); 16 | // root.render( 17 | // 18 | // 19 | // 20 | // ); -------------------------------------------------------------------------------- /src/pages/About.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonContent, 3 | IonHeader, 4 | IonPage, 5 | IonTitle, 6 | IonToolbar, 7 | getPlatforms, 8 | IonChip, 9 | IonItem, 10 | IonThumbnail, 11 | IonLabel, 12 | IonButton, 13 | } from "@ionic/react"; 14 | 15 | import packageJson from "../../package.json"; 16 | import { useTranslation } from "react-i18next"; 17 | import logo from "../assets/countdateapp-logo-foreground.png"; 18 | import banner from "../assets/sk5s-project-bar.png"; 19 | import LocalizeBackButton from "../components/LocalizeBackButton"; 20 | import { isPlatform } from "@ionic/core"; 21 | import { useEffect, useState } from "react"; 22 | import { Device } from "@capacitor/device"; 23 | 24 | const About: React.FC<{ accent: string }> = ({ accent }) => { 25 | const { t } = useTranslation(); 26 | const [platform, setPlatform] = useState<'ios' | 'android' | 'web'>("android") 27 | useEffect(() => { 28 | const getDevicePlatform = async () => { 29 | const info = await Device.getInfo() 30 | setPlatform(info.platform) 31 | } 32 | getDevicePlatform() 33 | }, []) 34 | return ( 35 | 36 | 37 | 38 | 39 | {t("p.about.title")} 40 | 41 | 42 | 43 | {/* 44 | 45 | {t("p.about.title")} 46 | 47 | */} 48 |
49 | 50 | Logo 51 | 52 |

53 | {t("p.about.version",{versionName: packageJson.version})} 54 | {" "} 55 | {isPlatform("mobileweb") ? <> 56 | { 57 | window.location.reload() 58 | }}> 59 | {t("p.about.reload")} 60 | 61 | : <>} 62 |

63 |

64 | {t("p.about.platform")}{" "} 65 | {(() => { 66 | let platforms: any = []; 67 | getPlatforms().forEach((p, i) => { 68 | platforms.push( 69 | 70 | {p} 71 | 72 | ); 73 | }); 74 | return platforms; 75 | })()} 76 |

77 |
78 |

79 | {t("p.about.slogan")} 80 |

81 | 105 | 143 | 144 | {platform === "ios" ? null : ( 145 | <> 146 |

147 | {t("p.about.more.title")} 148 |

149 | 150 | 151 |
152 | 153 | 154 | Silhouette of mountains 155 | 156 | {t("p.about.more.weread")} 157 | 158 |
159 |
160 | 161 |
162 | 163 | 164 | Silhouette of mountains 165 | 166 | {t("p.about.more.onea4paperyourlife")} 167 | 168 |
169 |
170 | 171 | )} 172 | 173 | 174 | 175 | 176 |
177 |
178 | ); 179 | }; 180 | 181 | export default About; 182 | -------------------------------------------------------------------------------- /src/pages/Add.css: -------------------------------------------------------------------------------- 1 | ion-popover.wide-popover-300 { 2 | --width: 300px; 3 | } -------------------------------------------------------------------------------- /src/pages/Add.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonContent, 3 | IonHeader, 4 | IonPage, 5 | IonTitle, 6 | IonToolbar, 7 | IonDatetime, 8 | IonGrid, 9 | IonRow, 10 | IonCol, 11 | IonInput, 12 | IonLabel, 13 | IonButton, 14 | IonItem, 15 | useIonToast, 16 | IonIcon, 17 | IonPopover, 18 | } from "@ionic/react"; 19 | import { Preferences } from "@capacitor/preferences"; 20 | import { useEffect, useState } from "react"; 21 | import { useHistory, useLocation } from "react-router"; 22 | import { v4 as uuid } from "uuid"; 23 | import { format } from "date-fns"; 24 | 25 | import { on, trigger } from "../lib/Events"; 26 | import { useTranslation } from "react-i18next"; 27 | 28 | import key from "../lib/storageKey.json"; 29 | import { add, informationCircle } from "ionicons/icons"; 30 | import LocalizeBackButton from "../components/LocalizeBackButton"; 31 | import { EXTEND_YEARS } from '../constants/Constants' 32 | import './Add.css' 33 | 34 | const Add: React.FC<{ accent: string }> = ({ accent }) => { 35 | const { t } = useTranslation(); 36 | const [selectedDate, setSelectedDate] = useState( 37 | format(new Date(), "yyyy-MM-dd") + "T23:59:00+08:00" 38 | ); 39 | const [titleText, setTitleText] = useState(""); 40 | const [years,setYears] = useState(2); 41 | const [presentToast] = useIonToast(); 42 | const history = useHistory(); 43 | const location = useLocation() 44 | let countdate_events_data = []; 45 | const add_new_countdate_item = async (newItem: { 46 | event_name: any; 47 | date: string; 48 | description?: string; 49 | }) => { 50 | if (!newItem.event_name) return; 51 | const { value } = await Preferences.get({ key: key.data }); 52 | if (value) { 53 | countdate_events_data = JSON.parse(value); 54 | } else { 55 | countdate_events_data = []; 56 | } 57 | countdate_events_data.push({ 58 | id: uuid(), 59 | event_name: newItem.event_name, 60 | date: newItem.date, 61 | }); 62 | history.push("/home"); 63 | let content = JSON.stringify(countdate_events_data); 64 | await Preferences.set({ 65 | key: key.data, 66 | value: content, 67 | }); 68 | presentToast({ 69 | message: t("p.add.toast"), 70 | duration: 1500, 71 | position: "bottom", 72 | icon: add, 73 | color: accent, 74 | }); 75 | trigger("countdate_data:change"); 76 | }; 77 | // Listen the enter key 78 | const SearchF = (value: any) => { 79 | value = value.toLowerCase(); 80 | if (value === "enter") { 81 | add_new_countdate_item({ event_name: titleText, date: selectedDate }); 82 | } 83 | }; 84 | const getExtendMode = async () => { 85 | const { value } = await Preferences.get({ key: key.extend }); 86 | if (value === "true") { 87 | setYears(EXTEND_YEARS); 88 | } else { 89 | setYears(2); 90 | } 91 | }; 92 | on("countdate_extend:change", () => { 93 | getExtendMode(); 94 | }); 95 | getExtendMode(); 96 | useEffect(() => { 97 | console.log(location.search) 98 | const urlParams = new URLSearchParams(location.search) 99 | if (urlParams.get("title")){ 100 | setTitleText(urlParams.get("title")) 101 | } 102 | if (urlParams.get("date")){ 103 | if (urlParams.get("date").length === 10){ 104 | setSelectedDate(urlParams.get("date") + "T23:59:00+08:00") 105 | } 106 | } 107 | },[location]) 108 | return ( 109 | 110 | 111 | 112 | 113 | {t("p.add.title")} 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | {t("p.add.eventName.label")} 123 | 124 | SearchF(e.key)} 128 | clearInput={true} 129 | value={titleText} 130 | placeholder={ 131 | t("p.add.eventName.placeholder") 132 | } 133 | onIonChange={(e) => setTitleText(e.detail.value!)} 134 | > 135 | 136 | 137 | 138 | 139 | 140 | 147 | {" "} 148 | {" "} 149 | 150 | 151 | 152 | {t("p.add.tips")} 153 |
154 | {t("p.add.extend")} 155 |
156 |
157 |
158 |
159 | 160 | 161 | 170 | setSelectedDate( 171 | format(new Date(`${e.detail.value}`), "yyyy-MM-dd") + 172 | "T23:59:00+08:00" 173 | ) 174 | } 175 | showDefaultTitle={false} 176 | > 177 | 178 | 179 | 180 | 181 | { 187 | add_new_countdate_item({ 188 | event_name: titleText, 189 | date: selectedDate, 190 | }); 191 | }} 192 | > 193 | {t("p.add.add")} 194 | 195 | 196 | 197 |
198 |
199 |
200 | ); 201 | }; 202 | 203 | export default Add; 204 | -------------------------------------------------------------------------------- /src/pages/AppUrlListener.tsx: -------------------------------------------------------------------------------- 1 | import { App, URLOpenListenerEvent } from "@capacitor/app"; 2 | import { useEffect } from "react"; 3 | import { useHistory } from "react-router"; 4 | 5 | const AppUrlListener: React.FC = () => { 6 | let history = useHistory(); 7 | useEffect(() => { 8 | App.addListener('appUrlOpen', (event: URLOpenListenerEvent) => { 9 | // Example url: https://beerswift.app/tabs/tab2 10 | // slug = /tabs/tab2 11 | const slug = event.url.split('app').pop(); 12 | console.log(slug) 13 | if (slug) { 14 | history.push({ 15 | pathname: slug.split("?")[0], 16 | search: "?"+slug.split("?")[1] 17 | }); 18 | } 19 | // If no match, do nothing - let regular routing 20 | // logic take over 21 | }); 22 | }, []); 23 | 24 | return null; 25 | }; 26 | 27 | export default AppUrlListener; -------------------------------------------------------------------------------- /src/pages/Edit.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonContent, 3 | IonHeader, 4 | IonPage, 5 | IonTitle, 6 | IonToolbar, 7 | IonList, 8 | IonReorderGroup, 9 | ItemReorderEventDetail, 10 | IonIcon, 11 | IonItem, 12 | IonFab, 13 | IonFabButton, 14 | } from "@ionic/react"; 15 | import { add } from "ionicons/icons"; 16 | import { useTranslation } from "react-i18next"; 17 | import { useState, useEffect } from "react"; 18 | import { Preferences } from "@capacitor/preferences"; 19 | import key from "../lib/storageKey.json"; 20 | import CountdateItem from "../components/CountdownItem"; 21 | import { on, trigger } from "../lib/Events"; 22 | import LocalizeBackButton from "../components/LocalizeBackButton"; 23 | import CountDownUpSwitcher from "../components/CountDownUpSwitcher"; 24 | 25 | const NoCountdate = () => { 26 | const { t } = useTranslation(); 27 | return ( 28 | 29 |

30 | {t("p.edit.noData")} 31 |

32 |
33 | ) 34 | } 35 | 36 | const Edit: React.FC<{ accent: string; textColor: string; count:any; setCount:any; }> = ({ 37 | accent, 38 | textColor, 39 | count, 40 | setCount 41 | }) => { 42 | let countdate_events_data: { 43 | id: string; 44 | event_name: string; 45 | date: string; 46 | }[] = []; 47 | const { t } = useTranslation(); 48 | 49 | function swapCountdownElement(from: any, to: any) { 50 | let copyarr = [...countdownList]; 51 | copyarr.splice(to, 0, copyarr.splice(from, 1)[0]); 52 | return copyarr; 53 | } 54 | function swapCountupElement(from: any, to: any) { 55 | let copyarr = [...countupList]; 56 | copyarr.splice(to, 0, copyarr.splice(from, 1)[0]); 57 | return copyarr; 58 | } 59 | async function handleReorder(event: CustomEvent) { 60 | // The `from` and `to` properties contain the index of the item 61 | // when the drag started and ended, respectively 62 | console.log("Dragged from index", event.detail.from, "to", event.detail.to); 63 | let content = "" 64 | if (count === "countdown") { 65 | console.log(swapCountdownElement(event.detail.from, event.detail.to)); 66 | content = JSON.stringify( 67 | [...swapCountdownElement(event.detail.from, event.detail.to),...countupList] 68 | ) 69 | } else if (count === "countup"){ 70 | console.log(swapCountupElement(event.detail.from, event.detail.to)); 71 | content = JSON.stringify( 72 | [...countdownList,...swapCountupElement(event.detail.from, event.detail.to)] 73 | ) 74 | } else { 75 | console.error("Wrong count assigned") 76 | } 77 | console.log(content) 78 | await Preferences.set({ 79 | key: key.data, 80 | value: content, 81 | }); 82 | trigger("countdate_data:change"); 83 | 84 | // Finish the reorder and position the item in the DOM based on 85 | // where the gesture ended. This method can also be called directly 86 | // by the reorder group 87 | event.detail.complete(); 88 | } 89 | const [countupList,setCountupList] = useState([]) 90 | const [countdownList,setCountdownList] = useState([]) 91 | const check_countdate_events_storage_data = async () => { 92 | const { value } = await Preferences.get({ key: key.data }); 93 | if (value) { 94 | countdate_events_data = JSON.parse(value); 95 | } else { 96 | countdate_events_data = []; 97 | } 98 | let downlist = [] 99 | let uplist = [] 100 | countdate_events_data.forEach(event => { 101 | let now = new Date(); 102 | let countFrom = new Date(event.date.split("+")[0]); 103 | let timeDifference = now.getTime() - countFrom.getTime(); 104 | if (timeDifference < 0) { 105 | downlist.push({...event}) 106 | } else if (timeDifference >= 0) { 107 | uplist.push({...event}) 108 | } 109 | }) 110 | setCountdownList(downlist) 111 | setCountupList(uplist) 112 | }; 113 | useEffect(() => { 114 | check_countdate_events_storage_data(); 115 | on("countdate_data:change", () => { 116 | check_countdate_events_storage_data(); 117 | }); 118 | }, []); 119 | 120 | return ( 121 | 122 | 123 | 124 | 125 | {t("p.edit.title")} 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
134 | 135 | {t("p.edit.description")} 136 | 137 |
138 | 139 | {/* Countdate lists */} 140 | 141 | {/* The reorder gesture is disabled by default, enable it to drag and drop items */} 142 | 143 | {(() => { 144 | let row = []; 145 | if (count === "countdown") { 146 | if (!countdownList.length) return 147 | countdownList.forEach((event) => { 148 | //countdown 149 | row.push( 150 | 159 | ) 160 | }) 161 | } else if (count === "countup") { 162 | if (!countupList.length) return 163 | countupList.forEach((event) => { 164 | //countdown 165 | row.push( 166 | 175 | ) 176 | }) 177 | } 178 | return row; 179 | })()} 180 | 181 | 182 | 183 | {/* New countdate action button */} 184 | 185 | 186 | 187 | 188 | 189 |
190 |
191 | ); 192 | }; 193 | 194 | export default Edit; 195 | -------------------------------------------------------------------------------- /src/pages/Home.css: -------------------------------------------------------------------------------- 1 | ion-refresher{ 2 | background-color: var(--background); 3 | } -------------------------------------------------------------------------------- /src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IonContent, 3 | IonHeader, 4 | IonPage, 5 | IonTitle, 6 | IonToolbar, 7 | IonFab, 8 | IonFabButton, 9 | IonIcon, 10 | IonSegment, 11 | IonSegmentButton, 12 | IonLabel, 13 | IonFooter, 14 | IonRefresher, 15 | IonRefresherContent, 16 | } from "@ionic/react"; 17 | 18 | import { add } from "ionicons/icons"; 19 | 20 | import CountCards from "../components/CountCards"; 21 | import { useTranslation } from "react-i18next"; 22 | import { useEffect, useState } from "react"; 23 | import { useSwipeable } from "react-swipeable"; 24 | import { trigger, on } from "../lib/Events"; 25 | import "./Home.css" 26 | import CountDownUpSwitcher from "../components/CountDownUpSwitcher"; 27 | import { Device } from "@capacitor/device"; 28 | 29 | const Home: React.FC<{ accent: string; textColor: string;count:any;setCount:any;view:string;setView:any;relative:boolean; }> = ({ 30 | accent, 31 | textColor, 32 | count, 33 | setCount, 34 | view, 35 | setView, 36 | relative 37 | }) => { 38 | const { t } = useTranslation(); 39 | const [platform, setPlatform] = useState<'ios' | 'android' | 'web'>("android") 40 | 41 | const handlers = useSwipeable({ 42 | onSwipedLeft: () => left(), 43 | onSwipedRight: () => left(), 44 | }); 45 | const left = () => { 46 | if (count === "countdown") { 47 | setCount("countup"); 48 | localStorage.setItem("countdownOrUp", "up") 49 | } else { 50 | setCount("countdown"); 51 | localStorage.removeItem("countdownOrUp") 52 | } 53 | }; 54 | const handleRefresh = (event: any) => { 55 | trigger("countdate_data:change"); 56 | setTimeout(() => { 57 | event.detail.complete(); 58 | }, 500); 59 | }; 60 | 61 | useEffect(() => { 62 | // Translation not updating workaround 63 | on("countdate_data:change", () => { 64 | setView("weeks") 65 | setTimeout(() => { 66 | setView("days") 67 | }, 100); 68 | }) 69 | const getDevicePlatform = async () => { 70 | const info = await Device.getInfo() 71 | setPlatform(info.platform) 72 | } 73 | getDevicePlatform() 74 | }, []) 75 | 76 | return ( 77 | 78 | 79 | 80 | {t("p.home.title")} 81 | 82 | 83 | 84 | {/* Refresher */} 85 | {platform === "ios" ? null : ( 86 | 87 | 88 | 89 | )} 90 | 91 | 92 | 93 | 94 | 95 |
96 | {/* Countcards */} 97 | 105 |
106 | 107 | {/* New countdate action button */} 108 | 109 | 110 | 111 | 112 | 113 |
114 | 115 | {/* Days and weeks switcher */} 116 | 117 | setView(`${e.detail.value}`)} 121 | > 122 | 123 | {t("p.home.daysView")} 124 | 125 | 126 | {t("p.home.weeksView")} 127 | 128 | 129 | {t("p.home.monthsView")} 130 | 131 | 132 | 133 | 134 |
135 | ); 136 | }; 137 | 138 | export default Home; 139 | -------------------------------------------------------------------------------- /src/pages/Settings.css: -------------------------------------------------------------------------------- 1 | ion-item { 2 | cursor: pointer; 3 | } 4 | a { 5 | text-decoration: none !important; 6 | } 7 | .ion-text-wrap { 8 | text-wrap: wrap; 9 | } -------------------------------------------------------------------------------- /src/pages/Share.tsx: -------------------------------------------------------------------------------- 1 | import { IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonContent, IonHeader, IonIcon, IonPage, IonTitle, IonToolbar } from "@ionic/react"; 2 | import { useTranslation } from "react-i18next"; 3 | import { useEffect, useState } from "react"; 4 | import { useHistory, useLocation } from "react-router"; 5 | import { isPlatform } from "@ionic/core"; 6 | import { informationCircleSharp, settingsSharp, shareSocialSharp } from "ionicons/icons"; 7 | import logo from "../assets/countdateapp-logo-foreground.png"; 8 | 9 | export default function Share({ 10 | accent 11 | }) { 12 | const {t} = useTranslation() 13 | const location = useLocation() 14 | const history = useHistory() 15 | const [event,setEvent] = useState({title:"",date:"",dateRaw:""}) 16 | const formatDate = (date) => { 17 | let formatted_date = `${date.getFullYear()}/${date.getMonth()+1}/${date.getDate()}` 18 | return formatted_date; 19 | } 20 | useEffect(() => { 21 | console.log(location.search) 22 | let title = "" 23 | let dateRaw = "" 24 | let date = "" 25 | const urlParams = new URLSearchParams(location.search) 26 | if (urlParams.get("title")){ 27 | title = urlParams.get("title") 28 | } 29 | if (urlParams.get("date")){ 30 | dateRaw = urlParams.get("date") 31 | if (urlParams.get("date").length === 10){ 32 | date = urlParams.get("date") + "T23:59:00+08:00" 33 | } 34 | } 35 | setEvent({ 36 | title: title, 37 | dateRaw: dateRaw, 38 | date: date 39 | }) 40 | },[]) 41 | return ( 42 | 43 | 44 | 45 | 46 | { 47 | history.push("/settings") 48 | }}> 49 | 50 | 51 | { 52 | history.push("/about") 53 | }}> 54 | 55 | 56 | 57 | {t("p.share.title")} 58 | 59 | 60 | 61 | 62 | 63 | {t("p.add.eventName.label")} : {event.title} 64 | {t("p.share.date")} : {formatDate(new Date(event.date))} 65 | 66 | 67 | 68 | {isPlatform("mobileweb") ? <> 69 | { 70 | window.location.replace(`countdate://app/add?title=${event.title}&date=${event.dateRaw}`) 71 | }}> 72 | {t("p.share.openApp")} 73 | 74 | : <>} 75 | 76 | 77 |
78 |
79 | Logo 80 | {!isPlatform("ios") && ( 81 |
82 | 87 | Get it on Google Play 92 | 93 |
94 | )} 95 |
96 | 102 | Download on the App Store 106 | 107 |
108 |
109 |
110 |
111 | ) 112 | } 113 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": true, 7 | "checkJs": false, 8 | "skipLibCheck": true, 9 | "esModuleInterop": false, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "module": "ESNext", 14 | "moduleResolution": "Node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react-jsx" 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import legacy from '@vitejs/plugin-legacy' 2 | import react from '@vitejs/plugin-react' 3 | import { defineConfig } from 'vite' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | react(), 9 | legacy() 10 | ] 11 | }) --------------------------------------------------------------------------------