├── .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 |
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 |
155 |
156 | {t("p.about.more.weread")}
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
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 |

80 | {!isPlatform("ios") && (
81 |
94 | )}
95 |
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 | })
--------------------------------------------------------------------------------