9 Commits

Author SHA1 Message Date
Sebastian Molenda 244aa8457a Moze wersja 1.0.0?
Deploy to FTP / deploy (push) Successful in 5s
Build APK / build (push) Successful in 2m11s
2026-05-29 22:53:11 +02:00
Sebastian Molenda 03bcb465cf 0.2.3-t9
Deploy to FTP / deploy (push) Successful in 5s
Build APK / build (push) Successful in 1m56s
2026-05-29 22:37:21 +02:00
Sebastian Molenda b2721c2ec3 0.2.3-t8
Deploy to FTP / deploy (push) Successful in 5s
Build APK / build (push) Failing after 1m56s
2026-05-29 22:26:03 +02:00
Sebastian Molenda 1385b3ace0 0.2.3-t7
Deploy to FTP / deploy (push) Successful in 4s
Build APK / build (push) Failing after 1m57s
2026-05-29 22:20:33 +02:00
Sebastian Molenda 66a028880c 0.2.3-t5
Deploy to FTP / deploy (push) Successful in 5s
Build APK / build (push) Failing after 1m59s
2026-05-27 21:45:21 +02:00
Sebastian Molenda 365b12b0eb 0.2.3-t4
Deploy to FTP / deploy (push) Successful in 4s
Build APK / build (push) Failing after 1m56s
Release APK on Tag (Gitea Actions) / build-and-release (push) Failing after 1s
2026-05-27 21:38:16 +02:00
Sebastian Molenda 5ef6747e4d 0.2.3-t3
Deploy to FTP / deploy (push) Successful in 4s
Release APK on Tag (Gitea Actions) / build-and-release (push) Failing after 2s
Build APK / build (push) Failing after 1m57s
2026-05-27 21:21:49 +02:00
Sebastian Molenda 7ce23309f6 0.2.3-t2
Deploy to FTP / deploy (push) Successful in 5s
Build APK / build (push) Successful in 2m1s
Release APK on Tag (Gitea Actions) / build-and-release (push) Failing after 2s
2026-05-27 15:04:28 +02:00
Sebastian Molenda 6c4b5f4adf 0.2.3
Deploy to FTP / deploy (push) Successful in 5s
Build APK / build (push) Successful in 1m58s
2026-05-27 14:57:44 +02:00
29 changed files with 427 additions and 165 deletions
+130 -5
View File
@@ -27,11 +27,136 @@ jobs:
- name: Grant execute permission
run: chmod +x ./gradlew
- name: Update version in assets
run: |
set -e
TAG="${{ github.ref_name }}"
# Jeśli tag jest pusty (np. push nie na tag), użyjemy skróconego SHA
if [ -z "$TAG" ] || [ "$TAG" = "main" ]; then
TAG=$(git rev-parse --short HEAD)
fi
echo "Setting version to: $TAG"
# Aktualizacja pliku js/version.js
echo "const APP_VERSION = '$TAG';" > app/src/main/assets/js/version.js
echo "document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('app-version') || document.getElementById('commit-sha');
if (el) el.textContent = '$TAG';
});" >> app/src/main/assets/js/version.js
- name: Build APK
run: ./gradlew assembleRelease
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: app-release
path: app/build/outputs/apk/release/app-release.apk
- name: Locate APK
id: locate_apk
run: |
set -e
APK_PATH=$(ls app/build/outputs/apk/release/*.apk | head -n1 || true)
if [ -z "$APK_PATH" ]; then
echo "No APK found in app/build/outputs/apk/release" >&2
exit 1
fi
echo "APK_PATH=$APK_PATH" >> $GITHUB_ENV
echo "Found $APK_PATH"
# Note: uploading artifacts with actions/upload-artifact@v4 is not supported
# on some self-hosted/enterprise runners (GHES). We skip storing artifacts
# via that action and instead upload the APK directly to the Gitea release
# in the steps below.
- name: Create Gitea release
id: create_release
env:
GITEA_TOKEN: ${{ secrets.GIT_TOKEN }}
GITEA_SERVER: ${{ secrets.GIT_SERVER }}
run: |
set -e
SERVER_CLEAN=$(echo "$GITEA_SERVER" | sed 's#/$##')
API_BASE="${SERVER_CLEAN}/api/v1"
OWNER="${{ github.repository_owner }}"
REPO_NAME=$(echo "${{ github.repository }}" | cut -d/ -f2)
TAG="${{ github.ref_name }}"
echo "Debug Info:"
echo "- API URL: $API_BASE/repos/$OWNER/$REPO_NAME/releases"
echo "- OWNER: $OWNER"
echo "- REPO: $REPO_NAME"
echo "- TAG: $TAG"
# Próba utworzenia release z przechwyceniem statusu HTTP
RESPONSE_FILE=$(mktemp)
HTTP_STATUS=$(curl -s -o "$RESPONSE_FILE" -w "%{http_code}" \
-H "Content-Type: application/json" \
-H "Authorization: token $GITEA_TOKEN" \
-X POST \
-d "{
\"tag_name\": \"$TAG\",
\"name\": \"$TAG\",
\"body\": \"Automated release for $TAG\",
\"draft\": false,
\"prerelease\": false
}" \
"$API_BASE/repos/$OWNER/$REPO_NAME/releases")
RESPONSE_BODY=$(cat "$RESPONSE_FILE")
echo "HTTP Status: $HTTP_STATUS"
echo "Response: $RESPONSE_BODY"
if [ "$HTTP_STATUS" -eq 409 ]; then
echo "Release already exists, fetching existing ID..."
RESPONSE_BODY=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$API_BASE/repos/$OWNER/$REPO_NAME/releases/tags/$TAG")
RELEASE_ID=$(echo "$RESPONSE_BODY" | sed -n 's/.*"id":\([0-9]*\),.*/\1/p' | head -n1)
elif [ "$HTTP_STATUS" -eq 201 ]; then
RELEASE_ID=$(echo "$RESPONSE_BODY" | sed -n 's/.*"id":\([0-9]*\),.*/\1/p' | head -n1)
else
echo "Failed to create release. Expected 201 or 409, got $HTTP_STATUS"
exit 1
fi
if [ -z "$RELEASE_ID" ]; then
echo "Failed to extract RELEASE_ID from response"
exit 1
fi
echo "RELEASE_ID=$RELEASE_ID" >> $GITHUB_ENV
echo "Successfully processed release ID: $RELEASE_ID"
- name: Upload APK to Gitea release
env:
GITEA_TOKEN: ${{ secrets.GIT_TOKEN }}
GITEA_SERVER: ${{ secrets.GIT_SERVER }}
run: |
set -e
SERVER_CLEAN=$(echo "$GITEA_SERVER" | sed 's#/$##')
API_BASE="${SERVER_CLEAN}/api/v1"
OWNER="${{ github.repository_owner }}"
REPO_NAME=$(echo "${{ github.repository }}" | cut -d/ -f2)
TAG="${{ github.ref_name }}"
if [ -z "$APK_PATH" ]; then
echo "APK_PATH not set" >&2
exit 1
fi
if [ -z "$RELEASE_ID" ]; then
echo "RELEASE_ID not set" >&2
exit 1
fi
# Tworzymy ładną nazwę dla pliku
FRIENDLY_NAME="Matma-${TAG}.apk"
cp "$APK_PATH" "./$FRIENDLY_NAME"
UPLOAD_URL="$API_BASE/repos/$OWNER/$REPO_NAME/releases/$RELEASE_ID/assets?name=$FRIENDLY_NAME"
echo "Uploading $FRIENDLY_NAME to $UPLOAD_URL"
curl --fail -s -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @"./$FRIENDLY_NAME" \
"$UPLOAD_URL"
# Dodanie bezpośredniego linku do podsumowania buildu w Gitea
DOWNLOAD_URL="${SERVER_CLEAN}/${OWNER}/${REPO_NAME}/releases/download/${TAG}/${FRIENDLY_NAME}"
echo "### ✅ APK gotowy do pobrania!" >> $GITHUB_STEP_SUMMARY
echo "[Pobierz plik $FRIENDLY_NAME]($DOWNLOAD_URL)" >> $GITHUB_STEP_SUMMARY
echo "Adres: $DOWNLOAD_URL" >> $GITHUB_STEP_SUMMARY
+36 -1
View File
@@ -1,3 +1,38 @@
.DS_Store
./QuizzyTemplate
./QuizzyTemplate/*
/.QuizzyTemplate
# Android/Gradle
/.gradle/
/build/
/app/build/
/**/build/
# Gradle wrapper
/gradle/wrapper/gradle-wrapper.jar
# Local configuration
/local.properties
# Keystore
*.jks
# Generated APKs/outputs
**/outputs/
**/apk/**
*.apk
*.ap_
# IntelliJ / Android Studio
.idea/
*.iml
# macOS
.DS_Store
# NPM / Node
node_modules/
# Misc
*.log
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+27 -77
View File
@@ -4,70 +4,7 @@
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="51538617-7e5b-4e71-9f47-7bda274cf4cc" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.gitea/workflows/build-apk.yml" beforeDir="false" afterPath="$PROJECT_DIR$/.gitea/workflows/build-apk.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/executionHistory/executionHistory.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/fileHashes/fileHashes.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/8.5/fileHashes/resourceHashesCache.bin" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/8.5/fileHashes/resourceHashesCache.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/buildOutputCleanup/buildOutputCleanup.lock" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/buildOutputCleanup/buildOutputCleanup.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gradle/file-system.probe" beforeDir="false" afterPath="$PROJECT_DIR$/.gradle/file-system.probe" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/components.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/components.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/dzielenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/dzielenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/mnozenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/assets/debug/js/mnozenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/components.js.jar" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/components.js.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/dzielenie.js.jar" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/dzielenie.js.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/mnozenie.js.jar" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/compressed_assets/debug/out/assets/js/mnozenie.js.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_3/graph.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/desugar_graph/debug/out/currentProject/dirs_bucket_3/graph.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/dex/debug/mergeProjectDexDebug/6/classes.dex" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/dex/debug/mergeProjectDexDebug/6/classes.dex" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/incremental/packageDebug/tmp/debug/dex-renamer-state.txt" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/incremental/packageDebug/tmp/debug/dex-renamer-state.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/intermediates/project_dex_archive/debug/out/com/example/app/MainActivity.dex" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/intermediates/project_dex_archive/debug/out/com/example/app/MainActivity.dex" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/last-build.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/cacheable/last-build.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/local-state/build-history.bin" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/kotlin/compileDebugKotlin/local-state/build-history.bin" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/outputs/apk/debug/app-debug.apk" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/outputs/apk/debug/app-debug.apk" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/build/tmp/kotlin-classes/debug/com/example/app/MainActivity.class" beforeDir="false" afterPath="$PROJECT_DIR$/app/build/tmp/kotlin-classes/debug/com/example/app/MainActivity.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/assets/js/components.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/assets/js/components.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/assets/js/dzielenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/assets/js/dzielenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/assets/js/mnozenie.js" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/assets/js/mnozenie.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/app/MainActivity.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/app/MainActivity.kt" afterDir="false" />
</list>
<list default="true" id="51538617-7e5b-4e71-9f47-7bda274cf4cc" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -76,6 +13,18 @@
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="ExternalProjectsManager">
<system id="GRADLE">
<state>
<projects_view>
<tree_state>
<expand />
<select />
</tree_state>
</projects_view>
</state>
</system>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
@@ -88,20 +37,21 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/aln/Work/Matma&quot;
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.readMode.enableVisualFormatting": "true",
"android.gradle.sync.needed": "true",
"cf.first.check.clang-format": "false",
"cidr.known.project.marker": "true",
"git-widget-placeholder": "master",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "/Users/aln/Work/Matma"
}
}</component>
}]]></component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="51538617-7e5b-4e71-9f47-7bda274cf4cc" name="Changes" comment="" />
+30
View File
@@ -13,3 +13,33 @@ Pliki
Jak uruchomić
Otwórz plik `index.html` w przeglądarce (najlepiej na urządzeniu mobilnym lub w trybie responsywnym).
Signing a release APK/AAB
------------------------
Aby uniknąć ostrzeżeń "nieznany deweloper" lub instalatora na Androidzie, zbuduj podpisany pakiet APK lub AAB i zainstaluj go zamiast debugowego APK.
1. Utwórz magazyn kluczy (jeśli go nie masz):
```bash
keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my_key_alias
```
2. Skopiuj `gradle.properties.example` do swojego osobistego pliku `~/.gradle/gradle.properties` i uzupełnij wartości (nie commituj prawdziwych haseł do kontroli wersji).
3. Zbuduj podpisany pakiet APK lub AAB:
```bash
./gradlew assembleRelease # podpisany APK (jeśli podpisywanie jest skonfigurowane)
./gradlew bundleRelease # AAB do Sklepu Play
```
4. Zainstaluj APK za pomocą adb:
```bash
adb install -r app/build/outputs/apk/release/app-release.apk
```
Lub przesłać AAB do Google Play (testowanie wewnętrzne) — zalecane w celu najłatwiejszej dystrybucji.
Jeśli chcesz tylko przetestować lokalnie i zobaczyć ostrzeżenie "Nieznane źródła", możesz tymczasowo włączyć instalowanie z nieznanych źródeł na urządzeniu, ale dystrybucja podpisanego wydania jest bezpieczniejszym podejściem.
+29
View File
@@ -14,10 +14,39 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// Signing configuration for release builds.
// Configure the following properties in either your
// - ~/.gradle/gradle.properties (recommended for secrets), or
// - <project root>/gradle.properties (do NOT commit passwords)
//
// Example properties:
// MYAPP_STORE_FILE=keystores/my-release-key.jks
// MYAPP_STORE_PASSWORD=your_store_password
// MYAPP_KEY_ALIAS=my_key_alias
// MYAPP_KEY_PASSWORD=your_key_password
signingConfigs {
release {
// Only configure signing when the properties are provided.
if (project.hasProperty('MYAPP_STORE_FILE')) {
storeFile file(MYAPP_STORE_FILE)
storePassword MYAPP_STORE_PASSWORD
keyAlias MYAPP_KEY_ALIAS
keyPassword MYAPP_KEY_PASSWORD
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// Apply signing config if available. This keeps debug and CI builds
// working even when signing properties are absent locally.
if (project.hasProperty('MYAPP_STORE_FILE')) {
signingConfig signingConfigs.release
}
// Ensure release builds are not debuggable.
debuggable false
}
}
+23 -23
View File
@@ -55,19 +55,19 @@ const statusEl = document.getElementById('status');
// load settings from localStorage
function loadSettings(){
try{
let progressInner = null
const raw = (typeof localStorage !== 'undefined') ? localStorage.getItem('matma:settings') : null
if (raw) {
const s = JSON.parse(raw)
state.settings = Object.assign(state.settings, s)
}
}catch(e){ console.warn('settings load failed', e) }
// reflect to inputs
settingTimed.value = state.settings.timedSeconds
settingMaxResult.value = state.settings.maxResult
settingMaxOperand.value = state.settings.maxOperand
settingSessionProblems.value = state.settings.sessionProblems
settingAllowNegative.checked = !!state.settings.allowNegative
settingAllowFraction.checked = !!state.settings.allowFraction
// reflect to inputs (guard in case some inputs are missing)
if (settingTimed) settingTimed.value = state.settings.timedSeconds
if (settingMaxResult) settingMaxResult.value = state.settings.maxResult
if (settingMaxOperand) settingMaxOperand.value = state.settings.maxOperand
if (settingSessionProblems) settingSessionProblems.value = state.settings.sessionProblems
if (settingAllowNegative) settingAllowNegative.checked = !!state.settings.allowNegative
if (settingAllowFraction) settingAllowFraction.checked = !!state.settings.allowFraction
}
function saveSettings(){
@@ -145,16 +145,16 @@ function startPlay(){
renderProblem()
if (state.mode === 'timed'){
progressInner.style.width = '0%'
startTimer(state.settings.timedSeconds)
statusEl.textContent = 'Na czas';
timerEl.classList.remove('hidden');
if (progressInner) progressInner.style.width = '0%'
startTimer(state.settings.timedSeconds)
if (statusEl) statusEl.textContent = 'Na czas';
if (timerEl) timerEl.classList.remove('hidden');
} else {
progressInner.style.width = '0%'
state.sessionSolved = 0
state.sessionTarget = Math.max(1, state.settings.sessionProblems)
updateProgress()
timerEl.classList.add('hidden');
if (progressInner) progressInner.style.width = '0%'
state.sessionSolved = 0
state.sessionTarget = Math.max(1, state.settings.sessionProblems)
updateProgress()
if (timerEl) timerEl.classList.add('hidden');
}
}
@@ -311,14 +311,14 @@ backspaceBtn.addEventListener('click', ()=>{
function startTimer(seconds){
state.timeLeft = seconds
timerEl.textContent = state.timeLeft
if (timerEl) timerEl.textContent = state.timeLeft
stopTimer();
state.timerId = setInterval(()=>{
state.timeLeft -= 1
timerEl.textContent = state.timeLeft
if (timerEl) timerEl.textContent = state.timeLeft
const pct = Math.max(0, (state.timeLeft / seconds) * 100)
progressInner.style.width = pct + '%'
if (progressInner) progressInner.style.width = pct + '%'
if (state.timeLeft <= 0) {
stopTimer()
endSession()
@@ -357,9 +357,9 @@ summaryBack.addEventListener('click', ()=>{
function updateProgress(){
if (state.mode === 'training'){
const pct = Math.min(100, Math.round((state.sessionSolved/state.sessionTarget)*100))
progressInner.style.width = pct + '%'
statusEl.textContent = `${state.sessionSolved}/${state.sessionTarget}`
const pct = Math.min(100, Math.round((state.sessionSolved/state.sessionTarget)*100))
if (progressInner) progressInner.style.width = pct + '%'
if (statusEl) statusEl.textContent = `${state.sessionSolved}/${state.sessionTarget}`
}
}
@@ -4,14 +4,10 @@
const backBtn = document.getElementById('back-to-hub')
if (!backBtn) return
// Immediately navigate back to hub/menu without confirmation
backBtn.addEventListener('click', (e) => {
const playScreen = document.getElementById('play-screen')
const taskActive = playScreen && !playScreen.classList.contains('hidden')
if (taskActive) {
e.preventDefault()
const ok = confirm('Masz aktywne zadanie. Czy na pewno chcesz wyjść do menu?')
if (ok) window.location.href = backBtn.getAttribute('href')
}
const href = backBtn.getAttribute('href')
if (href) window.location.href = href
})
})
})()
@@ -1,4 +1,4 @@
#Wed May 27 14:20:16 CEST 2026
#Wed May 27 14:53:22 CEST 2026
base.2=/Users/aln/Work/Matma/app/build/intermediates/dex/debug/mergeProjectDexDebug/6/classes.dex
path.2=6/classes.dex
base.1=/Users/aln/Work/Matma/app/build/intermediates/dex/debug/mergeProjectDexDebug/0/classes.dex
Binary file not shown.
+8
View File
@@ -52,9 +52,17 @@
</div>
<div id="footer-placeholder"></div>
<header class="app-header">
<button id="back-btn" class="app-nav-btn">← Menu</button>
<div class="header-title">Nauka Czytania</div>
</header>
<footer class="app-footer" style="text-align: center; padding: 1rem; font-size: 0.8rem; color: #64748b;">
Wersja: <span id="app-version">...</span>
</footer>
<script src="js/nav.js?v=20260521"></script>
<script src="js/czytanie.js?v=20260524"></script>
<script src="js/components.js"></script>
<script src="js/version.js"></script>
<script>
// Ładowanie nagłówka i stopki
loadComponent('components/header.html', {
+4
View File
@@ -59,9 +59,13 @@
</div>
<div id="footer-placeholder"></div>
<footer class="app-footer" style="text-align: center; padding: 1rem; font-size: 0.8rem; color: #64748b;">
Wersja: <span id="app-version">...</span>
</footer>
<script src="js/nav.js?v=20260521"></script>
<script src="js/dyktando.js?v=20260524"></script>
<script src="js/components.js"></script>
<script src="js/version.js"></script>
<script>
loadComponent('components/header.html', {
'{TITLE}': '✏️ Dyktando',
+4
View File
@@ -95,9 +95,13 @@
</div>
<div id="footer-placeholder"></div>
<footer class="app-footer" style="text-align: center; padding: 1rem; font-size: 0.8rem; color: #64748b;">
Wersja: <span id="app-version">...</span>
</footer>
<script src="js/nav.js?v=20260521"></script>
<script src="js/dzielenie.js?v=20260524"></script>
<script src="js/components.js"></script>
<script src="js/version.js"></script>
<script>
loadComponent('components/header.html', {
'{TITLE}': '÷ Nauka Dzielenia',
+65 -42
View File
@@ -9,6 +9,7 @@ const state = {
timeLeft: 60,
sessionSolved: 0,
sessionTarget: 20,
history: [],
settings: {
timedSeconds: 60,
maxResult: 40,
@@ -55,19 +56,19 @@ const statusEl = document.getElementById('status');
// load settings from localStorage
function loadSettings(){
try{
let progressInner = null
const raw = (typeof localStorage !== 'undefined') ? localStorage.getItem('matma:settings') : null
if (raw) {
const s = JSON.parse(raw)
state.settings = Object.assign(state.settings, s)
}
}catch(e){ console.warn('settings load failed', e) }
// reflect to inputs
settingTimed.value = state.settings.timedSeconds
settingMaxResult.value = state.settings.maxResult
settingMaxOperand.value = state.settings.maxOperand
settingSessionProblems.value = state.settings.sessionProblems
settingAllowNegative.checked = !!state.settings.allowNegative
settingAllowFraction.checked = !!state.settings.allowFraction
// reflect to inputs (guard in case some inputs are missing)
if (settingTimed) settingTimed.value = state.settings.timedSeconds
if (settingMaxResult) settingMaxResult.value = state.settings.maxResult
if (settingMaxOperand) settingMaxOperand.value = state.settings.maxOperand
if (settingSessionProblems) settingSessionProblems.value = state.settings.sessionProblems
if (settingAllowNegative) settingAllowNegative.checked = !!state.settings.allowNegative
if (settingAllowFraction) settingAllowFraction.checked = !!state.settings.allowFraction
}
function saveSettings(){
@@ -137,6 +138,7 @@ function startPlay(){
menuScreen.classList.add('hidden')
playScreen.classList.remove('hidden')
state.score = 0
state.history = []
scoreEl.textContent = state.score
state.answerBuffer = ''
answerEl.textContent = ''
@@ -145,68 +147,89 @@ function startPlay(){
renderProblem()
if (state.mode === 'timed'){
progressInner.style.width = '0%'
startTimer(state.settings.timedSeconds)
statusEl.textContent = 'Na czas';
timerEl.classList.remove('hidden');
if (progressInner) progressInner.style.width = '0%'
startTimer(state.settings.timedSeconds)
if (statusEl) statusEl.textContent = 'Na czas';
if (timerEl) timerEl.classList.remove('hidden');
} else {
progressInner.style.width = '0%'
state.sessionSolved = 0
state.sessionTarget = Math.max(1, state.settings.sessionProblems)
updateProgress()
timerEl.classList.add('hidden');
if (progressInner) progressInner.style.width = '0%'
state.sessionSolved = 0
state.sessionTarget = Math.max(1, state.settings.sessionProblems)
updateProgress()
if (timerEl) timerEl.classList.add('hidden');
}
}
function generateProblem(){
const ops = Array.from(state.ops)
const op = ops[Math.floor(Math.random()*ops.length)]
const maxOp = Math.max(1, state.settings.maxOperand)
const maxRes = Math.max(1, state.settings.maxResult)
let a = 0, b = 0
for (let i=0;i<200;i++){
let a = 0, b = 0, op = '';
const maxAttempts = 200;
for (let i = 0; i < maxAttempts; i++) {
op = ops[Math.floor(Math.random()*ops.length)]
let candidate = null;
if (op === 'div'){
b = randInt(1, maxOp)
if (!state.settings.allowFraction){
const maxQByOp = Math.floor(maxOp / b)
const maxQ = Math.min(maxRes, maxQByOp)
if (maxQ < 0) continue
const q = randInt(0, maxQ)
a = q * b
if (state.settings.allowNegative && Math.random() < 0.2) a = -a
return {a,b,op}
if (maxQ >= 0) {
const q = randInt(0, maxQ)
a = q * b
if (state.settings.allowNegative && Math.random() < 0.2) a = -a
candidate = {a,b,op}
}
} else {
const maxResultNByOp = Math.floor((10 * maxOp) / b)
const maxResultN = Math.min(maxRes * 10, maxResultNByOp)
if (maxResultN < 0) continue
const result_n = randInt(0, maxResultN)
if ((result_n * b) % 10 !== 0) continue
a = (result_n * b) / 10
if (state.settings.allowNegative && Math.random() < 0.2) a = -a
return {a,b,op}
if (maxResultN >= 0) {
const result_n = randInt(0, maxResultN)
if ((result_n * b) % 10 === 0) {
a = (result_n * b) / 10
if (state.settings.allowNegative && Math.random() < 0.2) a = -a
candidate = {a,b,op}
}
}
}
} else if (op === 'mul'){
a = randInt(0, maxOp)
b = randInt(0, maxOp)
if (a * b <= maxRes) return {a,b,op}
if (a * b <= maxRes) candidate = {a,b,op}
} else if (op === 'add'){
a = randInt(0, maxOp)
b = randInt(0, maxOp)
if (a + b <= maxRes) return {a,b,op}
if (a + b <= maxRes) candidate = {a,b,op}
} else if (op === 'sub'){
a = randInt(0, maxOp)
b = randInt(0, maxOp)
if (!state.settings.allowNegative){
if (a < b) [a,b] = [b,a]
if (a - b <= maxRes) return {a,b,op}
if (a - b <= maxRes) candidate = {a,b,op}
} else {
if (Math.abs(a - b) <= maxRes) return {a,b,op}
if (Math.abs(a - b) <= maxRes) candidate = {a,b,op}
}
}
if (candidate) {
// Generujemy klucz dla historii. Dla dodawania i mnożenia 2+3 to to samo co 3+2.
const key = (op === 'add' || op === 'mul')
? `${op}:${[candidate.a, candidate.b].sort().join(',')}`
: `${op}:${candidate.a},${candidate.b}`;
if (!state.history.includes(key) || i === maxAttempts - 1) {
state.history.push(key);
if (state.history.length > 20) state.history.shift();
return candidate;
}
}
}
a = Math.min(maxOp, Math.floor(maxRes/2))
b = Math.min(maxOp, 1)
return {a,b,op}
return {a,b,op: ops[0] || 'add'}
}
function randInt(min,max){
@@ -311,14 +334,14 @@ backspaceBtn.addEventListener('click', ()=>{
function startTimer(seconds){
state.timeLeft = seconds
timerEl.textContent = state.timeLeft
if (timerEl) timerEl.textContent = state.timeLeft
stopTimer();
state.timerId = setInterval(()=>{
state.timeLeft -= 1
timerEl.textContent = state.timeLeft
if (timerEl) timerEl.textContent = state.timeLeft
const pct = Math.max(0, (state.timeLeft / seconds) * 100)
progressInner.style.width = pct + '%'
if (progressInner) progressInner.style.width = pct + '%'
if (state.timeLeft <= 0) {
stopTimer()
endSession()
@@ -357,9 +380,9 @@ summaryBack.addEventListener('click', ()=>{
function updateProgress(){
if (state.mode === 'training'){
const pct = Math.min(100, Math.round((state.sessionSolved/state.sessionTarget)*100))
progressInner.style.width = pct + '%'
statusEl.textContent = `${state.sessionSolved}/${state.sessionTarget}`
const pct = Math.min(100, Math.round((state.sessionSolved/state.sessionTarget)*100))
if (progressInner) progressInner.style.width = pct + '%'
if (statusEl) statusEl.textContent = `${state.sessionSolved}/${state.sessionTarget}`
}
}
+25 -5
View File
@@ -1,6 +1,6 @@
// Nauka Dzielenia
;(function () {
const st = { divisor: null, total: 20, solved: 0, score: 0, current: null, buf: '' }
const st = { divisor: null, total: 20, solved: 0, score: 0, current: null, buf: '', history: [] }
const selectScreen = document.getElementById('select-screen')
const playScreen = document.getElementById('play-screen')
@@ -26,13 +26,14 @@
st.total = Math.max(5, Math.min(100, parseInt(totalInput.value, 10) || 20))
st.solved = 0
st.score = 0
st.history = []
show(playScreen)
nextProblem()
})
document.getElementById('back-btn').addEventListener('click', () => show(selectScreen))
document.getElementById('again-btn').addEventListener('click', () => {
st.solved = 0; st.score = 0; show(playScreen); nextProblem()
st.solved = 0; st.score = 0; st.history = []; show(playScreen); nextProblem()
})
document.getElementById('change-btn').addEventListener('click', () => show(selectScreen))
@@ -60,9 +61,28 @@
function randInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min }
function nextProblem() {
const b = st.divisor || randInt(1, 10) // divisor
const answer = randInt(1, 10) // quotient (always integer)
const a = b * answer // dividend
let a, b, answer, key;
const maxAttempts = 50;
for (let i = 0; i < maxAttempts; i++) {
b = st.divisor || randInt(1, 10) // divisor
answer = randInt(1, 10) // quotient
a = b * answer // dividend
// Jeśli wybrano konkretny dzielnik, kluczem jest iloraz (wynik).
// W przeciwnym razie kluczem jest całe działanie.
key = st.divisor ? answer : `${a}:${b}`;
if (!st.history.includes(key) || i === maxAttempts - 1) {
st.history.push(key);
break;
}
}
if (st.history.length > (st.divisor ? 6 : 15)) {
st.history.shift();
}
st.current = { a, b, answer }
problemEl.textContent = `${a} ÷ ${b}`
feedbackEl.textContent = ''
+23 -4
View File
@@ -1,6 +1,6 @@
// Nauka Mnożenia
;(function () {
const st = { table: null, total: 20, solved: 0, score: 0, current: null, buf: '' }
const st = { table: null, total: 20, solved: 0, score: 0, current: null, buf: '', history: [] }
const selectScreen = document.getElementById('select-screen')
const playScreen = document.getElementById('play-screen')
@@ -27,13 +27,14 @@
st.total = Math.max(5, Math.min(100, parseInt(totalInput.value, 10) || 20))
st.solved = 0
st.score = 0
st.history = []
show(playScreen)
nextProblem()
})
document.getElementById('back-btn').addEventListener('click', () => show(selectScreen))
document.getElementById('again-btn').addEventListener('click', () => {
st.solved = 0; st.score = 0; show(playScreen); nextProblem()
st.solved = 0; st.score = 0; st.history = []; show(playScreen); nextProblem()
})
document.getElementById('change-btn').addEventListener('click', () => show(selectScreen))
@@ -61,8 +62,26 @@
function randInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min }
function nextProblem() {
const a = st.table || randInt(1, 10)
const b = randInt(1, 10)
let a, b, key;
const maxAttempts = 50;
for (let i = 0; i < maxAttempts; i++) {
a = st.table || randInt(1, 10)
b = randInt(1, 10)
// Jeśli wybrano konkretną tabelkę, kluczem jest tylko drugi składnik.
// Jeśli "wszystkie", kluczem jest posortowana para (żeby 2x3 i 3x2 były traktowane tak samo).
key = st.table ? b : [a, b].sort().join('x');
if (!st.history.includes(key) || i === maxAttempts - 1) {
st.history.push(key);
break;
}
}
if (st.history.length > (st.table ? 6 : 15)) {
st.history.shift();
}
st.current = { a, b, answer: a * b }
problemEl.textContent = `${a} × ${b}`
feedbackEl.textContent = ''
+4
View File
@@ -95,9 +95,13 @@
</div>
<div id="footer-placeholder"></div>
<footer class="app-footer" style="text-align: center; padding: 1rem; font-size: 0.8rem; color: #64748b;">
Wersja: <span id="app-version">...</span>
</footer>
<script src="js/nav.js?v=20260521"></script>
<script src="js/mnozenie.js?v=20260524"></script>
<script src="js/components.js"></script>
<script src="js/version.js"></script>
<script>
loadComponent('components/header.html', {
'{TITLE}': '× Nauka Mnożenia',
+3
View File
@@ -148,6 +148,9 @@
</div>
<div id="footer-placeholder"></div>
<footer class="app-footer" style="text-align: center; padding: 1rem; font-size: 0.8rem; color: #64748b;">
Wersja: <span id="app-version">...</span>
</footer>
<script src="js/version.js?v=20260521" defer></script>
<script src="js/nav.js?v=20260521"></script>
<script src="js/app.js?v=20260524"></script>
+12
View File
@@ -0,0 +1,12 @@
# Example properties for signing your release APK/AAB.
# DO NOT commit real passwords. Copy this to gradle.properties in your home
# directory (~/.gradle/gradle.properties) or to a secure CI secret store.
# Path to your keystore file (relative to project root or absolute path)
MYAPP_STORE_FILE=keystores/my-release-key.jks
# Your keystore password
MYAPP_STORE_PASSWORD=
# Your key alias inside the keystore
MYAPP_KEY_ALIAS=
# Your key password (often same as store password)
MYAPP_KEY_PASSWORD=