open-story-teller/story-player/lib/main.dart
2024-05-17 21:12:39 +02:00

353 lines
11 KiB
Dart

import 'package:flutter/material.dart' hide Router;
import 'dart:io';
import 'dart:convert';
import 'dart:async';
import 'dart:typed_data';
import 'package:saf/saf.dart';
import 'package:path_provider/path_provider.dart';
import 'package:audioplayers/audioplayers.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/indexfile.dart';
import 'httpserver.dart';
class ProductionFilter extends LogFilter {
@override
bool shouldLog(LogEvent event) {
return true;
}
}
var logger = Logger(
printer: PrettyPrinter(methodCount: 0),
filter: ProductionFilter(), // Use the ProductionFilter to enable logging in release mode
);
void main() {
StoryVm.loadLibrary();
StoryVm.initialize();
udpServer();
httpServer();
printIps();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a blue toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
scaffoldBackgroundColor: const Color(0xFF9ab4a4),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
enum PlayerState { disabled, indexFile, inStory }
class _MyHomePageState extends State<MyHomePage> {
String myPath = 'fffff';
IndexFile indexFile = IndexFile();
String currentImage = 'assets/320x240.png';
final player = AudioPlayer();
StreamSubscription? mediaPub;
PlayerState state = PlayerState.disabled;
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 {
Directory? dir;
if (Platform.isAndroid) {
dir = await getExternalStorageDirectory();
} else {
dir = await getApplicationDocumentsDirectory();
}
setState(() {
myPath = dir.toString();
});
logger.d("===============+> $myPath");
}
_MyHomePageState() {
initPaths();
mediaPub = eventBus.on<MediaEvent>().listen((event) {
setState(() {
if (event.image.isNotEmpty) {
currentImage = '${indexFile.getCurrentStoryPath()}/assets/${event.image}';
}
});
img = Image.file(File(currentImage));
if (event.sound.isNotEmpty) {
player.play(DeviceFileSource('${indexFile.getCurrentStoryPath()}/assets/${event.sound}'));
}
});
audioPlayerSub = player.onPlayerComplete.listen((event) {
if (state == PlayerState.inStory) {
// Send end of music event
StoryVm.endOfSound();
}
});
state = PlayerState.indexFile;
}
Uint8List soundBuffer = Uint8List(0);
void showCurrentStoryIndex() async {
setState(() {
currentImage = indexFile.getCurrentTitleImage();
logger.d('Current image: $currentImage');
});
img = Image.file(File(currentImage));
// 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
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
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(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
myPath,
style: Theme.of(context).textTheme.bodySmall,
),
img,
],
),
),
bottomNavigationBar: BottomAppBar(
color: const Color(0xFF9ab4a4),
child: Row(
children: <Widget>[
/* IconButton(
tooltip: 'Select library directory',
icon: const Icon(
Icons.folder,
size: 40,
),
onPressed: () 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;
}
}
},
color: const Color(0xFFb05728),
),*/
IconButton(
tooltip: 'Previous',
icon: const Icon(Icons.arrow_circle_left, size: 40),
onPressed: () {
if (state == PlayerState.inStory) {
StoryVm.previousButton();
} else if (state == PlayerState.indexFile) {
indexFile.previous();
showCurrentStoryIndex();
}
},
color: const Color(0xFFb05728),
),
IconButton(
tooltip: 'Next',
icon: const Icon(Icons.arrow_circle_right, size: 40),
onPressed: () {
if (state == PlayerState.inStory) {
StoryVm.nextButton();
} else if (state == PlayerState.indexFile) {
indexFile.next();
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),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (state == PlayerState.inStory) {
StoryVm.okButton();
} else if (state == PlayerState.indexFile) {
String path = indexFile.getCurrentStoryPath();
if (path.isNotEmpty) {
StoryVm.start(path);
state = PlayerState.inStory;
}
}
},
tooltip: 'Ok',
backgroundColor: const Color(0xFF0092c8),
foregroundColor: Colors.white,
child: const Text('O K'),
),
floatingActionButtonLocation: FloatingActionButtonLocation.endContained,
);
}
}