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_TOKEN=
|
||||||
GITEA_REPO=Groupe1/Ressources-relationnel
|
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}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
/public/build
|
/public/build
|
||||||
/public/hot
|
/public/hot
|
||||||
/public/storage
|
/public/storage
|
||||||
|
/public/downloads/*.apk
|
||||||
|
/public/downloads/*.ipa
|
||||||
/storage/*.key
|
/storage/*.key
|
||||||
/storage/pail
|
/storage/pail
|
||||||
/vendor
|
/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>
|
</div>
|
||||||
|
|
||||||
<nav class="flex items-center gap-2 whitespace-nowrap">
|
<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
|
@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">
|
<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
|
💡 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
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\DownloadController;
|
||||||
use App\Http\Controllers\LoginController;
|
use App\Http\Controllers\LoginController;
|
||||||
use App\Http\Controllers\ProfileController;
|
use App\Http\Controllers\ProfileController;
|
||||||
use App\Http\Controllers\SuggestionController;
|
use App\Http\Controllers\SuggestionController;
|
||||||
@@ -35,6 +36,9 @@ Route::get('/resources', function () {
|
|||||||
return view('resources');
|
return view('resources');
|
||||||
})->name('resources.index');
|
})->name('resources.index');
|
||||||
|
|
||||||
|
// Téléchargement des applications mobiles (public)
|
||||||
|
Route::get('/telecharger', [DownloadController::class, 'index'])->name('telecharger');
|
||||||
|
|
||||||
// Routes d'authentification
|
// Routes d'authentification
|
||||||
Route::get('/login', [LoginController::class, 'login'])->name('login');
|
Route::get('/login', [LoginController::class, 'login'])->name('login');
|
||||||
Route::post('/login', [LoginController::class, 'store'])->name('login.store');
|
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