youscribe_sdk (1.2.0)

Published 2026-07-28 10:35:50 +00:00 by hugo

Installation

dart pub add youscribe_sdk:1.2.0 --hosted-url=

About this package

A new Flutter package project.

YouScribe SDK - Version 1.2.0

Ce document présente le SDK YouScribe pour Flutter, conçu pour intégrer la lecture de produits YouScribe dans une application hôte. Il détaille les fonctionnalités, l'architecture, les étapes d'installation et d'intégration, ainsi que des exemples d'usage recommandés.

ATTENTION Cette documentation est en cours de rédaction et peut être sujette à des modifications.

Le SDK prend en charge :

  • l'initialisation et la configuration des accès API ;
  • l'ouverture d'un produit à partir de son productId ;
  • le téléchargement, le suivi et la gestion des fichiers locaux ;
  • la lecture PDF, EPUB et audio via un widget prêt à l'emploi ;
  • la gestion du crédit temps audio avec blocage et avertissement automatiques ;
  • la personnalisation des thèmes de lecture ;
  • l'émission d'événements techniques et de téléchargement.

Architecture

Le SDK s'articule autour de quatre points d'entrée publics :

  • YouScribeSDK pour l'initialisation et les opérations de service ;
  • YouScribeReader pour ouvrir un produit ;
  • YouScribeConfig pour la configuration ;
  • DownloadTask pour suivre un téléchargement.

Le point d'entrée principal est le singleton YouScribeSDK . L'application hôte l'initialise au démarrage, lui fournit son token, puis utilise :

  • YouScribeReader pour ouvrir un produit ;
  • download / trackDownload / watchActiveDownloads pour piloter les téléchargements.

Fonctionnalités disponibles

Lecture produit

YouScribeReader(productId: ...) encapsule le flux standard :

  1. vérification de la disponibilité locale ou distante du produit ;
  2. déclenchement du téléchargement si nécessaire ;
  3. suivi de la tâche jusqu'à disponibilité du fichier ;
  4. ouverture du lecteur adapté au type de fichier disponible : PDF, EPUB ou audio.

Téléchargement et cache local

Le SDK expose un vrai cycle de téléchargement :

  • démarrage avec download(productId: ...) ;
  • rattachement à une tâche existante avec trackDownload(productId: ...) ;
  • observation globale avec watchActiveDownloads() ;
  • annulation avec DownloadTask.cancel() ou stopDownload(productId) ;
  • récupération des fichiers locaux avec getDownloadedFiles() ;
  • suppression d'un fichier avec deleteFile(productId: ...).

Une tâche de téléchargement expose :

  • snapshot pour l'état courant ;
  • snapshotStream pour suivre l'évolution complète ;
  • progressStream pour une progression simple ;
  • completed pour attendre la disponibilité finale du fichier.

Événements SDK

Le singleton expose events , un stream d'événements émis par le SDK. En pratique, ce flux permet surtout de journaliser :

  • les événements HTTP de diagnostic ;
  • les erreurs SDK ;
  • le cycle de vie des téléchargements ;
  • le cycle de vie des readers, y compris les passages background/foreground (ReaderOpenedEvent, ReaderClosedEvent, ReaderBackgroundEvent, ReaderForegroundEvent).

Le flag enableEventDebugPrint permet de dupliquer ces événements dans debugPrint .

Installation

Le SDK est publié sur le registre Dart privé Wyatt Studio. La méthode recommandée pour l'ajouter à un projet Flutter est d'utiliser la commande suivante :

dart pub add youscribe_sdk:1.2.0 --hosted-url=https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/

Cette commande ajoute automatiquement la dépendance avec le bon registre dans votre pubspec.yaml .

Puis :

flutter pub get

Vous pourriez être amené à forcer certaines versions de dépendances:

dependency_overrides:
  youscribe_audio_player:
    hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
    version: 1.2.0
  youscribe_epub_reader:
    hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
    version: 1.2.0
  youscribe_pdf_reader:
    hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
    version: 1.2.0
  nutrient_flutter: 5.3.1
  flutter_readium:
    git:
      url: https://github.com/hugo-pcl/flutter_readium.git
      path: flutter_readium
      ref: feat/auto-scroll
  flutter_readium_platform_interface:
    git:
      url: https://github.com/hugo-pcl/flutter_readium.git
      path: flutter_readium_platform_interface
      ref: feat/auto-scroll

Configuration native Android pour la liseuse PDF

La lecture PDF repose sur le SDK Android de Nutrient. Dans une application Flutter existante, il faut donc compléter la configuration native Android de l'application hôte en plus de l'ajout de la dépendance Flutter.

Note

