Compare commits
9 Commits
android-ve
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 839252e3ef | |||
| a902062726 | |||
| e330346d86 | |||
|
|
b91f94a388 | ||
|
|
99a61b5112 | ||
|
|
87cbfa54f7 | ||
| 2c7d136a33 | |||
| 29bb669f60 | |||
| 85c8aea7d3 |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
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
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
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
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# 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}}"
|
||||||
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# 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
@@ -1,101 +0,0 @@
|
|||||||
# 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
@@ -1,2 +0,0 @@
|
|||||||
/build/*
|
|
||||||
!/build/.npmkeep
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// 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
@@ -1,21 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.habitgrid.app;
|
|
||||||
|
|
||||||
import com.getcapacitor.BridgeActivity;
|
|
||||||
|
|
||||||
public class MainActivity extends BridgeActivity {}
|
|
||||||
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,34 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,12 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#FFFFFF</color>
|
|
||||||
</resources>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// 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')
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# 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
@@ -1,7 +0,0 @@
|
|||||||
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
@@ -1,252 +0,0 @@
|
|||||||
#!/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
@@ -1,94 +0,0 @@
|
|||||||
@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
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
include ':app'
|
|
||||||
include ':capacitor-cordova-android-plugins'
|
|
||||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
|
||||||
|
|
||||||
apply from: 'capacitor.settings.gradle'
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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'
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { CapacitorConfig } from '@capacitor/cli';
|
|
||||||
|
|
||||||
const config: CapacitorConfig = {
|
|
||||||
appId: 'com.habitgrid.app',
|
|
||||||
appName: 'HabitGrid',
|
|
||||||
webDir: 'dist',
|
|
||||||
server: {
|
|
||||||
androidScheme: 'https',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
@@ -34,13 +34,14 @@
|
|||||||
<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,11 +6,7 @@
|
|||||||
"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",
|
||||||
@@ -43,9 +39,6 @@
|
|||||||
"@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,102 +1,209 @@
|
|||||||
[
|
[
|
||||||
"Great job! Keep going!",
|
"You did it! 🎉",
|
||||||
"You're on fire! 🔥",
|
"High five! ✋",
|
||||||
"Consistency is key!",
|
"You’re awesome!",
|
||||||
"Amazing streak!",
|
"Keep rocking!",
|
||||||
"You crushed it today!",
|
"Level up! 🚀",
|
||||||
"Small steps, big results!",
|
"You’re a legend!",
|
||||||
"Habit hero!",
|
"That’s how it’s done!",
|
||||||
"Progress, not perfection!",
|
"You’re crushing it!",
|
||||||
"Every dot counts!",
|
"Go you!",
|
||||||
"Keep up the momentum!",
|
|
||||||
"You’re building something awesome!",
|
|
||||||
"One step closer to your goal!",
|
|
||||||
"You’re unstoppable!",
|
"You’re unstoppable!",
|
||||||
"Keep the streak alive!",
|
"Boom! Achievement unlocked!",
|
||||||
"You’re making it happen!",
|
"You’re a superstar!",
|
||||||
"Your effort is inspiring!",
|
"You make it look easy!",
|
||||||
"You’re a streak superstar!",
|
"You’re a force of nature!",
|
||||||
"Every day matters!",
|
"You’re a wizard! 🧙♂️",
|
||||||
"You’re a habit legend!",
|
"You’re a ninja! 🥷",
|
||||||
"You’re doing fantastic!",
|
"You’re a rockstar! 🎸",
|
||||||
"Keep shining!",
|
"You’re a champion! 🏆",
|
||||||
"You’re a role model!",
|
"You’re a hero! 🦸♂️",
|
||||||
"You’re a champion!",
|
"You’re a genius!",
|
||||||
"You’re making progress!",
|
"You’re a boss!",
|
||||||
|
"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 streak master!",
|
"You’re a motivator!",
|
||||||
"You’re a habit machine!",
|
"You’re a creator!",
|
||||||
"You’re a streak builder!",
|
"You’re a dreamer!",
|
||||||
"You’re a streak star!",
|
"You’re a doer!",
|
||||||
"You’re a streak hero!",
|
"You’re a finisher!",
|
||||||
"You’re a streak ninja!",
|
"You’re a Firestarter!",
|
||||||
"You’re a streak wizard!",
|
"You’re a closer!",
|
||||||
"You’re a streak warrior!",
|
"You’re a believer!",
|
||||||
"You’re a streak explorer!",
|
"You’re a planner!",
|
||||||
"You’re a streak adventurer!",
|
"You’re an organizer!",
|
||||||
"You’re a streak conqueror!",
|
"You’re a strategist!",
|
||||||
"You’re a streak champion!",
|
"You’re a visionary!",
|
||||||
"You’re a streak genius!",
|
"You’re an optimist!",
|
||||||
"You’re a streak guru!",
|
"You’re an enthusiast!",
|
||||||
"You’re a streak expert!",
|
"You’re a player!",
|
||||||
"You’re a streak pro!",
|
"You’re a contender!",
|
||||||
"You’re a streak veteran!",
|
"You’re a competitor!",
|
||||||
"You’re a streak rookie!",
|
"You’re a challenger!",
|
||||||
"You’re a streak all-star!",
|
"You’re a victor!",
|
||||||
"You’re a streak MVP!",
|
"You’re a survivor!",
|
||||||
"You’re a streak superstar!",
|
"You’re a thriver!",
|
||||||
"You’re a streak rockstar!",
|
"You’re an overcomer!",
|
||||||
"You’re a streak dynamo!",
|
"You’re a high achiever!",
|
||||||
"You’re a streak powerhouse!",
|
"Not all those who wander are lost. —J.R.R. Tolkien",
|
||||||
"You’re a streak inspiration!",
|
"So it goes. —Kurt Vonnegut",
|
||||||
"You’re a streak motivator!",
|
"The only way out is through. —Robert Frost",
|
||||||
"You’re a streak leader!",
|
"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 innovator!",
|
"Courage is found in unlikely places. —J.R.R. Tolkien",
|
||||||
"You’re a streak creator!",
|
"Do or do not. There is no try. —Yoda",
|
||||||
"You’re a streak builder!",
|
"It’s dangerous to go alone! Take this. —The Legend of Zelda",
|
||||||
"You’re a streak achiever!",
|
"The game is afoot! —Sherlock Holmes",
|
||||||
"You’re a streak doer!",
|
"To the stars who listen and the dreams that are answered. —Sarah J. Maas",
|
||||||
"You’re a streak finisher!",
|
"All we have to decide is what to do with the time that is given us. —Gandalf",
|
||||||
"You’re a streak starter!",
|
"Even the smallest person can change the course of the future. —Galadriel",
|
||||||
"You’re a streak closer!",
|
"Winter passed and the world grew up. —C.S. Lewis",
|
||||||
"You’re a streak winner!",
|
"The secret of getting ahead is getting started. —Mark Twain",
|
||||||
"You’re a streak believer!",
|
"Not all heroes wear capes.",
|
||||||
"You’re a streak dreamer!",
|
"Achievement unlocked!",
|
||||||
"You’re a streak thinker!",
|
"You rolled a natural 20!",
|
||||||
"You’re a streak planner!",
|
"The odds are ever in your favor.",
|
||||||
"You’re a streak organizer!",
|
"You found the last Horcrux!",
|
||||||
"You’re a streak strategist!",
|
"You solved the puzzle before Watson!",
|
||||||
"You’re a streak tactician!",
|
"You’re the plot twist everyone needed.",
|
||||||
"You’re a streak visionary!",
|
"You’re the missing page in the story.",
|
||||||
"You’re a streak optimist!",
|
"You’re the last piece of the puzzle.",
|
||||||
"You’re a streak realist!",
|
"You’re the answer to the riddle.",
|
||||||
"You’re a streak enthusiast!",
|
"You’re the light at the end of the tunnel.",
|
||||||
"You’re a streak supporter!",
|
"You’re the hero of your own epic.",
|
||||||
"You’re a streak encourager!",
|
"You’re the author of your next chapter.",
|
||||||
"You’re a streak helper!",
|
"You’re the main character energy!",
|
||||||
"You’re a streak friend!",
|
"You’re the chosen one (but in a good way).",
|
||||||
"You’re a streak teammate!",
|
"You’re the unexpected ending everyone loves.",
|
||||||
"You’re a streak partner!",
|
"You’re the plot armor in a tough scene.",
|
||||||
"You’re a streak ally!",
|
"You’re the magic in the mundane.",
|
||||||
"You’re a streak companion!",
|
"You’re the hope in the dystopia.",
|
||||||
"You’re a streak buddy!",
|
"You’re the clever twist in the mystery.",
|
||||||
"You’re a streak pal!",
|
"You’re the spark in the revolution.",
|
||||||
"You’re a streak mate!",
|
"You’re the prophecy fulfilled.",
|
||||||
"You’re a streak peer!",
|
"You’re the legend in the making.",
|
||||||
"You’re a streak colleague!",
|
"You’re the muse for tomorrow’s story.",
|
||||||
"You’re a streak associate!",
|
"You’re the ink in the pen of progress.",
|
||||||
"You’re a streak collaborator!",
|
"You’re the page-turner in a slow chapter.",
|
||||||
"You’re a streak contributor!",
|
"You’re the secret passage in the labyrinth.",
|
||||||
"You’re a streak participant!",
|
"You’re the dragon’s gold at the end of the quest.",
|
||||||
"You’re a streak member!",
|
"You’re the ring-bearer on the journey.",
|
||||||
"You’re a streak player!",
|
"You’re the phoenix rising from the ashes.",
|
||||||
"You’re a streak contender!",
|
"You’re the sword in the stone.",
|
||||||
"You’re a streak competitor!",
|
"You’re the magic bean that grew the beanstalk.",
|
||||||
"You’re a streak challenger!",
|
"You’re the time traveler who fixed the timeline.",
|
||||||
"You’re a streak rival!",
|
"You’re the detective who cracked the case.",
|
||||||
"You’re a streak victor!",
|
"You’re the rebel with a cause.",
|
||||||
"You’re a streak survivor!",
|
"You’re the wizard who remembered the spell.",
|
||||||
"You’re a streak thriver!",
|
"You’re the hobbit who left the Shire.",
|
||||||
"You’re a streak overcomer!",
|
"You’re the poet who found the rhyme.",
|
||||||
"You’re a streak achiever!"
|
"You’re the knight who saved the day.",
|
||||||
|
"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,16 +1,15 @@
|
|||||||
<?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://yourdomain.com/</loc>
|
<loc>https://myhabitgrid.com/</loc>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://yourdomain.com/add</loc>
|
<loc>https://myhabitgrid.com/add</loc>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://yourdomain.com/settings</loc>
|
<loc>https://myhabitgrid.com/settings</loc>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://yourdomain.com/login-providers</loc>
|
<loc>https://myhabitgrid.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,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter, HashRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, 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';
|
||||||
@@ -10,10 +9,8 @@ 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 (
|
||||||
<RouterComponent>
|
<Router>
|
||||||
<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." />
|
||||||
@@ -29,7 +26,7 @@ function App() {
|
|||||||
</Routes>
|
</Routes>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</div>
|
</div>
|
||||||
</RouterComponent>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 } from '../lib/utils-habit';
|
import { getColorIntensity, isToday, formatDate, getWeekdayLabel, getFrozenDays, calculateStreaks } 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,6 +50,11 @@ 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 } from '../lib/utils-habit';
|
import { getColorIntensity, isToday, formatDate, calculateStreaks } 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,6 +79,11 @@ 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,6 +23,7 @@ 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';
|
||||||
@@ -30,7 +31,23 @@ 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();
|
||||||
return data?.user ?? null;
|
const user = 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());
|
||||||
@@ -156,7 +173,10 @@ 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);
|
||||||
return updateHabit(habitId, { completions });
|
// Calculate streaks and preserve personal record (do not decrease longest)
|
||||||
|
const { currentStreak, longestStreak } = calculateStreaks(completions);
|
||||||
|
const nextLongest = Math.max(longestStreak, target.longestStreak || 0);
|
||||||
|
return updateHabit(habitId, { completions, currentStreak, longestStreak: nextLongest });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ 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 = {};
|
||||||
@@ -228,6 +229,7 @@ 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,65 +126,7 @@ export const toggleCompletion = (habitId, dateStr) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
import { getFrozenDays } from './utils-habit.js';
|
import { calculateStreaks } 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,6 +40,71 @@ 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,7 +9,8 @@ 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 { getHabits, updateHabit, syncLocalToRemoteIfNeeded, syncRemoteToLocal, getAuthUser } from '../lib/datastore';
|
import { calculateStreaks } from '../lib/utils-habit';
|
||||||
|
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 = () => {
|
||||||
@@ -19,6 +20,7 @@ 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';
|
||||||
@@ -30,6 +32,12 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -63,13 +71,27 @@ 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') || '[]');
|
||||||
loadedHabits.sort((a, b) => {
|
// One-time consistency pass: recompute streaks from completions if missing or outdated
|
||||||
|
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(loadedHabits);
|
setHabits(updated);
|
||||||
// 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 => {
|
||||||
@@ -86,7 +108,7 @@ const HomePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginSync = () => {
|
const handleLoginSync = () => {
|
||||||
navigate('/login');
|
navigate('/login-providers');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleManualSync = async () => {
|
const handleManualSync = async () => {
|
||||||
@@ -133,6 +155,20 @@ 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 && (
|
||||||
@@ -168,8 +204,6 @@ 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
|
||||||
@@ -422,8 +456,6 @@ const HomePage = () => {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Add Button */}
|
{/* Add Button */}
|
||||||
{habits.length > 0 && (
|
{habits.length > 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||