This commit is contained in:
Renan LE CARO 2025-04-20 10:58:26 +02:00
parent 70f3c2307a
commit 1ba55bf2a6
13 changed files with 122 additions and 114 deletions

View file

@ -23,7 +23,8 @@ Other translation are very welcome, contact me if you'd like to submit one.
# Changelog # Changelog
## To do ## To do
- redo video presentation - in apk, video download doesnt work
-
## Done ## Done

View file

@ -29,8 +29,8 @@ android {
applicationId = "me.lecaro.breakout" applicationId = "me.lecaro.breakout"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 29085573 versionCode = 29085612
versionName = "29085573" versionName = "29085612"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true
@ -67,3 +67,6 @@ android {
} }
} }
} }
dependencies {
implementation(libs.androidx.core)
}

View file

@ -23,6 +23,14 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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" />
</provider>
</application> </application>
</manifest> </manifest>

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,7 @@
package me.lecaro.breakout package me.lecaro.breakout
import android.app.Activity
import android.app.DownloadManager
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -20,13 +16,12 @@ import android.webkit.ValueCallback
import android.webkit.WebChromeClient import android.webkit.WebChromeClient
import android.webkit.WebView import android.webkit.WebView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.FileProvider
import java.io.File import java.io.File
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.jar.Manifest
const val CHOOSE_FILE_REQUEST_CODE = 548459 const val CHOOSE_FILE_REQUEST_CODE = 548459
@ -60,9 +55,9 @@ class MainActivity : android.app.Activity() {
val sdf = SimpleDateFormat("yyyy-M-dd-hh-mm") val sdf = SimpleDateFormat("yyyy-M-dd-hh-mm")
val currentDate = sdf.format(Date()) val currentDate = sdf.format(Date())
val urlEncoded = url.substring("data:application/json;charset=utf-8,".length) val urlEncoded = url.substring("data:application/json;charset=utf-8,".length)
val str =URLDecoder.decode(urlEncoded, StandardCharsets.UTF_8.name()) val str = URLDecoder.decode(urlEncoded, StandardCharsets.UTF_8.name())
writeFile(str, "breakout-71-save-$currentDate.json", "application/json") writeFileAndShare(str, "breakout-71-save-$currentDate.json", "application/json")
} catch (e: Exception) { } catch (e: Exception) {
Log.e("DL", "Error ${e.message}") Log.e("DL", "Error ${e.message}")
@ -70,41 +65,52 @@ class MainActivity : android.app.Activity() {
} }
} }
fun writeFile(jsonData:String,fileName:String, mime:String){ fun writeFileAndShare(jsonData: String, fileName: String, mime: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// android 10 // android 10
val contentValues = ContentValues().apply { val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, fileName) put(MediaStore.Downloads.DISPLAY_NAME, fileName)
put(MediaStore.Downloads.MIME_TYPE,mime ) put(MediaStore.Downloads.MIME_TYPE, mime)
put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
} }
val uri: Uri? = contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues
)
uri?.let {
contentResolver.openOutputStream(it)?.use { outputStream ->
outputStream.write(jsonData.toByteArray())
}
}
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = mime
}
startActivity(Intent.createChooser(shareIntent, null))
} else {
val dir = getExternalFilesDir(null)
val file = File(dir, fileName)
file.writeText(jsonData)
Toast.makeText(this, "Saved in $dir", Toast.LENGTH_LONG).show()
val uri: Uri? = contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues
)
uri?.let {
contentResolver.openOutputStream(it)?.use { outputStream ->
outputStream.write(jsonData.toByteArray())
} }
}
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = mime
}
startActivity(Intent.createChooser(shareIntent, null))
} else {
val file = File(getExternalFilesDir(null), fileName)
file.writeText(jsonData)
val uri = FileProvider.getUriForFile(
this,
"$packageName.fileprovider", // Adjust if your authority is different
file
)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = mime
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
startActivity(Intent.createChooser(shareIntent, null))
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="external_files"
path="." />
</paths>

49
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -8,6 +8,7 @@ espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1" lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.7.0" activityCompose = "1.7.0"
composeBom = "2023.08.00" composeBom = "2023.08.00"
core = "1.13.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -24,6 +25,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-core = { group = "androidx.core", name = "core", version.ref = "core" }
[plugins] [plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" } androidApplication = { id = "com.android.application", version.ref = "agp" }

View file

@ -1,5 +1,5 @@
// The version of the cache. // The version of the cache.
const VERSION = "29085573"; const VERSION = "29085612";
// The name of the cache // The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`; const CACHE_NAME = `breakout-71-${VERSION}`;

View file

@ -1 +1 @@
"29085573" "29085612"

View file

@ -23,6 +23,7 @@ import {
describeLevel, describeLevel,
getRowColIndex, getRowColIndex,
highScoreText, highScoreText,
isInWebView,
levelsListHTMl, levelsListHTMl,
max_levels, max_levels,
pickedUpgradesHTMl, pickedUpgradesHTMl,
@ -36,7 +37,7 @@ import { getCurrentLang, languages, t } from "./i18n/i18n";
import { import {
commitSettingsChangesToLocalStorage, commitSettingsChangesToLocalStorage,
cycleMaxCoins, cycleMaxCoins,
getCurrentMaxCoins, getCurrentMaxCoins, getCurrentMaxParticles,
getSettingValue, getSettingValue,
getTotalScore, getTotalScore,
setSettingValue, setSettingValue,
@ -113,7 +114,12 @@ export async function play() {
export function pause(playerAskedForPause: boolean) { export function pause(playerAskedForPause: boolean) {
if (!gameState.running) return; if (!gameState.running) return;
if (gameState.pauseTimeout) return; if (gameState.pauseTimeout) return;
if (gameState.startParams.computer_controlled) return; if (gameState.startParams.computer_controlled) {
if (gameState.startParams?.computer_controlled) {
play();
}
return;
}
const stop = () => { const stop = () => {
gameState.running = false; gameState.running = false;
@ -475,17 +481,12 @@ export function tick() {
FPSCounter++; FPSCounter++;
} }
let FPSCounter = 0;
export let lastMeasuredFPS = 60;
setInterval(() => {
lastMeasuredFPS = FPSCounter;
FPSCounter = 0;
}, 1000);
const stats = document.getElementById("stats") as HTMLDivElement; const stats = document.getElementById("stats") as HTMLDivElement;
let total = {}; let total = {};
let lastTick = performance.now(); let lastTick = performance.now();
let doing = "idle"; let doing = "idle";
let FPSCounter = 0;
export let lastMeasuredFPS = 60;
export function startWork(what) { export function startWork(what) {
if (!gameState.startParams.stress) return; if (!gameState.startParams.stress) return;
const newNow = performance.now(); const newNow = performance.now();
@ -496,6 +497,10 @@ export function startWork(what) {
doing = what; doing = what;
} }
setInterval(() => { setInterval(() => {
lastMeasuredFPS = FPSCounter;
FPSCounter = 0;
if (!gameState.startParams.stress) { if (!gameState.startParams.stress) {
stats.style.display = "none"; stats.style.display = "none";
return; return;
@ -506,12 +511,14 @@ setInterval(() => {
stats.innerHTML = stats.innerHTML =
` `
<div> <div>
<strong>Coins ${liveCount(gameState.coins)} / ${getCurrentMaxCoins()} - Particles : ${liveCount(gameState.particles) + liveCount(gameState.lights) + liveCount(gameState.texts)}</strong> ${lastMeasuredFPS} FPS -
${liveCount(gameState.coins)} / ${getCurrentMaxCoins()} Coins -
${liveCount(gameState.particles) + liveCount(gameState.lights) + liveCount(gameState.texts)} / ${getCurrentMaxParticles()*3} particles
</div> </div>
` + ` +
Object.entries(total) Object.entries(total)
// .sort((a, b) => b[1] - a[1]) // .sort((a, b) => b[1] - a[1])
@ -519,7 +526,7 @@ setInterval(() => {
(t) => (t) =>
` <div> ` <div>
<div style="transform: scale(${clamp(t[1] / totalTime, 0, 1)},1)"></div> <div style="transform: scale(${clamp(t[1] / totalTime, 0, 1)},1)"></div>
<strong>${t[0]} : ${t[1]} ms</strong> <strong>${t[0]} : ${Math.floor(t[1])} ms</strong>
</div> </div>
`, `,
) )
@ -694,6 +701,7 @@ async function openSettingsMenu() {
"precise_lighting", "precise_lighting",
"probabilistic_lighting", "probabilistic_lighting",
].includes(key)) || ].includes(key)) ||
(isInWebView && key == "record") ||
false, false,
value: () => { value: () => {
toggleOption(key); toggleOption(key);

View file

@ -421,3 +421,5 @@ export function getCornerOffset(gameState: GameState) {
gameState.perks.unbounded * gameState.brickWidth gameState.perks.unbounded * gameState.brickWidth
); );
} }
export const isInWebView = !!window.location.href.includes("isInWebView=true");

View file

@ -1,5 +1,5 @@
import { gameCanvas } from "./render"; import { gameCanvas } from "./render";
import { max_levels } from "./game_utils"; import { isInWebView, max_levels } from "./game_utils";
import { getAudioRecordingTrack } from "./sounds"; import { getAudioRecordingTrack } from "./sounds";
import { t } from "./i18n/i18n"; import { t } from "./i18n/i18n";
import { GameState } from "./types"; import { GameState } from "./types";
@ -12,7 +12,7 @@ let mediaRecorder: MediaRecorder | null,
recordCanvasCtx: CanvasRenderingContext2D; recordCanvasCtx: CanvasRenderingContext2D;
export function recordOneFrame(gameState: GameState) { export function recordOneFrame(gameState: GameState) {
if (!isOptionOn("record")) { if (!isOptionOn("record") || isInWebView) {
return; return;
} }
// if (!gameState.running) return; // if (!gameState.running) return;
@ -59,7 +59,7 @@ export function drawMainCanvasOnSmallCanvas(gameState: GameState) {
} }
export function startRecordingGame(gameState: GameState) { export function startRecordingGame(gameState: GameState) {
if (!isOptionOn("record")) { if (!isOptionOn("record") || isInWebView) {
return; return;
} }
if (mediaRecorder) return; if (mediaRecorder) return;
@ -124,11 +124,8 @@ export function startRecordingGame(gameState: GameState) {
const a = document.createElement("a"); const a = document.createElement("a");
a.download = captureFileName("webm"); a.download = captureFileName("webm");
a.target = "_blank"; a.target = "_blank";
if (window.location.href.endsWith("index.html?isInWebView=true")) {
a.href = await blobToBase64(blob); a.href = video.src;
} else {
a.href = video.src;
}
a.textContent = t("settings.record_download", { a.textContent = t("settings.record_download", {
size: (blob.size / 1000000).toFixed(2), size: (blob.size / 1000000).toFixed(2),
@ -137,22 +134,6 @@ export function startRecordingGame(gameState: GameState) {
}; };
} }
function blobToBase64(blob: Blob): Promise<string> {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = function () {
resolve(reader.result);
};
reader.onerror = function (e) {
console.error(e);
reject(new Error("Failed to readAsDataURL of the video "));
};
reader.readAsDataURL(blob);
});
}
export function pauseRecording() { export function pauseRecording() {
if (!isOptionOn("record")) { if (!isOptionOn("record")) {
return; return;