Les versions exactes de compileSdkVersion et minSdkVersion peuvent évoluer selon la version de Nutrient utilisée. Le guide officiel Nutrient reste la référence pour les valeurs minimales à jour : https://www.nutrient.io/sdk/flutter/getting-started/

1. Mettre à jour la configuration Gradle Android

Dans android/app/build.gradle ou android/app/build.gradle.kts , vérifiez au minimum les points suivants :

android {

	compileSdkVersion 35

	defaultConfig {
		minSdkVersion 21
	}

	compileOptions {
		sourceCompatibility JavaVersion.VERSION_17
		targetCompatibility JavaVersion.VERSION_17
	}

	kotlinOptions {
		jvmTarget = '17'
	}

}

dependencies {

	implementation 'androidx.appcompat:appcompat:<version>'

}

Si votre projet utilise la syntaxe Kotlin DSL ( build.gradle.kts ), adaptez simplement la syntaxe. L'important est d'utiliser Java 17, Kotlin JVM 17 et d'ajouter androidx.appcompat .

2. Mettre à jour le thème Android

Dans android/app/src/main/res/values/styles.xml , remplacez le parent du thème principal :

<style name="NormalTheme" parent="Theme.AppCompat.Light.NoActionBar">

par :

<style name="NormalTheme" parent="PSPDFKit.Theme.Default">

Si votre application définit aussi un thème nuit dans android/app/src/main/res/values-night/styles.xml , appliquez le même parent ou une variante compatible avec votre thème sombre.

3. Utiliser FlutterAppCompatActivity

Mettez à jour votre activité principale Android pour hériter de FlutterAppCompatActivity :

import io.flutter.embedding.android.FlutterAppCompatActivity

class MainActivity : FlutterAppCompatActivity()

Cette étape est nécessaire pour permettre à la couche Android de la liseuse PDF de s'intégrer correctement à l'activité Flutter hôte.

4. Ajouter l'icône retour Android

Copiez le fichier back_icon.xml dans :

android/app/src/main/res/drawable/back_icon.xml

Vous pouvez reprendre l'icône utilisée dans l'exemple du SDK :

<vector xmlns:android="http://schemas.android.com/apk/res/android"
	 android:width="24dp"
	 android:height="24dp"
	 android:viewportWidth="24"
	 android:viewportHeight="24">
	 <path
		 android:fillColor="#000000"
		 android:pathData="M20,11H7.83L13.42,5.41L12,4L4,12L12,20L13.41,18.59L7.83,13H20V11Z"/>
</vector>

5. Vérifier la configuration avant intégration

Avant d'ouvrir YouScribeReader , vérifiez que :

  • l'application Android compile bien avec Java 17 ;
  • androidx.appcompat est bien présent dans les dépendances de l'application hôte ;
  • MainActivity hérite de FlutterAppCompatActivity ;
  • le thème NormalTheme utilise bien PSPDFKit.Theme.Default ;
  • back_icon.xml est présent dans les resources Android.

Configuration native pour la liseuse EPUB (Readium)

La lecture EPUB utilise le moteur Readium (via flutter_readium ), qui nécessite une configuration native supplémentaire sur Android et iOS.

Android

Dans android/app/build.gradle.kts (ou build.gradle ), appliquez les changements suivants :

android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true

        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    defaultConfig {
        minSdk = 24
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}
Changement Raison
minSdk = 24 Readium nécessite Android 7.0 (API 24) minimum
isCoreLibraryDesugaringEnabled = true Requis par les dépendances Java 8+ de Readium
coreLibraryDesugaring(...) Dépendance de désugarisation nécessaire

iOS

Dans ios/Podfile , ajoutez ces lignes dans target 'Runner' do :

use_modular_headers!

pod 'ReadiumShared', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumShared.podspec'
pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumInternal.podspec'
pod 'ReadiumStreamer', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumStreamer.podspec'
pod 'ReadiumNavigator', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumNavigator.podspec'
pod 'ReadiumOPDS', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumOPDS.podspec'
pod 'ReadiumZIPFoundation', podspec: 'https://raw.githubusercontent.com/readium/podspecs/refs/heads/main/ReadiumZIPFoundation/3.0.1/ReadiumZIPFoundation.podspec'

Exemple complet :

