Compare commits
1 Commits
main
...
android-ve
| Author | SHA1 | Date | |
|---|---|---|---|
| f993bc5810 |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
99
.github/workflows/codeql.yml
vendored
@@ -1,99 +0,0 @@
|
|||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL Advanced"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
schedule:
|
|
||||||
- cron: '27 4 * * 6'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze (${{ matrix.language }})
|
|
||||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
|
||||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
|
||||||
# - https://gh.io/supported-runners-and-hardware-resources
|
|
||||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
|
||||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
|
||||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
|
||||||
permissions:
|
|
||||||
# required for all workflows
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
# required to fetch internal or private CodeQL packs
|
|
||||||
packages: read
|
|
||||||
|
|
||||||
# only required for workflows in private repositories
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- language: javascript-typescript
|
|
||||||
build-mode: none
|
|
||||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
|
||||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
|
||||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
|
||||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
|
||||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
|
||||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
|
||||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
|
||||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
|
||||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
|
||||||
# or others). This is typically only required for manual builds.
|
|
||||||
# - name: Setup runtime (example)
|
|
||||||
# uses: actions/setup-example@v1
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v4
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
build-mode: ${{ matrix.build-mode }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
|
|
||||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
|
||||||
# queries: security-extended,security-and-quality
|
|
||||||
|
|
||||||
# If the analyze step fails for one of the languages you are analyzing with
|
|
||||||
# "We were unable to automatically build your code", modify the matrix above
|
|
||||||
# to set the build mode to "manual" for that language. Then modify this step
|
|
||||||
# to build your code.
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
||||||
- name: Run manual build steps
|
|
||||||
if: matrix.build-mode == 'manual'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
|
||||||
'languages you are analyzing, replace this with the commands to build' \
|
|
||||||
'your code, for example:'
|
|
||||||
echo ' make bootstrap'
|
|
||||||
echo ' make release'
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v4
|
|
||||||
with:
|
|
||||||
category: "/language:${{matrix.language}}"
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
mixaciric000@gmail.com.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
|
||||||
https://www.contributor-covenant.org/translations.
|
|
||||||
101
android/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||||
|
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.aar
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
|
# release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
#*.jks
|
||||||
|
#*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
||||||
|
# Android Profiling
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
# Cordova plugins for Capacitor
|
||||||
|
capacitor-cordova-android-plugins
|
||||||
|
|
||||||
|
# Copied web assets
|
||||||
|
app/src/main/assets/public
|
||||||
|
|
||||||
|
# Generated Config files
|
||||||
|
app/src/main/assets/capacitor.config.json
|
||||||
|
app/src/main/assets/capacitor.plugins.json
|
||||||
|
app/src/main/res/xml/config.xml
|
||||||
2
android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/build/*
|
||||||
|
!/build/.npmkeep
|
||||||
54
android/app/build.gradle
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "com.habitgrid.app"
|
||||||
|
compileSdk rootProject.ext.compileSdkVersion
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.habitgrid.app"
|
||||||
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
aaptOptions {
|
||||||
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||||
|
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
flatDir{
|
||||||
|
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||||
|
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||||
|
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||||
|
implementation project(':capacitor-android')
|
||||||
|
testImplementation "junit:junit:$junitVersion"
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||||
|
implementation project(':capacitor-cordova-android-plugins')
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: 'capacitor.build.gradle'
|
||||||
|
|
||||||
|
try {
|
||||||
|
def servicesJSON = file('google-services.json')
|
||||||
|
if (servicesJSON.text) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||||
|
}
|
||||||
19
android/app/capacitor.build.gradle
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_21
|
||||||
|
targetCompatibility JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hasProperty('postBuildExtras')) {
|
||||||
|
postBuildExtras()
|
||||||
|
}
|
||||||
21
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.getcapacitor.myapp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useAppContext() throws Exception {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
41
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/title_activity_main"
|
||||||
|
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths"></meta-data>
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<!-- Permissions -->
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.habitgrid.app;
|
||||||
|
|
||||||
|
import com.getcapacitor.BridgeActivity;
|
||||||
|
|
||||||
|
public class MainActivity extends BridgeActivity {}
|
||||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</vector>
|
||||||
170
android/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#26A69A"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
</vector>
|
||||||
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
12
android/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
||||||
7
android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">HabitGrid</string>
|
||||||
|
<string name="title_activity_main">HabitGrid</string>
|
||||||
|
<string name="package_name">com.habitgrid.app</string>
|
||||||
|
<string name="custom_url_scheme">com.habitgrid.app</string>
|
||||||
|
</resources>
|
||||||
22
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:background">@null</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||||
|
<item name="android:background">@drawable/splash</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
5
android/app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-path name="my_images" path="." />
|
||||||
|
<cache-path name="my_cache_images" path="." />
|
||||||
|
</paths>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.getcapacitor.myapp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() throws Exception {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.7.2'
|
||||||
|
classpath 'com.google.gms:google-services:4.4.2'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "variables.gradle"
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
3
android/capacitor.settings.gradle
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
include ':capacitor-android'
|
||||||
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
22
android/gradle.properties
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
252
android/gradlew
vendored
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
94
android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
5
android/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
include ':app'
|
||||||
|
include ':capacitor-cordova-android-plugins'
|
||||||
|
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||||
|
|
||||||
|
apply from: 'capacitor.settings.gradle'
|
||||||
16
android/variables.gradle
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
ext {
|
||||||
|
minSdkVersion = 23
|
||||||
|
compileSdkVersion = 35
|
||||||
|
targetSdkVersion = 35
|
||||||
|
androidxActivityVersion = '1.9.2'
|
||||||
|
androidxAppCompatVersion = '1.7.0'
|
||||||
|
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||||
|
androidxCoreVersion = '1.15.0'
|
||||||
|
androidxFragmentVersion = '1.8.4'
|
||||||
|
coreSplashScreenVersion = '1.0.1'
|
||||||
|
androidxWebkitVersion = '1.12.1'
|
||||||
|
junitVersion = '4.13.2'
|
||||||
|
androidxJunitVersion = '1.2.1'
|
||||||
|
androidxEspressoCoreVersion = '3.6.1'
|
||||||
|
cordovaAndroidVersion = '10.1.1'
|
||||||
|
}
|
||||||
12
capacitor.config.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { CapacitorConfig } from '@capacitor/cli';
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: 'com.habitgrid.app',
|
||||||
|
appName: 'HabitGrid',
|
||||||
|
webDir: 'dist',
|
||||||
|
server: {
|
||||||
|
androidScheme: 'https',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -34,14 +34,13 @@
|
|||||||
<meta property="og:description" content="Track your habits and visualize your progress with HabitGrid." />
|
<meta property="og:description" content="Track your habits and visualize your progress with HabitGrid." />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://myhabitgrid.com" />
|
<meta property="og:url" content="https://myhabitgrid.com" />
|
||||||
<meta property="og:image" content="/assets/fav.png" />
|
<meta property="og:image" content="./assets/fav.png" />
|
||||||
<!-- Twitter Card Meta Tags -->
|
<!-- Twitter Card Meta Tags -->
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:title" content="Habit Tracker App" />
|
<meta name="twitter:title" content="Habit Tracker App" />
|
||||||
<meta name="twitter:description" content="Track your habits and visualize your progress with HabitGrid." />
|
<meta name="twitter:description" content="Track your habits and visualize your progress with HabitGrid." />
|
||||||
<meta name="twitter:image" content="/assets/fav.png" />
|
<meta name="twitter:image" content="./assets/fav.png" />
|
||||||
<link rel="icon" type="image/png" href="/assets/fav.png" />
|
<link rel="icon" type="image/png" href="./assets/fav.png" />
|
||||||
<link rel="stylesheet" href="/src/index.css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100">
|
<body class="bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
1023
package-lock.json
generated
@@ -6,7 +6,11 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host :: --port 3000",
|
"dev": "vite --host :: --port 3000",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview --host :: --port 3000"
|
"preview": "vite preview --host :: --port 3000",
|
||||||
|
"cap:init": "npx cap init HabitGrid com.habitgrid.app --web-dir=dist",
|
||||||
|
"cap:sync": "npx cap sync",
|
||||||
|
"cap:android": "npx cap add android",
|
||||||
|
"cap:open": "npx cap open android"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
@@ -39,6 +43,9 @@
|
|||||||
"@babel/parser": "^7.27.0",
|
"@babel/parser": "^7.27.0",
|
||||||
"@babel/traverse": "^7.27.0",
|
"@babel/traverse": "^7.27.0",
|
||||||
"@babel/types": "^7.27.0",
|
"@babel/types": "^7.27.0",
|
||||||
|
"@capacitor/android": "^7.4.3",
|
||||||
|
"@capacitor/cli": "^7.4.3",
|
||||||
|
"@capacitor/core": "^7.4.3",
|
||||||
"@types/node": "^20.8.3",
|
"@types/node": "^20.8.3",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
|
|||||||
@@ -1,209 +1,102 @@
|
|||||||
[
|
[
|
||||||
"You did it! 🎉",
|
"Great job! Keep going!",
|
||||||
"High five! ✋",
|
"You're on fire! 🔥",
|
||||||
"You’re awesome!",
|
"Consistency is key!",
|
||||||
"Keep rocking!",
|
"Amazing streak!",
|
||||||
"Level up! 🚀",
|
"You crushed it today!",
|
||||||
"You’re a legend!",
|
"Small steps, big results!",
|
||||||
"That’s how it’s done!",
|
"Habit hero!",
|
||||||
"You’re crushing it!",
|
"Progress, not perfection!",
|
||||||
"Go you!",
|
"Every dot counts!",
|
||||||
|
"Keep up the momentum!",
|
||||||
|
"You’re building something awesome!",
|
||||||
|
"One step closer to your goal!",
|
||||||
"You’re unstoppable!",
|
"You’re unstoppable!",
|
||||||
"Boom! Achievement unlocked!",
|
"Keep the streak alive!",
|
||||||
"You’re a superstar!",
|
"You’re making it happen!",
|
||||||
"You make it look easy!",
|
"Your effort is inspiring!",
|
||||||
"You’re a force of nature!",
|
"You’re a streak superstar!",
|
||||||
"You’re a wizard! 🧙♂️",
|
"Every day matters!",
|
||||||
"You’re a ninja! 🥷",
|
"You’re a habit legend!",
|
||||||
"You’re a rockstar! 🎸",
|
"You’re doing fantastic!",
|
||||||
"You’re a champion! 🏆",
|
"Keep shining!",
|
||||||
"You’re a hero! 🦸♂️",
|
"You’re a role model!",
|
||||||
"You’re a genius!",
|
"You’re a champion!",
|
||||||
"You’re a boss!",
|
"You’re making progress!",
|
||||||
"You’re a machine!",
|
|
||||||
"You’re a powerhouse!",
|
|
||||||
"You’re a trailblazer!",
|
|
||||||
"You’re a game changer!",
|
|
||||||
"You’re a winner!",
|
"You’re a winner!",
|
||||||
"You’re a motivator!",
|
"You’re a streak master!",
|
||||||
"You’re a creator!",
|
"You’re a habit machine!",
|
||||||
"You’re a dreamer!",
|
"You’re a streak builder!",
|
||||||
"You’re a doer!",
|
"You’re a streak star!",
|
||||||
"You’re a finisher!",
|
"You’re a streak hero!",
|
||||||
"You’re a Firestarter!",
|
"You’re a streak ninja!",
|
||||||
"You’re a closer!",
|
"You’re a streak wizard!",
|
||||||
"You’re a believer!",
|
"You’re a streak warrior!",
|
||||||
"You’re a planner!",
|
"You’re a streak explorer!",
|
||||||
"You’re an organizer!",
|
"You’re a streak adventurer!",
|
||||||
"You’re a strategist!",
|
"You’re a streak conqueror!",
|
||||||
"You’re a visionary!",
|
"You’re a streak champion!",
|
||||||
"You’re an optimist!",
|
"You’re a streak genius!",
|
||||||
"You’re an enthusiast!",
|
"You’re a streak guru!",
|
||||||
"You’re a player!",
|
"You’re a streak expert!",
|
||||||
"You’re a contender!",
|
"You’re a streak pro!",
|
||||||
"You’re a competitor!",
|
"You’re a streak veteran!",
|
||||||
"You’re a challenger!",
|
"You’re a streak rookie!",
|
||||||
"You’re a victor!",
|
"You’re a streak all-star!",
|
||||||
"You’re a survivor!",
|
"You’re a streak MVP!",
|
||||||
"You’re a thriver!",
|
"You’re a streak superstar!",
|
||||||
"You’re an overcomer!",
|
"You’re a streak rockstar!",
|
||||||
"You’re a high achiever!",
|
"You’re a streak dynamo!",
|
||||||
"Not all those who wander are lost. —J.R.R. Tolkien",
|
"You’re a streak powerhouse!",
|
||||||
"So it goes. —Kurt Vonnegut",
|
"You’re a streak inspiration!",
|
||||||
"The only way out is through. —Robert Frost",
|
"You’re a streak motivator!",
|
||||||
"You have brains in your head. You have feet in your shoes. You can steer yourself any direction you choose. —Dr. Seuss",
|
"You’re a streak leader!",
|
||||||
"Courage is found in unlikely places. —J.R.R. Tolkien",
|
"You’re a streak innovator!",
|
||||||
"Do or do not. There is no try. —Yoda",
|
"You’re a streak creator!",
|
||||||
"It’s dangerous to go alone! Take this. —The Legend of Zelda",
|
"You’re a streak builder!",
|
||||||
"The game is afoot! —Sherlock Holmes",
|
"You’re a streak achiever!",
|
||||||
"To the stars who listen and the dreams that are answered. —Sarah J. Maas",
|
"You’re a streak doer!",
|
||||||
"All we have to decide is what to do with the time that is given us. —Gandalf",
|
"You’re a streak finisher!",
|
||||||
"Even the smallest person can change the course of the future. —Galadriel",
|
"You’re a streak starter!",
|
||||||
"Winter passed and the world grew up. —C.S. Lewis",
|
"You’re a streak closer!",
|
||||||
"The secret of getting ahead is getting started. —Mark Twain",
|
"You’re a streak winner!",
|
||||||
"Not all heroes wear capes.",
|
"You’re a streak believer!",
|
||||||
"Achievement unlocked!",
|
"You’re a streak dreamer!",
|
||||||
"You rolled a natural 20!",
|
"You’re a streak thinker!",
|
||||||
"The odds are ever in your favor.",
|
"You’re a streak planner!",
|
||||||
"You found the last Horcrux!",
|
"You’re a streak organizer!",
|
||||||
"You solved the puzzle before Watson!",
|
"You’re a streak strategist!",
|
||||||
"You’re the plot twist everyone needed.",
|
"You’re a streak tactician!",
|
||||||
"You’re the missing page in the story.",
|
"You’re a streak visionary!",
|
||||||
"You’re the last piece of the puzzle.",
|
"You’re a streak optimist!",
|
||||||
"You’re the answer to the riddle.",
|
"You’re a streak realist!",
|
||||||
"You’re the light at the end of the tunnel.",
|
"You’re a streak enthusiast!",
|
||||||
"You’re the hero of your own epic.",
|
"You’re a streak supporter!",
|
||||||
"You’re the author of your next chapter.",
|
"You’re a streak encourager!",
|
||||||
"You’re the main character energy!",
|
"You’re a streak helper!",
|
||||||
"You’re the chosen one (but in a good way).",
|
"You’re a streak friend!",
|
||||||
"You’re the unexpected ending everyone loves.",
|
"You’re a streak teammate!",
|
||||||
"You’re the plot armor in a tough scene.",
|
"You’re a streak partner!",
|
||||||
"You’re the magic in the mundane.",
|
"You’re a streak ally!",
|
||||||
"You’re the hope in the dystopia.",
|
"You’re a streak companion!",
|
||||||
"You’re the clever twist in the mystery.",
|
"You’re a streak buddy!",
|
||||||
"You’re the spark in the revolution.",
|
"You’re a streak pal!",
|
||||||
"You’re the prophecy fulfilled.",
|
"You’re a streak mate!",
|
||||||
"You’re the legend in the making.",
|
"You’re a streak peer!",
|
||||||
"You’re the muse for tomorrow’s story.",
|
"You’re a streak colleague!",
|
||||||
"You’re the ink in the pen of progress.",
|
"You’re a streak associate!",
|
||||||
"You’re the page-turner in a slow chapter.",
|
"You’re a streak collaborator!",
|
||||||
"You’re the secret passage in the labyrinth.",
|
"You’re a streak contributor!",
|
||||||
"You’re the dragon’s gold at the end of the quest.",
|
"You’re a streak participant!",
|
||||||
"You’re the ring-bearer on the journey.",
|
"You’re a streak member!",
|
||||||
"You’re the phoenix rising from the ashes.",
|
"You’re a streak player!",
|
||||||
"You’re the sword in the stone.",
|
"You’re a streak contender!",
|
||||||
"You’re the magic bean that grew the beanstalk.",
|
"You’re a streak competitor!",
|
||||||
"You’re the time traveler who fixed the timeline.",
|
"You’re a streak challenger!",
|
||||||
"You’re the detective who cracked the case.",
|
"You’re a streak rival!",
|
||||||
"You’re the rebel with a cause.",
|
"You’re a streak victor!",
|
||||||
"You’re the wizard who remembered the spell.",
|
"You’re a streak survivor!",
|
||||||
"You’re the hobbit who left the Shire.",
|
"You’re a streak thriver!",
|
||||||
"You’re the poet who found the rhyme.",
|
"You’re a streak overcomer!",
|
||||||
"You’re the knight who saved the day.",
|
"You’re a streak achiever!"
|
||||||
"You’re the bard who inspired the crowd.",
|
|
||||||
"You’re the scientist who made the breakthrough.",
|
|
||||||
"You’re the explorer who found the map.",
|
|
||||||
"You’re the inventor of your own future.",
|
|
||||||
"You’re the captain of your own starship.",
|
|
||||||
"You’re the one who knocks (success).",
|
|
||||||
"You’re the last Jedi in the room.",
|
|
||||||
"You’re the answer to the ultimate question of life, the universe, and everything.",
|
|
||||||
"You’re the one who remembered the towel. —Hitchhiker’s Guide",
|
|
||||||
"You’re the spark that started the fire.",
|
|
||||||
"You’re the wind beneath the wings.",
|
|
||||||
"You’re the punchline to the cosmic joke.",
|
|
||||||
"You’re the plot device that saves the day.",
|
|
||||||
"You’re the deus ex machina of your own story.",
|
|
||||||
"You’re the twist ending everyone talks about.",
|
|
||||||
"You’re the secret ingredient in the recipe for success.",
|
|
||||||
"You’re the magic feather that lets Dumbo fly.",
|
|
||||||
"You’re the golden ticket in the chocolate bar.",
|
|
||||||
"You’re the red pill in the Matrix.",
|
|
||||||
"You’re the portal to the next adventure.",
|
|
||||||
"You’re the key to the locked door.",
|
|
||||||
"You’re the map to the hidden treasure.",
|
|
||||||
"You’re the light saber in the darkness.",
|
|
||||||
"You’re the spell that works every time.",
|
|
||||||
"You’re the last page in the book—and it’s a happy ending!",
|
|
||||||
"You’re the chosen one, Neo!",
|
|
||||||
"Live long and prosper! 🖖",
|
|
||||||
"To infinity and beyond!",
|
|
||||||
"You’re a wizard, Harry!",
|
|
||||||
"Winter is NOT coming—you’re winning!",
|
|
||||||
"You’ve got the power of Grayskull!",
|
|
||||||
"You’re the hero Gotham deserves!",
|
|
||||||
"Victory tastes sweeter than lembas bread.",
|
|
||||||
"The Sorting Hat would put you in Gryffindor today.",
|
|
||||||
"The cake is not a lie—success is real!",
|
|
||||||
"The odds are ever in your favor.",
|
|
||||||
"Achievement unlocked!",
|
|
||||||
"The Force is strong with this one.",
|
|
||||||
"Elementary, my dear Watson!",
|
|
||||||
"The journey is the reward.",
|
|
||||||
"Allons-y!",
|
|
||||||
"The adventure begins anew.",
|
|
||||||
"The stars look very different today.",
|
|
||||||
"The game is afoot!",
|
|
||||||
"The world is your oyster.",
|
|
||||||
"The pen is mightier—and you’re writing history.",
|
|
||||||
"The quest was perilous, but you prevailed.",
|
|
||||||
"The answer is 42.",
|
|
||||||
"The road goes ever on and on.",
|
|
||||||
"The magic is in the doing.",
|
|
||||||
"The universe just gave you a thumbs up.",
|
|
||||||
"The next chapter is looking epic.",
|
|
||||||
"The finish line is just the beginning.",
|
|
||||||
"The secret passage opened!",
|
|
||||||
"The plot thickens—in your favor.",
|
|
||||||
"The prophecy has been fulfilled.",
|
|
||||||
"The spell worked perfectly.",
|
|
||||||
"The treasure chest is open!",
|
|
||||||
"The portal to greatness is unlocked.",
|
|
||||||
"The time machine landed on success.",
|
|
||||||
"The dragon has been tamed.",
|
|
||||||
"The labyrinth has been solved.",
|
|
||||||
"The riddle is cracked.",
|
|
||||||
"The beacon is lit—Gondor calls for aid!",
|
|
||||||
"The ring is destroyed—peace returns to Middle-earth.",
|
|
||||||
"The Millennium Falcon made the jump to lightspeed.",
|
|
||||||
"The Bat-Signal is shining bright.",
|
|
||||||
"The TARDIS is ready for the next adventure.",
|
|
||||||
"The wand chose the wizard.",
|
|
||||||
"The shield held strong.",
|
|
||||||
"The sword gleams with victory.",
|
|
||||||
"The map led to treasure.",
|
|
||||||
"The spellbook is open to the right page.",
|
|
||||||
"The dice rolled in your favor.",
|
|
||||||
"The stars aligned for you.",
|
|
||||||
"The quest log is complete.",
|
|
||||||
"The story ends happily ever after.",
|
|
||||||
"The legend grows.",
|
|
||||||
"The adventure continues.",
|
|
||||||
"The world just got a little brighter.",
|
|
||||||
"The muse is smiling.",
|
|
||||||
"The universe applauds your effort.",
|
|
||||||
"The crowd goes wild!",
|
|
||||||
"The confetti is falling!",
|
|
||||||
"The fireworks are for you!",
|
|
||||||
"The applause is thunderous!",
|
|
||||||
"The curtain rises on your next act.",
|
|
||||||
"The credits roll—and you’re the star!",
|
|
||||||
"Foundation built, success inevitable. —Isaac Asimov",
|
|
||||||
"The robots would approve your logic. —Isaac Asimov",
|
|
||||||
"Hari Seldon predicted this win! —Isaac Asimov",
|
|
||||||
"You navigated the Sprawl like Case. —William Gibson",
|
|
||||||
"The future is now—cyberspace conquered. —William Gibson",
|
|
||||||
"You’re jacked in and winning. —William Gibson",
|
|
||||||
"The spice must flow—and so does your progress. —Frank Herbert",
|
|
||||||
"Fear is the mind-killer, but you crushed it. —Frank Herbert",
|
|
||||||
"You walked without rhythm and avoided the sandworm. —Frank Herbert",
|
|
||||||
"You claimed the Iron Throne of achievement. —George R.R. Martin",
|
|
||||||
"A mind needs books as a sword needs a whetstone. —George R.R. Martin",
|
|
||||||
"You survived the game of habits—winter is not coming! —George R.R. Martin",
|
|
||||||
"Chaos isn’t a pit, it’s a ladder—and you climbed it. —George R.R. Martin",
|
|
||||||
"Progress is coming. —George R.R. Martin",
|
|
||||||
"You bent the arc of history—psychohistory style. —Isaac Asimov",
|
|
||||||
"You hacked the matrix and rewrote the code. —William Gibson",
|
|
||||||
"You rode the worm to victory. —Frank Herbert",
|
|
||||||
"You played the game of thrones and won. —George R.R. Martin"
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://myhabitgrid.com/</loc>
|
<loc>https://yourdomain.com/</loc>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://myhabitgrid.com/add</loc>
|
<loc>https://yourdomain.com/add</loc>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://myhabitgrid.com/settings</loc>
|
<loc>https://yourdomain.com/settings</loc>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://myhabitgrid.com/login-providers</loc>
|
<loc>https://yourdomain.com/login-providers</loc>
|
||||||
</url>
|
</url>
|
||||||
|
<!-- For dynamic routes like /habit/:id and /edit/:id, add actual URLs if you have them -->
|
||||||
</urlset>
|
</urlset>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter, HashRouter, Routes, Route } from 'react-router-dom';
|
||||||
|
import { Capacitor } from '@capacitor/core';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import HabitDetailPage from './pages/HabitDetailPage';
|
import HabitDetailPage from './pages/HabitDetailPage';
|
||||||
@@ -9,8 +10,10 @@ import LoginProvidersPage from './pages/LoginProvidersPage';
|
|||||||
import { Toaster } from './components/ui/toaster';
|
import { Toaster } from './components/ui/toaster';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const isNative = Capacitor?.isNativePlatform?.() ?? false;
|
||||||
|
const RouterComponent = isNative ? HashRouter : BrowserRouter;
|
||||||
return (
|
return (
|
||||||
<Router>
|
<RouterComponent>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>HabitGrid - Commit to yourself, one square at a time</title>
|
<title>HabitGrid - Commit to yourself, one square at a time</title>
|
||||||
<meta name="description" content="Track your habits with a beautiful GitHub-style contribution grid. Build streaks, visualize progress, and commit to yourself daily." />
|
<meta name="description" content="Track your habits with a beautiful GitHub-style contribution grid. Build streaks, visualize progress, and commit to yourself daily." />
|
||||||
@@ -26,7 +29,7 @@ function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</RouterComponent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useMemo, useEffect } from 'react';
|
import React, { useMemo, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { getColorIntensity, isToday, formatDate, getWeekdayLabel, getFrozenDays, calculateStreaks } from '../lib/utils-habit';
|
import { getColorIntensity, isToday, formatDate, getWeekdayLabel, getFrozenDays } from '../lib/utils-habit';
|
||||||
import { toggleCompletion, getAuthUser } from '../lib/datastore';
|
import { toggleCompletion, getAuthUser } from '../lib/datastore';
|
||||||
|
|
||||||
const HabitGrid = ({ habit, onUpdate, fullView = false }) => {
|
const HabitGrid = ({ habit, onUpdate, fullView = false }) => {
|
||||||
@@ -50,11 +50,6 @@ const HabitGrid = ({ habit, onUpdate, fullView = false }) => {
|
|||||||
const cidx = completions.indexOf(dateStr);
|
const cidx = completions.indexOf(dateStr);
|
||||||
if (cidx > -1) completions.splice(cidx, 1); else completions.push(dateStr);
|
if (cidx > -1) completions.splice(cidx, 1); else completions.push(dateStr);
|
||||||
habits[idx].completions = completions;
|
habits[idx].completions = completions;
|
||||||
// Recalculate streaks so counters reflect immediately
|
|
||||||
const { currentStreak, longestStreak } = calculateStreaks(completions);
|
|
||||||
const prevRecord = habits[idx].longestStreak || 0;
|
|
||||||
habits[idx].currentStreak = currentStreak;
|
|
||||||
habits[idx].longestStreak = Math.max(longestStreak, prevRecord);
|
|
||||||
localStorage.setItem('habitgrid_data', JSON.stringify(habits));
|
localStorage.setItem('habitgrid_data', JSON.stringify(habits));
|
||||||
}
|
}
|
||||||
onUpdate();
|
onUpdate();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function getFreezeIcon() {
|
|||||||
return icon || '❄️';
|
return icon || '❄️';
|
||||||
}
|
}
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { getColorIntensity, isToday, formatDate, calculateStreaks } from '../lib/utils-habit';
|
import { getColorIntensity, isToday, formatDate } from '../lib/utils-habit';
|
||||||
import { getFrozenDays } from '../lib/utils-habit';
|
import { getFrozenDays } from '../lib/utils-habit';
|
||||||
import { toggleCompletion, getAuthUser } from '../lib/datastore';
|
import { toggleCompletion, getAuthUser } from '../lib/datastore';
|
||||||
import { toast } from './ui/use-toast';
|
import { toast } from './ui/use-toast';
|
||||||
@@ -79,11 +79,6 @@ const MiniGrid = ({ habit, onUpdate }) => {
|
|||||||
const cidx = completions.indexOf(dateStr);
|
const cidx = completions.indexOf(dateStr);
|
||||||
if (cidx > -1) completions.splice(cidx, 1); else completions.push(dateStr);
|
if (cidx > -1) completions.splice(cidx, 1); else completions.push(dateStr);
|
||||||
habits[idx].completions = completions;
|
habits[idx].completions = completions;
|
||||||
// Recalculate streaks locally so counters update immediately
|
|
||||||
const { currentStreak, longestStreak } = calculateStreaks(completions);
|
|
||||||
const prevRecord = habits[idx].longestStreak || 0;
|
|
||||||
habits[idx].currentStreak = currentStreak;
|
|
||||||
habits[idx].longestStreak = Math.max(longestStreak, prevRecord);
|
|
||||||
localStorage.setItem('habitgrid_data', JSON.stringify(habits));
|
localStorage.setItem('habitgrid_data', JSON.stringify(habits));
|
||||||
}
|
}
|
||||||
onUpdate();
|
onUpdate();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ function ensureUUIDs(habits) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
import { supabase, isSupabaseConfigured } from './supabase';
|
import { supabase, isSupabaseConfigured } from './supabase';
|
||||||
import { calculateStreaks } from './utils-habit';
|
|
||||||
import * as local from './storage';
|
import * as local from './storage';
|
||||||
|
|
||||||
const SYNC_FLAG = 'habitgrid_remote_synced_at';
|
const SYNC_FLAG = 'habitgrid_remote_synced_at';
|
||||||
@@ -31,23 +30,7 @@ const SYNC_FLAG = 'habitgrid_remote_synced_at';
|
|||||||
export const getAuthUser = async () => {
|
export const getAuthUser = async () => {
|
||||||
if (!isSupabaseConfigured()) return null;
|
if (!isSupabaseConfigured()) return null;
|
||||||
const { data } = await supabase.auth.getUser();
|
const { data } = await supabase.auth.getUser();
|
||||||
const user = data?.user ?? null;
|
return data?.user ?? null;
|
||||||
// Mark that the user has logged in at least once so we can prompt later if they're logged out
|
|
||||||
try {
|
|
||||||
if (user) localStorage.setItem('habitgrid_ever_logged_in', '1');
|
|
||||||
} catch (e) {
|
|
||||||
// ignore localStorage errors in restrictive environments
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper to check whether the user has ever logged in from this browser
|
|
||||||
export const hasEverLoggedIn = () => {
|
|
||||||
try {
|
|
||||||
return localStorage.getItem('habitgrid_ever_logged_in') === '1';
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isLoggedIn = async () => Boolean(await getAuthUser());
|
export const isLoggedIn = async () => Boolean(await getAuthUser());
|
||||||
@@ -173,10 +156,7 @@ export async function toggleCompletion(habitId, dateStr) {
|
|||||||
const completions = Array.isArray(target.completions) ? [...target.completions] : [];
|
const completions = Array.isArray(target.completions) ? [...target.completions] : [];
|
||||||
const idx = completions.indexOf(dateStr);
|
const idx = completions.indexOf(dateStr);
|
||||||
if (idx > -1) completions.splice(idx, 1); else completions.push(dateStr);
|
if (idx > -1) completions.splice(idx, 1); else completions.push(dateStr);
|
||||||
// Calculate streaks and preserve personal record (do not decrease longest)
|
return updateHabit(habitId, { completions });
|
||||||
const { currentStreak, longestStreak } = calculateStreaks(completions);
|
|
||||||
const nextLongest = Math.max(longestStreak, target.longestStreak || 0);
|
|
||||||
return updateHabit(habitId, { completions, currentStreak, longestStreak: nextLongest });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -180,7 +180,6 @@ async function fetchGitHubEvents({ baseUrl = 'https://api.github.com', username,
|
|||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
//This bullshit is still not working, but at least it is not crashing everything
|
|
||||||
async function fetchGiteaLike({ baseUrl, username, token }, days = 365) {
|
async function fetchGiteaLike({ baseUrl, username, token }, days = 365) {
|
||||||
let authMode = token ? 'token' : null; // 'token' | 'bearer' | null
|
let authMode = token ? 'token' : null; // 'token' | 'bearer' | null
|
||||||
const counts = {};
|
const counts = {};
|
||||||
@@ -229,7 +228,6 @@ async function fetchGiteaLike({ baseUrl, username, token }, days = 365) {
|
|||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
//gitlab fetch should work now
|
|
||||||
async function fetchGitLabEvents({ baseUrl = 'https://gitlab.com', token }, days = 365) {
|
async function fetchGitLabEvents({ baseUrl = 'https://gitlab.com', token }, days = 365) {
|
||||||
const headers = { 'Accept': 'application/json', 'PRIVATE-TOKEN': token };
|
const headers = { 'Accept': 'application/json', 'PRIVATE-TOKEN': token };
|
||||||
const counts = {};
|
const counts = {};
|
||||||
|
|||||||
@@ -126,7 +126,65 @@ export const toggleCompletion = (habitId, dateStr) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
import { calculateStreaks } from './utils-habit.js';
|
import { getFrozenDays } from './utils-habit.js';
|
||||||
|
const calculateStreaks = (completions) => {
|
||||||
|
if (completions.length === 0) {
|
||||||
|
return { currentStreak: 0, longestStreak: 0 };
|
||||||
|
}
|
||||||
|
// Only use frozen days for streak calculation
|
||||||
|
const frozenDays = getFrozenDays(completions);
|
||||||
|
const allValid = Array.from(new Set([...completions, ...frozenDays]));
|
||||||
|
const sortedDates = allValid
|
||||||
|
.map(d => new Date(d))
|
||||||
|
.sort((a, b) => b - a);
|
||||||
|
|
||||||
|
let currentStreak = 0;
|
||||||
|
let longestStreak = 0;
|
||||||
|
let tempStreak = 1;
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
const yesterday = new Date(today);
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
const mostRecent = sortedDates[0];
|
||||||
|
mostRecent.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (mostRecent.getTime() === today.getTime() || mostRecent.getTime() === yesterday.getTime()) {
|
||||||
|
currentStreak = 1;
|
||||||
|
for (let i = 1; i < sortedDates.length; i++) {
|
||||||
|
const current = new Date(sortedDates[i]);
|
||||||
|
current.setHours(0, 0, 0, 0);
|
||||||
|
const previous = new Date(sortedDates[i - 1]);
|
||||||
|
previous.setHours(0, 0, 0, 0);
|
||||||
|
const diffDays = Math.floor((previous - current) / (1000 * 60 * 60 * 24));
|
||||||
|
if (diffDays === 1) {
|
||||||
|
currentStreak++;
|
||||||
|
tempStreak++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tempStreak = 1;
|
||||||
|
for (let i = 1; i < sortedDates.length; i++) {
|
||||||
|
const current = new Date(sortedDates[i]);
|
||||||
|
current.setHours(0, 0, 0, 0);
|
||||||
|
const previous = new Date(sortedDates[i - 1]);
|
||||||
|
previous.setHours(0, 0, 0, 0);
|
||||||
|
const diffDays = Math.floor((previous - current) / (1000 * 60 * 60 * 24));
|
||||||
|
if (diffDays === 1) {
|
||||||
|
tempStreak++;
|
||||||
|
longestStreak = Math.max(longestStreak, tempStreak);
|
||||||
|
} else {
|
||||||
|
tempStreak = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
longestStreak = Math.max(longestStreak, currentStreak, 1);
|
||||||
|
return { currentStreak, longestStreak };
|
||||||
|
}
|
||||||
|
|
||||||
export const exportData = () => {
|
export const exportData = () => {
|
||||||
const habits = getHabits();
|
const habits = getHabits();
|
||||||
|
|||||||
@@ -40,71 +40,6 @@ export const getWeekdayLabel = (dayIndex) => {
|
|||||||
const labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
const labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
return labels[dayIndex];
|
return labels[dayIndex];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate current and longest streaks from a list of completion date strings (YYYY-MM-DD)
|
|
||||||
// Rules:
|
|
||||||
// - Streaks count consecutive days
|
|
||||||
// - Today or yesterday must be present to have a non-zero current streak
|
|
||||||
// - We also include "frozen" days (one missed day per month sandwiched by completions)
|
|
||||||
// - Longest streak is at least 1 if there is at least one completion
|
|
||||||
export function calculateStreaks(completions) {
|
|
||||||
if (!Array.isArray(completions) || completions.length === 0) {
|
|
||||||
return { currentStreak: 0, longestStreak: 0 };
|
|
||||||
}
|
|
||||||
// Only use frozen days for streak calculation
|
|
||||||
const frozenDays = getFrozenDays(completions);
|
|
||||||
const allValid = Array.from(new Set([...(completions || []), ...frozenDays]));
|
|
||||||
const sortedDates = allValid
|
|
||||||
.map(d => new Date(d))
|
|
||||||
.sort((a, b) => b - a);
|
|
||||||
|
|
||||||
let currentStreak = 0;
|
|
||||||
let longestStreak = 0;
|
|
||||||
let tempStreak = 1;
|
|
||||||
|
|
||||||
const today = new Date();
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
const yesterday = new Date(today);
|
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
|
||||||
|
|
||||||
const mostRecent = sortedDates[0];
|
|
||||||
mostRecent.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
if (mostRecent.getTime() === today.getTime() || mostRecent.getTime() === yesterday.getTime()) {
|
|
||||||
currentStreak = 1;
|
|
||||||
for (let i = 1; i < sortedDates.length; i++) {
|
|
||||||
const current = new Date(sortedDates[i]);
|
|
||||||
current.setHours(0, 0, 0, 0);
|
|
||||||
const previous = new Date(sortedDates[i - 1]);
|
|
||||||
previous.setHours(0, 0, 0, 0);
|
|
||||||
const diffDays = Math.floor((previous - current) / (1000 * 60 * 60 * 24));
|
|
||||||
if (diffDays === 1) {
|
|
||||||
currentStreak++;
|
|
||||||
tempStreak++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tempStreak = 1;
|
|
||||||
for (let i = 1; i < sortedDates.length; i++) {
|
|
||||||
const current = new Date(sortedDates[i]);
|
|
||||||
current.setHours(0, 0, 0, 0);
|
|
||||||
const previous = new Date(sortedDates[i - 1]);
|
|
||||||
previous.setHours(0, 0, 0, 0);
|
|
||||||
const diffDays = Math.floor((previous - current) / (1000 * 60 * 60 * 24));
|
|
||||||
if (diffDays === 1) {
|
|
||||||
tempStreak++;
|
|
||||||
longestStreak = Math.max(longestStreak, tempStreak);
|
|
||||||
} else {
|
|
||||||
tempStreak = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
longestStreak = Math.max(longestStreak, currentStreak, 1);
|
|
||||||
return { currentStreak, longestStreak };
|
|
||||||
}
|
|
||||||
// Returns array of frozen days (date strings) for a given completions array
|
// Returns array of frozen days (date strings) for a given completions array
|
||||||
export function getFrozenDays(completions) {
|
export function getFrozenDays(completions) {
|
||||||
// Map: month string -> frozen day string
|
// Map: month string -> frozen day string
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import HabitCard from '../components/HabitCard';
|
|||||||
import AnimatedCounter from '../components/AnimatedCounter';
|
import AnimatedCounter from '../components/AnimatedCounter';
|
||||||
import GitActivityGrid from '../components/GitActivityGrid';
|
import GitActivityGrid from '../components/GitActivityGrid';
|
||||||
import { getGitEnabled } from '../lib/git';
|
import { getGitEnabled } from '../lib/git';
|
||||||
import { calculateStreaks } from '../lib/utils-habit';
|
import { getHabits, updateHabit, syncLocalToRemoteIfNeeded, syncRemoteToLocal, getAuthUser } from '../lib/datastore';
|
||||||
import { getHabits, updateHabit, syncLocalToRemoteIfNeeded, syncRemoteToLocal, getAuthUser, hasEverLoggedIn } from '../lib/datastore';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
@@ -20,7 +19,6 @@ const HomePage = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [loggedIn, setLoggedIn] = useState(false);
|
const [loggedIn, setLoggedIn] = useState(false);
|
||||||
const [collapsedGroups, setCollapsedGroups] = useState({});
|
const [collapsedGroups, setCollapsedGroups] = useState({});
|
||||||
const [everLoggedIn, setEverLoggedIn] = useState(false);
|
|
||||||
const [gitEnabled, setGitEnabled] = useState(getGitEnabled());
|
const [gitEnabled, setGitEnabled] = useState(getGitEnabled());
|
||||||
const [darkMode, setDarkMode] = useState(() => {
|
const [darkMode, setDarkMode] = useState(() => {
|
||||||
return localStorage.getItem('theme') === 'dark';
|
return localStorage.getItem('theme') === 'dark';
|
||||||
@@ -32,12 +30,6 @@ const HomePage = () => {
|
|||||||
// On login, pull remote habits into localStorage
|
// On login, pull remote habits into localStorage
|
||||||
const user = await getAuthUser();
|
const user = await getAuthUser();
|
||||||
setLoggedIn(!!user);
|
setLoggedIn(!!user);
|
||||||
// Mark whether this browser has seen a login before
|
|
||||||
try {
|
|
||||||
setEverLoggedIn(hasEverLoggedIn());
|
|
||||||
} catch (e) {
|
|
||||||
setEverLoggedIn(false);
|
|
||||||
}
|
|
||||||
if (user) {
|
if (user) {
|
||||||
await syncRemoteToLocal();
|
await syncRemoteToLocal();
|
||||||
}
|
}
|
||||||
@@ -71,27 +63,13 @@ const HomePage = () => {
|
|||||||
const loadHabits = async () => {
|
const loadHabits = async () => {
|
||||||
// Always read from local for instant UI
|
// Always read from local for instant UI
|
||||||
const loadedHabits = JSON.parse(localStorage.getItem('habitgrid_data') || '[]');
|
const loadedHabits = JSON.parse(localStorage.getItem('habitgrid_data') || '[]');
|
||||||
// One-time consistency pass: recompute streaks from completions if missing or outdated
|
loadedHabits.sort((a, b) => {
|
||||||
let changed = false;
|
|
||||||
const updated = loadedHabits.map(h => {
|
|
||||||
const { currentStreak, longestStreak } = calculateStreaks(h.completions || []);
|
|
||||||
const nextLongest = Math.max(longestStreak, h.longestStreak || 0);
|
|
||||||
if ((h.currentStreak ?? 0) !== currentStreak || (h.longestStreak ?? 0) !== nextLongest) {
|
|
||||||
changed = true;
|
|
||||||
return { ...h, currentStreak, longestStreak: nextLongest };
|
|
||||||
}
|
|
||||||
return h;
|
|
||||||
});
|
|
||||||
if (changed) {
|
|
||||||
localStorage.setItem('habitgrid_data', JSON.stringify(updated));
|
|
||||||
}
|
|
||||||
updated.sort((a, b) => {
|
|
||||||
if (a.sortOrder !== undefined && b.sortOrder !== undefined) return a.sortOrder - b.sortOrder;
|
if (a.sortOrder !== undefined && b.sortOrder !== undefined) return a.sortOrder - b.sortOrder;
|
||||||
if (a.sortOrder !== undefined) return -1;
|
if (a.sortOrder !== undefined) return -1;
|
||||||
if (b.sortOrder !== undefined) return 1;
|
if (b.sortOrder !== undefined) return 1;
|
||||||
return new Date(a.createdAt || 0) - new Date(b.createdAt || 0);
|
return new Date(a.createdAt || 0) - new Date(b.createdAt || 0);
|
||||||
});
|
});
|
||||||
setHabits(updated);
|
setHabits(loadedHabits);
|
||||||
// Initialize collapsed state for new categories
|
// Initialize collapsed state for new categories
|
||||||
const categories = Array.from(new Set(loadedHabits.map(h => h.category || 'Uncategorized')));
|
const categories = Array.from(new Set(loadedHabits.map(h => h.category || 'Uncategorized')));
|
||||||
setCollapsedGroups(prev => {
|
setCollapsedGroups(prev => {
|
||||||
@@ -108,7 +86,7 @@ const HomePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginSync = () => {
|
const handleLoginSync = () => {
|
||||||
navigate('/login-providers');
|
navigate('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleManualSync = async () => {
|
const handleManualSync = async () => {
|
||||||
@@ -155,21 +133,7 @@ const HomePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Prompt previously-signed-in users to re-authenticate if currently logged out */}
|
|
||||||
{!loggedIn && everLoggedIn && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: -6 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
className="max-w-6xl mx-auto mb-6 p-4 bg-yellow-50 dark:bg-yellow-900/40 rounded-lg border border-yellow-100 dark:border-yellow-800 flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<div className="text-sm text-yellow-800 dark:text-yellow-200">Looks like you were previously signed in. Sign in again to keep your habits synced across devices.</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button size="sm" onClick={() => navigate('/login-providers')}>Sign in</Button>
|
|
||||||
<Button size="sm" variant="ghost" onClick={() => { localStorage.removeItem('habitgrid_ever_logged_in'); setEverLoggedIn(false); }}>Dismiss</Button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Stats Overview */}
|
{/* Stats Overview */}
|
||||||
{habits.length > 0 && (
|
{habits.length > 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -204,6 +168,8 @@ const HomePage = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Habits List */}
|
{/* Habits List */}
|
||||||
{/* Grouped Habits by Category, collapsible, and uncategorized habits outside */}
|
{/* Grouped Habits by Category, collapsible, and uncategorized habits outside */}
|
||||||
<DragDropContext
|
<DragDropContext
|
||||||
@@ -456,6 +422,8 @@ const HomePage = () => {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Add Button */}
|
{/* Add Button */}
|
||||||
{habits.length > 0 && (
|
{habits.length > 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||