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