target 'Runner' do
  use_frameworks!
  use_modular_headers!
  
  pod 'ReadiumShared', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumShared.podspec'
  pod 'ReadiumInternal', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumInternal.podspec'
  pod 'ReadiumStreamer', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumStreamer.podspec'
  pod 'ReadiumNavigator', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumNavigator.podspec'
  pod 'ReadiumOPDS', podspec: 'https://raw.githubusercontent.com/readium/swift-toolkit/3.9.0/Support/CocoaPods/ReadiumOPDS.podspec'
  pod 'ReadiumZIPFoundation', podspec: 'https://raw.githubusercontent.com/readium/podspecs/refs/heads/main/ReadiumZIPFoundation/3.0.1/ReadiumZIPFoundation.podspec'

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  target 'RunnerTests' do
    inherit! :search_paths
  end
end

Intégration dans une application Flutter

1. Initialiser le SDK au démarrage

L'initialisation doit être faite une fois avant tout accès à YouScribeSDK.instance .

import 'package:flutter/material.dart';
import 'package:youscribe_sdk/youscribe_sdk.dart';

Future<void> main() async {
	WidgetsFlutterBinding.ensureInitialized();

	await YouScribeSDK.initialize(
		config: YouScribeConfig(
			apiBaseUrl: 'https://api-mobile.youscribe.com',
			audioStreamingBaseUrl: 'https://api-streaming.youscribe.com',
			authToken: '',
			locale: const Locale('fr'),
			enableEventDebugPrint: true,
			onTokenExpired: () async {
				final refreshedToken = await refreshTokenFromHostApp();
				YouScribeSDK.instance.updateAuthToken(refreshedToken);
			},
		),
	);

	runApp(const MyApp());
}

Points importants :

  • initialize() est idempotent : après la première initialisation, les appels suivants renvoient la même instance et ne réappliquent pas une nouvelle configuration ;
  • si vous avez besoin de changer le token après login ou refresh, utilisez updateAuthToken() ;
  • si vous avez besoin de cloisonner les données locales par utilisateur, utilisez updateLocalDataDiscriminator().

2. Brancher l'authentification de l'app hôte

Le SDK ne gère pas le login. L'application hôte reste responsable de :

  • récupérer le token d'accès ;
  • le transmettre au SDK ;
  • gérer son refresh ;
  • gérer les changements d'utilisateur.

Exemple après authentification :

Future<void> onUserSignedIn({
	required String authToken,
	required String userId,
}) async {
	final sdk = YouScribeSDK.instance;
	sdk.updateAuthToken(authToken);
	sdk.updateLocalDataDiscriminator(userId);
}

3. Ouvrir un produit dans l'application

Le point d'intégration le plus simple est YouScribeReader .

Navigator.of(context).push(
	MaterialPageRoute(
		builder: (_) => const YouScribeReader(productId: '123456'),
	),
);

Ce widget gère lui-même le chargement du produit. Dans le cas standard, il n'est pas nécessaire d'appeler readProduct() à la main.

4. Personnaliser les thèmes de lecture

YouScribeReader expose deux niveaux de personnalisation :

  • theme pour définir une palette commune à toutes les liseuses ;
  • pdfReaderTheme, epubReaderTheme et audioReaderTheme pour surcharger un lecteur précis.

L'ordre de priorité est le suivant :

  1. thème spécifique au lecteur ;
  2. thème partagé YouScribeReaderTheme ;
  3. thème par défaut fourni par le SDK.

Exemple avec un thème partagé cohérent sur les trois liseuses :

final readerTheme = YouScribeReaderTheme.defaultTheme().copyWith(
	accentColor: const Color(0xFF0F766E),
	textColor: const Color(0xFF1F2937),
	backButtonLabel: 'Retour',
);

Navigator.of(context).push(
	MaterialPageRoute(
		builder: (_) => YouScribeReader(
			productId: '123456',
			theme: readerTheme,
		),
	),
);

Exemple avec une surcharge ciblée pour l'audio uniquement :

final sharedTheme = YouScribeReaderTheme.defaultTheme();

Navigator.of(context).push(
	MaterialPageRoute(
		builder: (_) => YouScribeReader(
			productId: '123456',
			theme: sharedTheme,
			audioReaderTheme: sharedTheme.toAudioReaderTheme().copyWith(
				primaryColor: const Color(0xFF7C3AED),
				previewColor: const Color(0xFFD97706),
			),
		),
	),
);

Si vous préférez piloter chaque reader séparément, les classes PdfReaderTheme , EpubReaderTheme et AudioReaderTheme sont aussi exportées par package:youscribe_sdk/youscribe_sdk.dart .

5. Personnaliser les écrans de chargement

Le SDK distingue deux étapes de chargement :

  • productLoadingBuilder pour le chargement SDK avant qu'un reader concret soit prêt ;
  • readerLoadingBuilder pour le chargement interne des readers PDF, EPUB et audio.

Ces builders permettent soit d'afficher votre propre écran, soit de masquer totalement le loading par défaut avec un widget vide.

