diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index b390cb8e525354622a209de855729787e968fedb..0000000000000000000000000000000000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms - -github: ellenhp -liberapay: ellenhp diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml deleted file mode 100644 index a819e751fdeb4e7addc0180730b8ee339eddd54c..0000000000000000000000000000000000000000 --- a/.github/workflows/build_android.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: Android CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-latest - concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-android-build - cancel-in-progress: true - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - lfs: true - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: gradle - - - name: Set up Android SDK - uses: android-actions/setup-android@v3 - with: - api-level: 36 - - - name: Install NDK - run: sdkmanager "ndk;26.1.10909125" - - - name: Configure Android NDK environment - run: | - echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125" >> $GITHUB_ENV - echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/26.1.10909125" >> $GITHUB_ENV - echo "NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125" >> $GITHUB_ENV - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install Rust Android targets - run: | - rustup target add aarch64-linux-android - rustup target add x86_64-linux-android - - - name: Cache Rust dependencies - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - cardinal-geocoder/target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - - - name: Cache Gradle dependencies - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - cardinal-android/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Configure Gradle for CI - run: | - mkdir -p ~/.gradle - echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties - echo "org.gradle.parallel=true" >> ~/.gradle/gradle.properties - echo "org.gradle.configureondemand=true" >> ~/.gradle/gradle.properties - echo "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError" >> ~/.gradle/gradle.properties - - - name: Grant execute permission for gradlew - run: chmod +x cardinal-android/gradlew - - - name: Install cargo-ndk - run: cargo install cargo-ndk - - - name: Verify Android SDK and NDK setup - run: | - echo "=== Environment Variables ===" - echo "ANDROID_HOME: $ANDROID_HOME" - echo "ANDROID_NDK_HOME: $ANDROID_NDK_HOME" - echo "ANDROID_NDK_ROOT: $ANDROID_NDK_ROOT" - echo "NDK_HOME: $NDK_HOME" - echo "" - echo "=== Android SDK Structure ===" - ls -la $ANDROID_HOME || echo "ANDROID_HOME not found" - echo "" - echo "=== NDK Installation ===" - ls -la $ANDROID_NDK_HOME || echo "NDK not found at $ANDROID_NDK_HOME" - echo "" - echo "=== Rust Targets ===" - rustup target list --installed | grep android || echo "No Android targets found" - echo "" - echo "=== Cargo NDK Version ===" - cargo ndk --version || echo "cargo-ndk not found" - - - name: Install Protoc - uses: arduino/setup-protoc@v3 - - - name: Build with Gradle - working-directory: cardinal-android - run: | - touch local.properties - # Build debug APKs for each architecture to test the configuration - ./gradlew assembleArm64Debug assembleX86_64Debug --info --stacktrace --no-daemon - env: - ANDROID_HOME: ${{ env.ANDROID_HOME }} - ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_HOME }} - ANDROID_NDK_ROOT: ${{ env.ANDROID_NDK_ROOT }} - NDK_HOME: ${{ env.NDK_HOME }} - - - name: Upload build artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: android-build-artifacts - path: | - cardinal-android/app/build/outputs/ - retention-days: 7 - - - name: Upload build logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: build-logs - path: | - cardinal-android/app/build/reports/ - ~/.gradle/daemon/*/daemon-*.out.log - retention-days: 3 diff --git a/.github/workflows/detekt.yml b/.github/workflows/detekt.yml deleted file mode 100644 index 2658a6ca5b41ea7248b852cb1074fccd2c32363f..0000000000000000000000000000000000000000 --- a/.github/workflows/detekt.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Android CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - detekt: - runs-on: ubuntu-latest - concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-android-detekt - cancel-in-progress: true - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - lfs: true - - - name: Cache Gradle dependencies - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - cardinal-android/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Configure Gradle for CI - run: | - mkdir -p ~/.gradle - echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties - echo "org.gradle.parallel=true" >> ~/.gradle/gradle.properties - echo "org.gradle.configureondemand=true" >> ~/.gradle/gradle.properties - echo "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError" >> ~/.gradle/gradle.properties - - - name: Build with Gradle - working-directory: cardinal-android - run: | - touch local.properties - ./gradlew detekt - env: - ANDROID_HOME: ${{ env.ANDROID_HOME }} - ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_HOME }} - ANDROID_NDK_ROOT: ${{ env.ANDROID_NDK_ROOT }} - NDK_HOME: ${{ env.NDK_HOME }} - - - name: Upload build logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: build-logs - path: | - cardinal-android/app/build/reports/ - ~/.gradle/daemon/*/daemon-*.out.log - retention-days: 3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 502f53279e91509a29cbbaee1c87656912d21330..0000000000000000000000000000000000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,215 +0,0 @@ -name: Android Release - -on: - push: - tags: - - 'v*' - -jobs: - release: - runs-on: ubuntu-latest - concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-android-release - cancel-in-progress: true - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - lfs: true - - - name: Extract version from tag - id: version - run: | - TAG=${GITHUB_REF#refs/tags/} - VERSION=${TAG#v} - echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "version_code=$(date +%s)" >> $GITHUB_OUTPUT - echo "Extracted version: $VERSION from tag: $TAG" - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: gradle - - - name: Set up Android SDK - uses: android-actions/setup-android@v3 - with: - api-level: 36 - - - name: Install NDK - run: sdkmanager "ndk;26.1.10909125" - - - name: Configure Android NDK environment - run: | - echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125" >> $GITHUB_ENV - echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/26.1.10909125" >> $GITHUB_ENV - echo "NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125" >> $GITHUB_ENV - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install Rust Android targets - run: | - rustup target add aarch64-linux-android - rustup target add x86_64-linux-android - - - name: Cache Rust dependencies - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - cardinal-geocoder/target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - - - name: Cache Gradle dependencies - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - cardinal-android/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Configure Gradle for CI - run: | - mkdir -p ~/.gradle - echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties - echo "org.gradle.parallel=true" >> ~/.gradle/gradle.properties - echo "org.gradle.configureondemand=true" >> ~/.gradle/gradle.properties - echo "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError" >> ~/.gradle/gradle.properties - - - name: Grant execute permission for gradlew - run: chmod +x cardinal-android/gradlew - - - name: Install cargo-ndk - run: cargo install cargo-ndk - - - name: Install Protoc - uses: arduino/setup-protoc@v3 - - - name: Set up keystore - run: | - echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > cardinal-android/app/release.keystore - - - name: Build release APKs and AABs - working-directory: cardinal-android - run: | - touch local.properties - # Build APKs for each architecture - ./gradlew assembleArm64Release assembleX86_64Release --info --stacktrace --no-daemon - # Build AABs for each architecture - ./gradlew bundleArm64Release bundleX86_64Release --info --stacktrace --no-daemon - env: - ANDROID_HOME: ${{ env.ANDROID_HOME }} - ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_HOME }} - ANDROID_NDK_ROOT: ${{ env.ANDROID_NDK_ROOT }} - NDK_HOME: ${{ env.NDK_HOME }} - VERSION_NAME: ${{ steps.version.outputs.version }} - VERSION_CODE: ${{ steps.version.outputs.version_code }} - KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} - KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} - KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} - - - name: Verify APKs and AABs were signed - run: | - echo "=== Checking ARM64 APK ===" - ARM64_APK="cardinal-android/app/build/outputs/apk/arm64/release/app-arm64-release.apk" - if [ -f "$ARM64_APK" ]; then - echo "✅ ARM64 APK found at: $ARM64_APK" - ls -lh "$ARM64_APK" - $ANDROID_HOME/build-tools/*/aapt dump badging "$ARM64_APK" | head -3 - else - echo "❌ ARM64 APK not found!" - exit 1 - fi - - echo "=== Checking x86_64 APK ===" - X86_64_APK="cardinal-android/app/build/outputs/apk/x86_64/release/app-x86_64-release.apk" - if [ -f "$X86_64_APK" ]; then - echo "✅ x86_64 APK found at: $X86_64_APK" - ls -lh "$X86_64_APK" - $ANDROID_HOME/build-tools/*/aapt dump badging "$X86_64_APK" | head -3 - else - echo "❌ x86_64 APK not found!" - exit 1 - fi - - echo "=== Checking ARM64 AAB ===" - ARM64_AAB="cardinal-android/app/build/outputs/bundle/arm64Release/app-arm64-release.aab" - if [ -f "$ARM64_AAB" ]; then - echo "✅ ARM64 AAB found at: $ARM64_AAB" - ls -lh "$ARM64_AAB" - else - echo "❌ ARM64 AAB not found!" - exit 1 - fi - - echo "=== Checking x86_64 AAB ===" - X86_64_AAB="cardinal-android/app/build/outputs/bundle/x86_64Release/app-x86_64-release.aab" - if [ -f "$X86_64_AAB" ]; then - echo "✅ x86_64 AAB found at: $X86_64_AAB" - ls -lh "$X86_64_AAB" - else - echo "❌ x86_64 AAB not found!" - exit 1 - fi - - - name: Upload release artifacts - uses: actions/upload-artifact@v4 - with: - name: android-release-${{ steps.version.outputs.version }} - path: | - cardinal-android/app/build/outputs/apk/arm64/release/app-arm64-release.apk - cardinal-android/app/build/outputs/apk/x86_64/release/app-x86_64-release.apk - cardinal-android/app/build/outputs/bundle/arm64Release/app-arm64-release.aab - cardinal-android/app/build/outputs/bundle/x86_64Release/app-x86_64-release.aab - retention-days: 30 - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.version.outputs.tag }} - name: Cardinal Maps ${{ steps.version.outputs.version }} - draft: false - prerelease: true - generate_release_notes: true - body: | - ## Cardinal Maps ${{ steps.version.outputs.version }} - - This release includes separate builds for different architectures: - - ### APK Files (for sideloading) - - `app-arm64-release.apk` - For ARM64 devices (most modern Android phones) - - `app-x86_64-release.apk` - For x86_64 devices (emulators, some tablets) - - ### AAB Files (for Google Play Store) - - `app-arm64-release.aab` - ARM64 Android App Bundle for Google Play - - `app-x86_64-release.aab` - x86_64 Android App Bundle for Google Play - - Choose the appropriate file for your device architecture. If unsure, try the ARM64 version first as it works on most modern Android devices. - files: | - cardinal-android/app/build/outputs/apk/arm64/release/app-arm64-release.apk - cardinal-android/app/build/outputs/apk/x86_64/release/app-x86_64-release.apk - cardinal-android/app/build/outputs/bundle/arm64Release/app-arm64-release.aab - cardinal-android/app/build/outputs/bundle/x86_64Release/app-x86_64-release.aab - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload build logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: release-build-logs-${{ steps.version.outputs.version }} - path: | - cardinal-android/app/build/reports/ - ~/.gradle/daemon/*/daemon-*.out.log - retention-days: 7 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 5108fc5fe3b6f309bce5fab3470f777c108f8437..0000000000000000000000000000000000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code of Conduct - -The Cardinal project adheres to the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). This describes the minimum behavior expected from all contributors. - -## Enforcement - -Instances of violations of the Code of Conduct can be reported by [contacting the lead maintainer directly](mailto:ellen.h.poe@gmail.com). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 1004defc6368bae23e912734b102e1d5756fcf00..0000000000000000000000000000000000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,94 +0,0 @@ -# Contributing to Cardinal Maps - -Thank you for your interest in contributing to Cardinal Maps! We would love your help. Here are some tips and guidelines for how to get started. If your quesiton isn't answered here please feel free to [open an issue](https://github.com/ellenhp/cardinal/issues/new). - -## Code of Conduct - -First things first, the Cardinal project adheres to the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). You are expected to uphold these values while participating. Please report unacceptable behavior to [the lead maintainer](mailto:ellen.h.poe@gmail.com). - -## How to Contribute - -### Reporting Issues - -- Use the GitHub issue tracker to report bugs or suggest features -- Before creating a new issue, try to search existing issues to avoid duplicates -- Include as much relevant information as possible in your report: - - Steps to reproduce the issue - - Expected vs actual behavior - - Screenshots if applicable - - Device and OS information for Android issues - -### Pull Request Process - -1. Fork the repository and create your branch from `main` -2. Do your best to add some tests (at press time this is mostly aspirational) -3. Ensure your test suite passes -4. Make sure your code follows the existing style for whichever component(s) you're modifying -5. Update documentation as needed -6. Submit your pull request with a clear description of your changes - -## Development Guidelines - -### Android App - -The Android app is built with Kotlin and follows modern Android development practices. - -#### Code Style -- Use meaningful variable and function names -- Keep functions small and focused when you can -- Prefer immutable data structures where possible - -#### Architecture -- Follow MVVM (Model-View-ViewModel) architecture pattern -- Use dependency injection with Hilt -- Separate UI logic from business logic - -#### Design Principles -- Follow [Material 3 guidelines](https://m3.material.io/) for components, typography, and color schemes -- Maintain consistency in UI patterns throughout the app -- Prioritize accessibility and usability for all users -- Aim for a clean, minimalist aesthetic - -#### Implementation -- Use Android Compose for all UI components -- Use Material 3 components and themes where possible -- Try to implement adaptive layouts that work across different screen sizes (currently aspirational) -- Follow the existing color scheme and typography defined in the theme - -#### Building -```bash -cd cardinal-android -./gradlew assembleDebug -``` - -### Rust components - -The offline geocoder is written in Rust and changes to it should follow Rust best practices. - -#### Code Style -- Use `rustfmt` for code formatting -- Use `clippy` for linting -- Write idiomatic Rust code whenever possible - - We use UniFFI to expose rust code to Kotlin (and in the future, Swift) which imposes some limits - -### Privacy and Security -- Respect user privacy in all code changes -- Avoid mistakes that could leak user data to online services without knowledge or consent (consent entails knowledge) - -## Getting Help - -If you need help with your contribution, feel free to: -- Ask questions in the PR comments -- Open an issue for discussion about larger changes -- Contact the maintainers directly - -## Generative AI Policy - -Unlike quite a few projects, we do accept contributions created with generative AI tooling with some very important caveats: - -1. You must fully review all AI-generated code before submission (this should be obvious, and yet...) -2. Contributors are themselves responsible for the quality and correctness of all code, including AI-generated code they submit -3. Core business logic should not be "vibe-coded"—you may use AI for boilerplate, code review or idea generation, but not for making intricate changes to core functionality that you don't fully understand - -Please see our full [Generative AI Policy](GEN_AI_POLICY.md) for more details. Do not abuse this policy. - diff --git a/GEN_AI_POLICY.md b/GEN_AI_POLICY.md deleted file mode 100644 index e839eb4c992a44980c76b15c565ddc6ac6490636..0000000000000000000000000000000000000000 --- a/GEN_AI_POLICY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Generative AI Policy - -The Cardinal project accepts contributions created with the assistance of generative AI tooling such as LLMs, with some caveats. -1. A human must be in the loop. Do not under any circumstances send us PRs that haven't been reviewed in full by a human. This is a bannable offense. -2. The buck stops at the first human in the loop. If your LLM tooling writes a bug, I will not treat it any differently from you writing the bug. If the bug is particularly harmful or malicious, you need to be ready to take responsibility for that. -3. Don't vibe-code your core business logic. We don't want to see low-quality AI slop in our geocoder or any of the other core features that define Cardinal maps as an offering in the maps space. If something has been done thousands of times by thousands of apps, we're less concerned about AI-generated boilerplate. - -### Suggestions - -Every time an agentic change is made: -1. Note which files are affected. -2. Force the agent to read all affected files and examine them for "self-consistency and code quality issues including duplicated logic, dead code and unused stubs". diff --git a/PRIVACY.md b/PRIVACY.md deleted file mode 100644 index 157d022156f2cefd052678ba9ea77ecee78bb303..0000000000000000000000000000000000000000 --- a/PRIVACY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Cardinal Maps Privacy Policy - -Cardinal Maps is an open-soure mapping application focused on helping you get around while respecting your privacy. - -We do not collect your location data directly, but we do collect data in certain circumstances that is related to user location, or can be used to infer user location with some nonzero probability. - -## Offline Mode - -First things first, if you use Cardinal Maps in offline mode, the only data we collect is which areas of the earth you choose to download maps for, with one exception. We don't have analytics, don't show or target ads, or any do of the other invasive things that most other apps do. If you use the app offline, your location data never leaves your device. - -The exception to this rule is when you have the "fetch transit information even while offline" setting enabled. This setting is enabled by default, but can be diabled easily in the Offline Settings screen, which is accessible by tapping the Cardinal icon in the top left of the screen and tapping "Offline Settings". - -## Online Mode - -In order to provide an online mode, we do collect some information, but we've limited it to the information that we absolutely must have in order to provide the online service in question. - -### Configurable backends - -Cardinal Maps allows you to configure the application so that you can choose to send search and routing requests to whomever you choose. If you decide that you trust e.g. Stadia Maps more than you trust maps.earth (the default) you can configure the app to send your search and routing requests to them instead. You can also point the app at a self-hosted service if you decide you only trust yourself. Once you modify these advanced settings, the information described in the sections below will be sent to whoever it is that you choose, rather than to us. - -#### Online search - -We collect your search queries in online mode. We need this information in order to provide you search results. If you search for "Paris, France", we need that raw text in order to look up where Paris is in our database. - -#### Online routing - -We collect the "to" and "from" locations on your routing requests. If you choose to give the app location permissions, the "from" endpoint for your online routing requests will sometimes be your current location, and that location will be sent to our servers. If you do not choose to give the app location permissions, you can fetch routes for whichever locations you choose. We have no way of knowing whether the route requests we receive correspond to a user's location or not. This information is processed ephemerally and kept in our logs that are typically kept for about a week. We don't have any log persistence set up, but neither do we make any concerted effort to wipe our logs at fixed intervals. If you would like this feature, please help us out! Our default backend, maps.earth, is powered by Headway, an open-source web maps stack. We'd love an audit to make sure our logs aren't being retained for any longer than strictly necessary. diff --git a/README.md b/README.md index 3f08dd8f53a2a424ca0906ca7a60492f65e398fb..a79e7c1bec16dfa5b026ea0c936dc328ab310c50 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -![GitHub License](https://img.shields.io/github/license/ellenhp/cardinal) -![GitHub commit activity](https://img.shields.io/github/commit-activity/m/ellenhp/cardinal) -![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ellenhp/cardinal/build_android.yml) -![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/ellenhp/cardinal/total) - - Get it on Obtainium - - # Cardinal Maps Cardinal Maps is a mapping application for Android designed to get out of your way and be there when you need it. We believe maps should be fast, private, and focused on what matters most—helping you navigate the world around you. @@ -26,14 +18,6 @@ Every decision we make puts the user first: Basemap viewPlace card viewDirections view -## Support the project - -Cardinal maps is a labor of love with, currently, one committer. If you've been looking for something better in the FOSS maps space for years like me, and see the vision of Cardinal Maps, your support would mean the world. Anything from a small sponsorship on [GitHub Sponsors](https://github.com/sponsors/ellenhp) or [Liberapay](https://liberapay.com/ellenhp) to bug reports and pull requests help. Even starring the project is enough to give me a little dopamine hit and keep me motivated to continue work. - -## Contributing - -If you'd like to help out we welcome contributions from the community! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information. Cardinal Maps is written in Rust and Kotlin and based on industry standard maps tooling, so feature work is rewarding and developer friction is low. - ## License This project is licensed under the GNU General Public License, version 3—see the [LICENSE.md](LICENSE.md) file for details.