Files for version 1.0
6
story-player/.vscode/launch.json
vendored
|
|
@ -5,18 +5,18 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "flutter_launcher",
|
"name": "story_player",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart"
|
"type": "dart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flutter_launcher (profile mode)",
|
"name": "story_player (profile mode)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"flutterMode": "profile"
|
"flutterMode": "profile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flutter_launcher (release mode)",
|
"name": "story_player (release mode)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"flutterMode": "release"
|
"flutterMode": "release"
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,4 @@
|
||||||
# flutter_launcher
|
# Story Player
|
||||||
|
|
||||||
A new Flutter project.
|
See /docs or website to learn more.
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
This project is a starting point for a Flutter application.
|
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
|
||||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
|
||||||
|
|
||||||
For help getting started with Flutter development, view the
|
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
|
||||||
samples, guidance on mobile development, and a full API reference.
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "com.example.flutter_launcher"
|
namespace "org.openstoryteller.story_player"
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.example.flutter_launcher"
|
applicationId "org.openstoryteller.story_player"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
minSdkVersion flutter.minSdkVersion
|
minSdkVersion flutter.minSdkVersion
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="ANDROID.PERMISSION.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||||
|
|
||||||
<!-- allows startup after boot -->
|
<!-- allows startup after boot -->
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="flutter_launcher"
|
android:label="Story Player"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.example.flutter_launcher
|
package org.openstoryteller.story_player
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 9 KiB |
|
After Width: | Height: | Size: 9 KiB |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
102
story-player/lib/httpserver.dart
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'package:shelf/shelf_io.dart' as io;
|
||||||
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:http_parser/http_parser.dart'; // y'a un warning mais il faut laisser cet import
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
|
||||||
|
|
||||||
|
|
||||||
|
void httpServer() async {
|
||||||
|
final router = Router();
|
||||||
|
|
||||||
|
// Route pour uploader des fichiers
|
||||||
|
router.post('/upload', (Request request) async {
|
||||||
|
var contentType = MediaType.parse(request.headers['content-type']!);
|
||||||
|
var boundary = contentType.parameters['boundary']!;
|
||||||
|
var transformer = MimeMultipartTransformer(boundary);
|
||||||
|
var bodyStream = request.read();
|
||||||
|
var parts = await transformer.bind(bodyStream).toList();
|
||||||
|
|
||||||
|
for (var part in parts) {
|
||||||
|
var contentDisp = part.headers['content-disposition']!;
|
||||||
|
var filename = _extractFilename(contentDisp);
|
||||||
|
if (filename != null) {
|
||||||
|
var filePath = p.join('uploads', filename);
|
||||||
|
await _saveFile(part, filePath);
|
||||||
|
logger.d('File uploaded: $filePath');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok('File uploaded successfully');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route pour lister les fichiers
|
||||||
|
router.get('/files', (Request request) async {
|
||||||
|
final directory = Directory('uploads');
|
||||||
|
final files =
|
||||||
|
directory.listSync().map((file) => p.basename(file.path)).toList();
|
||||||
|
final json = {'files': files};
|
||||||
|
return Response.ok(jsonEncode(json),
|
||||||
|
headers: {'Content-Type': 'application/json'});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Démarrer le serveur
|
||||||
|
io.serve(router, 'localhost', 8080).then((server) {
|
||||||
|
logger.d('Serving at http://${server.address.host}:${server.port}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _extractFilename(String contentDisposition) {
|
||||||
|
return RegExp('filename="([^"]*)"').firstMatch(contentDisposition)?.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveFile(MimeMultipart part, String filename) async {
|
||||||
|
var file = File(filename);
|
||||||
|
await file.create(recursive: true);
|
||||||
|
await part.pipe(file.openWrite());
|
||||||
|
}
|
||||||
|
|
||||||
|
void udpServer() async {
|
||||||
|
// Créer un socket UDP
|
||||||
|
var port = 8080; // Vous pouvez spécifier le port de votre choix
|
||||||
|
var address =
|
||||||
|
InternetAddress.anyIPv4; // Écouter sur toutes les interfaces IPv4
|
||||||
|
RawDatagramSocket socket = await RawDatagramSocket.bind(address, port);
|
||||||
|
logger.d('Serveur UDP écoutant sur ${address.address}:$port');
|
||||||
|
|
||||||
|
// Gérer les événements du socket
|
||||||
|
socket.listen((RawSocketEvent event) {
|
||||||
|
if (event == RawSocketEvent.read) {
|
||||||
|
Datagram? datagram = socket.receive();
|
||||||
|
if (datagram != null) {
|
||||||
|
var message = utf8.decode(datagram.data);
|
||||||
|
logger.d(
|
||||||
|
'Message reçu de ${datagram.address.address}:${datagram.port}: $message');
|
||||||
|
|
||||||
|
// Envoyer une réponse
|
||||||
|
String response = 'Reçu: $message';
|
||||||
|
List<int> data = utf8.encode(response);
|
||||||
|
socket.send(data, datagram.address, datagram.port);
|
||||||
|
logger.d(
|
||||||
|
'Réponse envoyée à ${datagram.address.address}:${datagram.port}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future printIps() async {
|
||||||
|
for (var interface in await NetworkInterface.list()) {
|
||||||
|
logger.d('== Interface: ${interface.name} ==');
|
||||||
|
for (var addr in interface.addresses) {
|
||||||
|
logger.d(
|
||||||
|
'${addr.address} ${addr.host} ${addr.isLoopback} ${addr.rawAddress} ${addr.type.name}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
|
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
|
||||||
|
|
||||||
|
|
@ -117,14 +118,33 @@ class IndexFile {
|
||||||
Future<bool> loadIndexFile(String libraryRoot) async {
|
Future<bool> loadIndexFile(String libraryRoot) async {
|
||||||
libraryPath = libraryRoot;
|
libraryPath = libraryRoot;
|
||||||
indexFileIsValid = false;
|
indexFileIsValid = false;
|
||||||
final file = File('$libraryRoot/index.ost');
|
|
||||||
if (!await file.exists()) {
|
String indexFileName = '$libraryRoot/index.ost';
|
||||||
logger.d('Le fichier n\'existe pas.');
|
|
||||||
return false;
|
bool isGranted = true;
|
||||||
|
|
||||||
|
// if (Platform.isAndroid) {
|
||||||
|
|
||||||
|
// if (await Permission.manageExternalStorage.request().isGranted) {
|
||||||
|
// isGranted = true;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// isGranted = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (isGranted) {
|
||||||
|
final file = File(indexFileName);
|
||||||
|
if (!await file.exists()) {
|
||||||
|
logger.d('Le fichier n\'existe pas.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
indexFileBuffer = file.readAsBytesSync();
|
||||||
|
} else {
|
||||||
|
logger.e("Cannot access to file: $indexFileName");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ouvrir le fichier en mode lecture binaire
|
// Ouvrir le fichier en mode lecture binaire
|
||||||
indexFileBuffer = file.readAsBytesSync();
|
|
||||||
readPtr = 0;
|
readPtr = 0;
|
||||||
indexFileStream =
|
indexFileStream =
|
||||||
ByteData.sublistView(indexFileBuffer, readPtr); // start at zero
|
ByteData.sublistView(indexFileBuffer, readPtr); // start at zero
|
||||||
|
|
|
||||||
|
|
@ -3,113 +3,35 @@ import 'package:flutter/material.dart' hide Router;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:shelf/shelf.dart';
|
|
||||||
import 'package:shelf/shelf_io.dart' as io;
|
|
||||||
import 'package:shelf_router/shelf_router.dart';
|
|
||||||
import 'package:mime/mime.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:http_parser/http_parser.dart'; // y'a un warning mais il faut laisser cet import
|
|
||||||
import 'package:saf/saf.dart';
|
import 'package:saf/saf.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:external_path/external_path.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
|
||||||
import 'libstory/storyvm.dart';
|
import 'libstory/storyvm.dart';
|
||||||
import 'libstory/indexfile.dart';
|
import 'libstory/indexfile.dart';
|
||||||
|
import 'httpserver.dart';
|
||||||
|
|
||||||
import 'package:logger/logger.dart';
|
class ProductionFilter extends LogFilter {
|
||||||
|
@override
|
||||||
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
|
bool shouldLog(LogEvent event) {
|
||||||
|
return true;
|
||||||
void httpServer() async {
|
|
||||||
final router = Router();
|
|
||||||
|
|
||||||
// Route pour uploader des fichiers
|
|
||||||
router.post('/upload', (Request request) async {
|
|
||||||
var contentType = MediaType.parse(request.headers['content-type']!);
|
|
||||||
var boundary = contentType.parameters['boundary']!;
|
|
||||||
var transformer = MimeMultipartTransformer(boundary);
|
|
||||||
var bodyStream = request.read();
|
|
||||||
var parts = await transformer.bind(bodyStream).toList();
|
|
||||||
|
|
||||||
for (var part in parts) {
|
|
||||||
var contentDisp = part.headers['content-disposition']!;
|
|
||||||
var filename = _extractFilename(contentDisp);
|
|
||||||
if (filename != null) {
|
|
||||||
var filePath = p.join('uploads', filename);
|
|
||||||
await _saveFile(part, filePath);
|
|
||||||
logger.d('File uploaded: $filePath');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.ok('File uploaded successfully');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Route pour lister les fichiers
|
|
||||||
router.get('/files', (Request request) async {
|
|
||||||
final directory = Directory('uploads');
|
|
||||||
final files =
|
|
||||||
directory.listSync().map((file) => p.basename(file.path)).toList();
|
|
||||||
final json = {'files': files};
|
|
||||||
return Response.ok(jsonEncode(json),
|
|
||||||
headers: {'Content-Type': 'application/json'});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Démarrer le serveur
|
|
||||||
io.serve(router, 'localhost', 8080).then((server) {
|
|
||||||
logger.d('Serving at http://${server.address.host}:${server.port}');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _extractFilename(String contentDisposition) {
|
|
||||||
return RegExp('filename="([^"]*)"').firstMatch(contentDisposition)?.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _saveFile(MimeMultipart part, String filename) async {
|
|
||||||
var file = File(filename);
|
|
||||||
await file.create(recursive: true);
|
|
||||||
await part.pipe(file.openWrite());
|
|
||||||
}
|
|
||||||
|
|
||||||
void udpServer() async {
|
|
||||||
// Créer un socket UDP
|
|
||||||
var port = 8080; // Vous pouvez spécifier le port de votre choix
|
|
||||||
var address =
|
|
||||||
InternetAddress.anyIPv4; // Écouter sur toutes les interfaces IPv4
|
|
||||||
RawDatagramSocket socket = await RawDatagramSocket.bind(address, port);
|
|
||||||
logger.d('Serveur UDP écoutant sur ${address.address}:$port');
|
|
||||||
|
|
||||||
// Gérer les événements du socket
|
|
||||||
socket.listen((RawSocketEvent event) {
|
|
||||||
if (event == RawSocketEvent.read) {
|
|
||||||
Datagram? datagram = socket.receive();
|
|
||||||
if (datagram != null) {
|
|
||||||
var message = utf8.decode(datagram.data);
|
|
||||||
logger.d(
|
|
||||||
'Message reçu de ${datagram.address.address}:${datagram.port}: $message');
|
|
||||||
|
|
||||||
// Envoyer une réponse
|
|
||||||
String response = 'Reçu: $message';
|
|
||||||
List<int> data = utf8.encode(response);
|
|
||||||
socket.send(data, datagram.address, datagram.port);
|
|
||||||
logger.d(
|
|
||||||
'Réponse envoyée à ${datagram.address.address}:${datagram.port}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future printIps() async {
|
|
||||||
for (var interface in await NetworkInterface.list()) {
|
|
||||||
logger.d('== Interface: ${interface.name} ==');
|
|
||||||
for (var addr in interface.addresses) {
|
|
||||||
logger.d(
|
|
||||||
'${addr.address} ${addr.host} ${addr.isLoopback} ${addr.rawAddress} ${addr.type.name}');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var logger = Logger(
|
||||||
|
printer: PrettyPrinter(methodCount: 0),
|
||||||
|
filter: ProductionFilter(), // Use the ProductionFilter to enable logging in release mode
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
StoryVm.loadLibrary();
|
StoryVm.loadLibrary();
|
||||||
StoryVm.initialize();
|
StoryVm.initialize();
|
||||||
|
|
@ -182,6 +104,13 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
PlayerState state = PlayerState.disabled;
|
PlayerState state = PlayerState.disabled;
|
||||||
StreamSubscription? audioPlayerSub;
|
StreamSubscription? audioPlayerSub;
|
||||||
|
|
||||||
|
Image img = const Image(image: AssetImage('assets/320x240.png'));
|
||||||
|
|
||||||
|
// final Permission _permission = Permission.storage;
|
||||||
|
PermissionStatus _permissionStatus = PermissionStatus.denied;
|
||||||
|
|
||||||
|
var _openResult = 'Unknown';
|
||||||
|
|
||||||
void initPaths() async {
|
void initPaths() async {
|
||||||
Directory? dir;
|
Directory? dir;
|
||||||
|
|
||||||
|
|
@ -206,6 +135,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
img = Image.file(File(currentImage));
|
||||||
|
|
||||||
if (event.sound.isNotEmpty) {
|
if (event.sound.isNotEmpty) {
|
||||||
player.play(DeviceFileSource('${indexFile.getCurrentStoryPath()}/assets/${event.sound}'));
|
player.play(DeviceFileSource('${indexFile.getCurrentStoryPath()}/assets/${event.sound}'));
|
||||||
}
|
}
|
||||||
|
|
@ -221,17 +152,53 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
state = PlayerState.indexFile;
|
state = PlayerState.indexFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showCurrentStory() async {
|
Uint8List soundBuffer = Uint8List(0);
|
||||||
|
|
||||||
|
void showCurrentStoryIndex() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
currentImage = indexFile.getCurrentTitleImage();
|
currentImage = indexFile.getCurrentTitleImage();
|
||||||
logger.d('Current image: $currentImage');
|
logger.d('Current image: $currentImage');
|
||||||
});
|
});
|
||||||
|
|
||||||
var asset = DeviceFileSource(indexFile.getCurrentSoundImage());
|
img = Image.file(File(currentImage));
|
||||||
logger.d('Asset: ${asset.toString()}');
|
|
||||||
await player.play(asset);
|
// File sound = File(indexFile.getCurrentSoundImage());
|
||||||
|
// soundBuffer = sound.readAsBytesSync();
|
||||||
|
// // logger.d('Asset: ${asset.toString()}');
|
||||||
|
// await player.play(BytesSource(soundBuffer));
|
||||||
|
|
||||||
|
player.play(DeviceFileSource(indexFile.getCurrentSoundImage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void chooseLibraryDirectory() async {
|
||||||
|
// FilePickerResult? result = await FilePicker.platform.pickFiles();
|
||||||
|
String ?path = await FilePicker.platform.getDirectoryPath();
|
||||||
|
|
||||||
|
if (path != null) {
|
||||||
|
logger.d("Selected directory: $path");
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
myPath = path;
|
||||||
|
});
|
||||||
|
bool success = await indexFile.loadIndexFile(path);
|
||||||
|
if (success) {
|
||||||
|
showCurrentStoryIndex();
|
||||||
|
state = PlayerState.indexFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleClick(String value) async {
|
||||||
|
switch (value) {
|
||||||
|
case 'Library':
|
||||||
|
chooseLibraryDirectory();
|
||||||
|
break;
|
||||||
|
case 'Settings':
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// This method is rerun every time setState is called, for instance
|
// This method is rerun every time setState is called, for instance
|
||||||
|
|
@ -240,6 +207,23 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
// fast, so that you can just rebuild anything that needs updating rather
|
// fast, so that you can just rebuild anything that needs updating rather
|
||||||
// than having to individually change instances of widgets.
|
// than having to individually change instances of widgets.
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
// title: Text('Homepage'),
|
||||||
|
backgroundColor: const Color(0xFF9ab4a4),
|
||||||
|
actions: <Widget>[
|
||||||
|
PopupMenuButton<String>(
|
||||||
|
onSelected: handleClick,
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
return {'Library'}.map((String choice) {
|
||||||
|
return PopupMenuItem<String>(
|
||||||
|
value: choice,
|
||||||
|
child: Text(choice),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
// Center is a layout widget. It takes a single child and positions it
|
// Center is a layout widget. It takes a single child and positions it
|
||||||
// in the middle of the parent.
|
// in the middle of the parent.
|
||||||
|
|
@ -262,9 +246,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
myPath,
|
myPath,
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
Image(image: AssetImage(currentImage)),
|
img,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -272,49 +256,31 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
color: const Color(0xFF9ab4a4),
|
color: const Color(0xFF9ab4a4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
/* IconButton(
|
||||||
tooltip: 'Select library directory',
|
tooltip: 'Select library directory',
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.folder,
|
Icons.folder,
|
||||||
size: 40,
|
size: 40,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
String? selectedDirectory =
|
// FilePickerResult? result = await FilePicker.platform.pickFiles();
|
||||||
await FilePicker.platform.getDirectoryPath();
|
String ?path = await FilePicker.platform.getDirectoryPath();
|
||||||
|
|
||||||
if (selectedDirectory == null) {
|
if (path != null) {
|
||||||
// User canceled the picker
|
logger.d("Selected directory: $path");
|
||||||
} else {
|
|
||||||
bool? isGranted = true;
|
|
||||||
logger.d(selectedDirectory);
|
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
setState(() {
|
||||||
Saf saf = Saf(selectedDirectory);
|
myPath = path;
|
||||||
|
});
|
||||||
isGranted =
|
bool success = await indexFile.loadIndexFile(path);
|
||||||
await saf.getDirectoryPermission(isDynamic: false);
|
if (success) {
|
||||||
|
showCurrentStoryIndex();
|
||||||
if (isGranted != null && isGranted) {
|
state = PlayerState.indexFile;
|
||||||
// Perform some file operations
|
|
||||||
logger.d('Granted!');
|
|
||||||
} else {
|
|
||||||
// failed to get the permission
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isGranted == true) {
|
|
||||||
setState(() {
|
|
||||||
myPath = selectedDirectory;
|
|
||||||
});
|
|
||||||
bool success = await indexFile.loadIndexFile(selectedDirectory);
|
|
||||||
if (success) {
|
|
||||||
showCurrentStory();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
color: const Color(0xFFb05728),
|
color: const Color(0xFFb05728),
|
||||||
),
|
),*/
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Previous',
|
tooltip: 'Previous',
|
||||||
icon: const Icon(Icons.arrow_circle_left, size: 40),
|
icon: const Icon(Icons.arrow_circle_left, size: 40),
|
||||||
|
|
@ -323,7 +289,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
StoryVm.previousButton();
|
StoryVm.previousButton();
|
||||||
} else if (state == PlayerState.indexFile) {
|
} else if (state == PlayerState.indexFile) {
|
||||||
indexFile.previous();
|
indexFile.previous();
|
||||||
showCurrentStory();
|
showCurrentStoryIndex();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
color: const Color(0xFFb05728),
|
color: const Color(0xFFb05728),
|
||||||
|
|
@ -337,7 +303,22 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
StoryVm.nextButton();
|
StoryVm.nextButton();
|
||||||
} else if (state == PlayerState.indexFile) {
|
} else if (state == PlayerState.indexFile) {
|
||||||
indexFile.next();
|
indexFile.next();
|
||||||
showCurrentStory();
|
showCurrentStoryIndex();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color: const Color(0xFFb05728),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'Home',
|
||||||
|
icon: const Icon(Icons.home_filled, size: 40),
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
if (state == PlayerState.inStory) {
|
||||||
|
player.stop();
|
||||||
|
showCurrentStoryIndex();
|
||||||
|
state = PlayerState.indexFile;
|
||||||
|
} else if (state == PlayerState.indexFile) {
|
||||||
|
player.stop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
color: const Color(0xFFb05728),
|
color: const Color(0xFFb05728),
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
# The name of the executable created for the application. Change this to change
|
# The name of the executable created for the application. Change this to change
|
||||||
# the on-disk name of your application.
|
# the on-disk name of your application.
|
||||||
set(BINARY_NAME "flutter_launcher")
|
set(BINARY_NAME "story_player")
|
||||||
# The unique GTK application identifier for this application. See:
|
# The unique GTK application identifier for this application. See:
|
||||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
set(APPLICATION_ID "com.example.flutter_launcher")
|
set(APPLICATION_ID "org.openstoryteller.story_player")
|
||||||
|
|
||||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
# versions of CMake.
|
# versions of CMake.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
name: flutter_launcher
|
name: story_player
|
||||||
description: A new Flutter project.
|
description: OpenStoryTeller universal player.
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
@ -43,10 +43,12 @@ dependencies:
|
||||||
saf: ^1.0.3+4
|
saf: ^1.0.3+4
|
||||||
ffi: ^2.1.0
|
ffi: ^2.1.0
|
||||||
logger: ^2.2.0
|
logger: ^2.2.0
|
||||||
file_picker: ^7.0.2
|
file_picker: ^8.0.3
|
||||||
dqoi: ^1.3.0
|
dqoi: ^1.3.0
|
||||||
audioplayers: ^5.2.1
|
audioplayers: ^5.2.1
|
||||||
event_bus: ^2.0.0
|
event_bus: ^2.0.0
|
||||||
|
external_path: ^1.0.3
|
||||||
|
permission_handler: ^10.4.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:flutter_launcher/main.dart';
|
import 'package:story_player/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
|
|
|
||||||