Exemple minimal :

Navigator.of(context).push(
	MaterialPageRoute(
		builder: (_) => YouScribeReader(
			productId: '123456',
			productLoadingBuilder: (_) => const SizedBox.shrink(),
			readerLoadingBuilder: (_) => const Center(
				child: CircularProgressIndicator(),
			),
		),
	),
);

Configuration

YouScribeConfig centralise la configuration du SDK.

Paramètre Rôle Valeur par défaut
apiBaseUrl URL de base des APIs YouScribe https://api-mobile.youscribe.com
audioStreamingBaseUrl URL de base des endpoints audio https://api-streaming.youscribe.com
authToken Token transmis au backend ''
localDataDiscriminator Préfixe logique de stockage local par utilisateur/environnement 'default'
enableEventDebugPrint Duplication des événements SDK vers debugPrint true
locale Locale UI du SDK Locale('fr')
onTokenExpired Callback invoqué sur expiration de session pour refresh du token null

Le paramètre localDataDiscriminator permet de séparer les données locales entre plusieurs utilisateurs ou plusieurs environnements.

API d'usage recommandée

Ouvrir un produit

Usage recommandé pour une app classique :

await Navigator.of(context).push(
	MaterialPageRoute(
		builder: (_) => const YouScribeReader(productId: '123456'),
	),
);

Pré-télécharger un produit

Pour télécharger sans ouvrir immédiatement le reader :

final task = await YouScribeSDK.instance.download(productId: '123456');

task.snapshotStream.listen((snapshot) {
	debugPrint(
		'status=${snapshot.status} progress=${snapshot.progress}',
	);
});

final file = await task.completed;
debugPrint('Local file ready: ${file.metadata.uri}');

Pour un téléchargement temporaire :

await YouScribeSDK.instance.download(
	productId: '123456',
	temporary: true,
);

Reprendre le suivi d'un téléchargement en cours

final task = await YouScribeSDK.instance.trackDownload(productId: '123456');

if (task != null) {
	task.snapshotStream.listen((snapshot) {
		debugPrint('status=${snapshot.status}');
	});
}

Observer tous les téléchargements actifs

YouScribeSDK.instance.watchActiveDownloads().listen((downloads) {
	for (final download in downloads) {
		debugPrint(
			'product=${download.productId} status=${download.status} progress=${download.progress}',
		);
	}
});

Vérifier la disponibilité locale d'un fichier

final isAvailable = await YouScribeSDK.instance.isFileAvailable(
	productId: '123456',
);

Lister et supprimer les fichiers téléchargés

final files = await YouScribeSDK.instance.getDownloadedFiles();

for (final file in files) {
	debugPrint('${file.product.id} -> ${file.metadata.uri}');
}

await YouScribeSDK.instance.deleteFile(productId: '123456');

Hooks de lecture PDF

YouScribeReader accepte pdfReadingHooks pour étendre le comportement standard.

Exemple :

YouScribeReader(
	productId: '123456',
	pdfReadingHooks: PdfReadingHooks(
		onOpenBook: ({required pdf, required pageCount, required pageIndex}) {
			debugPrint('Opened ${pdf.productId} at page $pageIndex / $pageCount');
		},
		onPageChanged: ({
			required pdf,
			required oldPageIndex,
			required newPageIndex,
			required pageCount,
		}) {
			debugPrint('Page changed: $oldPageIndex -> $newPageIndex');
		},
		onError: ({pdf, required errorMessage}) {
			debugPrint('Reader error: $errorMessage');
		},
		onReaderBackground: ({required pdf}) {
			debugPrint('App backgrounded while reading ${pdf.productId}');
		},
		onReaderForeground: ({required pdf, required backgroundDuration}) {
			debugPrint(
				'App foregrounded after ${backgroundDuration.inSeconds}s away from ${pdf.productId}',
			);
		},
	),
)

onReaderBackground / onReaderForeground existent aussi sur EpubReadingHooks (paramètre book ) et AudioReadingHooks (paramètre book ), avec la même signature. Ils sont déclenchés automatiquement par YouScribeReader via un WidgetsBindingObserver interne : dès que l'app passe en arrière-plan ( AppLifecycleState.paused ) ou en revient ( AppLifecycleState.resumed ) pendant qu'un reader est ouvert, le hook du reader actif est appelé. backgroundDuration indique le temps passé en arrière-plan.

Gestion du crédit temps audio

Le SDK vérifie automatiquement le crédit temps de l'utilisateur pour les audiobooks en streaming ( remoteToStream ).

Comportement

