feat: page publique de téléchargement des applications mobiles
Page /telecharger avec deux cartes (Android / iOS). Pour chaque plateforme, utilise en priorité un lien de store (MOBILE_*_URL) sinon le binaire déposé dans public/downloads/ (.apk / .ipa), et affiche "Bientôt disponible" à défaut. - config/mobile.php + .env.example (URLs/versions) - DownloadController + vue telecharger + lien dans le header (public) - public/downloads/ (README) ; binaires ignorés par git - tests: accès public, lien store prioritaire, indisponibilité Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -76,4 +76,12 @@ GITEA_URL=https://gitea.sam-coffre.duckdns.org
|
||||
GITEA_TOKEN=
|
||||
GITEA_REPO=Groupe1/Ressources-relationnel
|
||||
|
||||
# --- Applications mobiles (page /telecharger) ---
|
||||
# Laisser vide pour servir les fichiers de public/downloads/ (.apk / .ipa),
|
||||
# ou renseigner un lien de store / TestFlight (prioritaire sur le fichier local).
|
||||
MOBILE_ANDROID_URL=
|
||||
MOBILE_IOS_URL=
|
||||
MOBILE_ANDROID_VERSION=1.0.0
|
||||
MOBILE_IOS_VERSION=1.0.0
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/downloads/*.apk
|
||||
/public/downloads/*.ipa
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
class DownloadController extends Controller
|
||||
{
|
||||
// Page publique de téléchargement des applications mobiles
|
||||
public function index()
|
||||
{
|
||||
return view('telecharger', [
|
||||
'android' => $this->resoudre('android'),
|
||||
'ios' => $this->resoudre('ios'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine, pour une plateforme, le lien de téléchargement et sa disponibilité.
|
||||
*
|
||||
* @return array{version: string, url: string|null, disponible: bool}
|
||||
*/
|
||||
private function resoudre(string $plateforme): array
|
||||
{
|
||||
$config = config("mobile.{$plateforme}");
|
||||
if (! is_array($config)) {
|
||||
$config = [];
|
||||
}
|
||||
|
||||
$version = is_string($config['version'] ?? null) ? $config['version'] : '1.0.0';
|
||||
$url = (is_string($config['url'] ?? null) && $config['url'] !== '') ? $config['url'] : null;
|
||||
$file = is_string($config['file'] ?? null) ? $config['file'] : null;
|
||||
|
||||
// À défaut d'URL explicite, on propose le fichier précompilé s'il existe.
|
||||
if ($url === null && $file !== null && file_exists(public_path($file))) {
|
||||
$url = asset($file);
|
||||
}
|
||||
|
||||
return [
|
||||
'version' => $version,
|
||||
'url' => $url,
|
||||
'disponible' => $url !== null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Applications mobiles (téléchargement)
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Pour chaque plateforme, la page de téléchargement utilise en priorité
|
||||
| l'URL fournie (lien de store OU URL directe). À défaut, elle propose le
|
||||
| fichier précompilé déposé dans public/downloads/. Si aucun des deux n'est
|
||||
| disponible, le bouton affiche « bientôt disponible ».
|
||||
|
|
||||
*/
|
||||
|
||||
'android' => [
|
||||
'version' => env('MOBILE_ANDROID_VERSION', '1.0.0'),
|
||||
'url' => env('MOBILE_ANDROID_URL'),
|
||||
'file' => 'downloads/ressources-relationnelles.apk',
|
||||
],
|
||||
|
||||
'ios' => [
|
||||
'version' => env('MOBILE_IOS_VERSION', '1.0.0'),
|
||||
'url' => env('MOBILE_IOS_URL'),
|
||||
'file' => 'downloads/ressources-relationnelles.ipa',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
# Applications mobiles précompilées
|
||||
|
||||
Déposez ici les binaires servis par la page **/telecharger** :
|
||||
|
||||
- `ressources-relationnelles.apk` — application Android
|
||||
- `ressources-relationnelles.ipa` — application Apple (iOS)
|
||||
|
||||
La page détecte automatiquement la présence de ces fichiers.
|
||||
|
||||
## Alternative : liens de store
|
||||
|
||||
Plutôt que des fichiers locaux, vous pouvez renseigner des liens (Play Store,
|
||||
App Store, TestFlight…) dans le fichier `.env` du serveur :
|
||||
|
||||
```
|
||||
MOBILE_ANDROID_URL=https://play.google.com/store/apps/details?id=...
|
||||
MOBILE_IOS_URL=https://apps.apple.com/app/id...
|
||||
MOBILE_ANDROID_VERSION=1.0.0
|
||||
MOBILE_IOS_VERSION=1.0.0
|
||||
```
|
||||
|
||||
Si une URL est renseignée, elle est prioritaire sur le fichier local.
|
||||
Si ni URL ni fichier ne sont disponibles, le bouton affiche « Bientôt disponible ».
|
||||
|
||||
> Les binaires (`*.apk`, `*.ipa`) ne sont pas versionnés dans git (voir `.gitignore`).
|
||||
@@ -23,6 +23,9 @@
|
||||
</div>
|
||||
|
||||
<nav class="flex items-center gap-2 whitespace-nowrap">
|
||||
<a href="{{ route('telecharger') }}" class="hidden lg:inline-flex items-center px-4 py-2.5 text-sm font-bold text-gray-700 rounded-lg hover:bg-gray-100 transition-smooth duration-200">
|
||||
📱 Télécharger l'app
|
||||
</a>
|
||||
@auth
|
||||
<a href="{{ route('suggestions.create') }}" class="hidden sm:inline-flex items-center px-4 py-2.5 text-sm font-bold text-niagara border-2 border-niagara rounded-lg hover:bg-niagara hover:text-white transition-smooth duration-200">
|
||||
💡 Proposer une amélioration
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
@extends('app')
|
||||
|
||||
@section('title', "Télécharger l'application - (RE)Sources Relationnelles")
|
||||
|
||||
@section('content')
|
||||
<section class="container mx-auto px-4 py-12">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<!-- En-tête -->
|
||||
<div class="text-center mb-10">
|
||||
<h1 class="text-4xl font-bold text-gray-900 mb-3">📱 Télécharger l'application</h1>
|
||||
<p class="text-gray-600 max-w-xl mx-auto">
|
||||
Emportez (RE)Sources Relationnelles partout avec vous. Choisissez votre plateforme
|
||||
et installez l'application mobile.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
|
||||
{{-- Carte Android --}}
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-8 flex flex-col items-center text-center shadow-sm">
|
||||
<div class="w-16 h-16 rounded-2xl bg-green-50 flex items-center justify-center mb-4">
|
||||
<span class="text-4xl">🤖</span>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-1">Android</h2>
|
||||
<p class="text-sm text-gray-500 mb-6">Version {{ $android['version'] }}</p>
|
||||
|
||||
@if ($android['disponible'])
|
||||
<a href="{{ $android['url'] }}"
|
||||
@if (str_starts_with($android['url'], url('/'))) download @else target="_blank" rel="noopener" @endif
|
||||
class="w-full px-6 py-3 gradient-niagara text-white font-bold rounded-lg hover:shadow-lg transition-smooth duration-200">
|
||||
⬇️ Télécharger (.apk)
|
||||
</a>
|
||||
<p class="text-xs text-gray-400 mt-3">
|
||||
Sur Android, autorisez l'installation depuis « sources inconnues » si nécessaire.
|
||||
</p>
|
||||
@else
|
||||
<button type="button" disabled
|
||||
class="w-full px-6 py-3 bg-gray-200 text-gray-500 font-bold rounded-lg cursor-not-allowed">
|
||||
Bientôt disponible
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Carte iOS --}}
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-8 flex flex-col items-center text-center shadow-sm">
|
||||
<div class="w-16 h-16 rounded-2xl bg-gray-100 flex items-center justify-center mb-4">
|
||||
<span class="text-4xl"></span>
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-1">Apple (iOS)</h2>
|
||||
<p class="text-sm text-gray-500 mb-6">Version {{ $ios['version'] }}</p>
|
||||
|
||||
@if ($ios['disponible'])
|
||||
<a href="{{ $ios['url'] }}"
|
||||
@if (str_starts_with($ios['url'], url('/'))) download @else target="_blank" rel="noopener" @endif
|
||||
class="w-full px-6 py-3 bg-gray-900 text-white font-bold rounded-lg hover:bg-black transition-smooth duration-200">
|
||||
⬇️ Télécharger
|
||||
</a>
|
||||
<p class="text-xs text-gray-400 mt-3">
|
||||
L'installation d'un fichier .ipa nécessite TestFlight ou un profil adapté.
|
||||
</p>
|
||||
@else
|
||||
<button type="button" disabled
|
||||
class="w-full px-6 py-3 bg-gray-200 text-gray-500 font-bold rounded-lg cursor-not-allowed">
|
||||
Bientôt disponible
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-10 text-center">
|
||||
<a href="{{ route('home') }}" class="text-sm font-semibold text-niagara hover:underline">
|
||||
← Retour à l'accueil
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\DownloadController;
|
||||
use App\Http\Controllers\LoginController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\SuggestionController;
|
||||
@@ -35,6 +36,9 @@ Route::get('/resources', function () {
|
||||
return view('resources');
|
||||
})->name('resources.index');
|
||||
|
||||
// Téléchargement des applications mobiles (public)
|
||||
Route::get('/telecharger', [DownloadController::class, 'index'])->name('telecharger');
|
||||
|
||||
// Routes d'authentification
|
||||
Route::get('/login', [LoginController::class, 'login'])->name('login');
|
||||
Route::post('/login', [LoginController::class, 'store'])->name('login.store');
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
class TelechargementTest extends TestCase
|
||||
{
|
||||
public function test_la_page_de_telechargement_est_accessible_publiquement(): void
|
||||
{
|
||||
$response = $this->get('/telecharger');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Android');
|
||||
$response->assertSee('iOS');
|
||||
}
|
||||
|
||||
public function test_un_lien_de_store_configure_est_utilise(): void
|
||||
{
|
||||
config(['mobile.android.url' => 'https://play.google.com/store/apps/details?id=test']);
|
||||
|
||||
$response = $this->get('/telecharger');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('play.google.com/store/apps/details?id=test');
|
||||
}
|
||||
|
||||
public function test_sans_binaire_ni_lien_le_bouton_est_indisponible(): void
|
||||
{
|
||||
config(['mobile.ios.url' => null]);
|
||||
|
||||
$response = $this->get('/telecharger');
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('Bientôt disponible');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user