Étape Action
Ouverture du reader Le SDK appelle GET /v1/time-credit/consumption via ProductFileRepository . Si le crédit est épuisé, une TimeCreditExhaustedError est levée et le reader affiche un dialog bloquant.
Warning à l'ouverture Si le seuil d'avertissement est atteint (80% du crédit consommé par défaut), un dialog d'information s'affiche sans bloquer la lecture.
Reprise après >1h de pause Lorsque l'utilisateur appuie sur play après une pause d'au moins une heure, le crédit est revérifié. Si épuisé → dialog bloquant. Si seuil d'avertissement dépassé → dialog d'information.
Échec réseau Le SDK échoue en mode fail-open : si l'API de crédit temps est indisponible, la lecture est autorisée.

Modèle

class TimeCreditConsumption extends Equatable {
  final Duration? duration;          // Crédit total
  final Duration? used;              // Temps déjà consommé
  final Duration? remaining;         // Temps restant
  final bool hasInfiniteCredit;      // Abonnement illimité ?
  final String? messageToDisplay;    // Message personnalisé du backend
  final int? counterResetDays;       // Jours avant réinitialisation
  final String? nextPeriod;          // Prochaine période de facturation
  bool get isExhausted;              // remaining <= 0 && !hasInfiniteCredit
}

Seuil d'avertissement configurable

Par défaut, le warning s'affiche lorsque 80% du crédit est consommé. Vous pouvez modifier ce seuil via AudioReaderConfiguration.warningCalculator :

AudioReaderConfiguration(
  warningCalculator: (TimeCreditConsumption c) {
    // Afficher le warning quand il reste moins de 10 minutes
    if (c.remaining == null || c.duration == null) return false;
    return c.remaining!.inMinutes < 10;
  },
)

Si aucun warning n'est souhaité :

AudioReaderConfiguration(
  warningCalculator: (_) => false,
)

Callback crédit épuisé

Vous pouvez réagir à l'épuisement du crédit via le callback onTimeCreditExhausted d' AudioReaderCallbacks :

AudioReaderCallbacks(
  onTimeCreditExhausted: (AudioBook book) {
    // Rediriger vers la page d'abonnement, journaliser, etc.
  },
)

Erreur spécifique

Lorsque le crédit est épuisé au moment de l'ouverture du produit, TimeCreditExhaustedError (étendant YouScribeError ) est levée automatiquement par ProductFileRepository :

try {
  await YouScribeSDK.instance.readProduct('123456');
} on TimeCreditExhaustedError catch (e) {
  // Crédit épuisé — proposer un abonnement
} catch (error) {
  // Autre erreur
}

Gestion des erreurs

Les opérations du SDK peuvent échouer pour des raisons réseau, d'authentification, de téléchargement ou de lecture.

Dans une app hôte, le plus simple est de :

  • entourer les appels réseau/lecture par un try/catch ;
  • afficher un message utilisateur adapté ;
  • journaliser l'erreur ;
  • s'appuyer sur onTokenExpired pour le refresh de session.

Exemple :

try {
	await YouScribeSDK.instance.readProduct('123456');
} catch (error) {
	debugPrint('SDK error: $error');
}

Écouter les événements du SDK

final subscription = YouScribeSDK.instance.events.listen((event) {
	debugPrint('SDK event: $event');
});

Ce flux est particulièrement utile pour :

  • suivre les téléchargements ;
  • observer les erreurs centralisées ;
  • diagnostiquer les échanges HTTP en environnement de développement ;
  • mesurer le temps passé par l'utilisateur en arrière-plan pendant une session de lecture (ReaderBackgroundEvent / ReaderForegroundEvent).

Cycle de vie conseillé dans l'app hôte

Flux recommandé :

  1. initialiser le SDK au lancement de l'app ;
  2. injecter ou mettre à jour le token après login ;
  3. mettre à jour le localDataDiscriminator quand l'utilisateur change ;
  4. ouvrir les produits via YouScribeReader ;
  5. utiliser download() pour le préchargement hors ligne ;
  6. appeler dispose() si vous voulez libérer explicitement le singleton.

YouScribeReader gère nativement les transitions background/foreground de l'app pendant qu'un reader est ouvert (via un WidgetsBindingObserver interne) : l'app hôte n'a rien à faire de spécial pour cela, elle peut simplement écouter pdfReadingHooks.onReaderBackground / onReaderForeground (ou les équivalents Epub/Audio), ou le stream events , si elle a besoin de réagir à ces transitions.

Details
Pub
2026-07-28 10:35:50 +00:00
4
190 KiB
Assets (1)
1.2.0.tar.gz 190 KiB
Versions (2) View all
1.2.0 2026-07-28
1.1.0 2026-05-28