story-player flutter: integrated new event mask + shared preferences

This commit is contained in:
anthony@rabine.fr 2024-05-28 11:52:33 +02:00
parent 26c917fe27
commit e65b506ce1
11 changed files with 131 additions and 86 deletions

15
shared/story_machine.h Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#define EV_MASK_OK_BUTTON 0b1
#define EV_MASK_PREVIOUS_BUTTON 0b10
#define EV_MASK_NEXT_BUTTON 0b100
#define EV_MASK_UP_BUTTON 0b1000
#define EV_MASK_DOWN_BUTTON 0b10000
#define EV_MASK_HOME_BUTTON 0b100000
#define EV_MASK_SELECT_BUTTON 0b1000000
#define EV_MASK_START_BUTTON 0b10000000
#define EV_MASK_STOP_BUTTON 0b100000000
#define EV_MASK_PAUSE_BUTTON 0b1000000000
#define EV_MASK_END_OF_AUDIO 0b10000000000
#define EV_MASK_ALL 0xFFFFFFFFUL

View file

@ -1,8 +1,9 @@
<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.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

View file

@ -1,4 +1,3 @@
allprojects {
repositories {
google()

View file

@ -8,7 +8,7 @@ pluginManagement {
}
settings.ext.flutterSdkPath = flutterSdkPath()
// includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()

View file

@ -0,0 +1,28 @@
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
// ignore_for_file: type=lint
const int EV_MASK_OK_BUTTON = 1;
const int EV_MASK_PREVIOUS_BUTTON = 2;
const int EV_MASK_NEXT_BUTTON = 4;
const int EV_MASK_UP_BUTTON = 8;
const int EV_MASK_DOWN_BUTTON = 16;
const int EV_MASK_HOME_BUTTON = 32;
const int EV_MASK_SELECT_BUTTON = 64;
const int EV_MASK_START_BUTTON = 128;
const int EV_MASK_STOP_BUTTON = 256;
const int EV_MASK_PAUSE_BUTTON = 512;
const int EV_MASK_END_OF_AUDIO = 1024;
const int EV_MASK_ALL = 4294967295;

View file

@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:logger/logger.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
@ -124,11 +125,22 @@ class IndexFile {
bool isGranted = false;
if (Platform.isAndroid) {
if (await Permission.storage.request().isGranted) {
AndroidDeviceInfo deviceInfo = await DeviceInfoPlugin().androidInfo;
if (deviceInfo.version.sdkInt <= 32) {
if (await Permission.storage.request().isGranted) {
isGranted = true;
}
} else {
if (await Permission.manageExternalStorage.request().isGranted) {
isGranted = true;
}
}
if (!isGranted) {
await openAppSettings();
}
} else {
isGranted = true;
}

View file

@ -8,6 +8,7 @@ import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:logger/logger.dart';
import 'package:event_bus/event_bus.dart';
import 'generated_story_machine.dart';
var logger = Logger(printer: PrettyPrinter(methodCount: 0));
@ -55,8 +56,6 @@ typedef VmRun = void Function();
typedef VmSendEventType = Void Function(Int);
typedef VmSendEvent = void Function(int event);
enum VmEvent { evNoEvent, evStep, evOkButton, evPreviousButton, evNextButton, evAudioFinished, evStop }
class StoryVm {
static late DynamicLibrary nativeApiLib;
static late VmInitialize vmInitialize;
@ -138,22 +137,27 @@ class StoryVm {
static void endOfSound() {
vmSendEvent(VmEvent.evAudioFinished.index);
vmSendEvent(EV_MASK_END_OF_AUDIO);
}
static void okButton() {
vmSendEvent(VmEvent.evOkButton.index);
vmSendEvent(EV_MASK_OK_BUTTON);
}
static void previousButton() {
vmSendEvent(VmEvent.evPreviousButton.index);
vmSendEvent(EV_MASK_PREVIOUS_BUTTON);
}
static void nextButton() {
vmSendEvent(VmEvent.evNextButton.index);
vmSendEvent(EV_MASK_NEXT_BUTTON);
}
static void homeButton() {
vmSendEvent(EV_MASK_HOME_BUTTON);
}
static void stop() {

View file

@ -14,6 +14,7 @@ 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 'package:shared_preferences/shared_preferences.dart';
import 'libstory/storyvm.dart';
import 'libstory/indexfile.dart';
@ -97,7 +98,7 @@ class MyHomePage extends StatefulWidget {
enum PlayerState { disabled, indexFile, inStory }
class _MyHomePageState extends State<MyHomePage> {
String myPath = 'fffff';
String libraryDir = 'fffff';
IndexFile indexFile = IndexFile();
String currentImage = 'assets/logo.png';
final player = AudioPlayer();
@ -105,22 +106,42 @@ class _MyHomePageState extends State<MyHomePage> {
PlayerState state = PlayerState.disabled;
StreamSubscription? audioPlayerSub;
Image img = const Image(image: AssetImage('assets/logo.png'));
void initPaths() async {
Directory? dir;
if (Platform.isAndroid) {
dir = await getExternalStorageDirectory();
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? libDir = prefs.getString('library-directory');
if (libDir != null) {
setState(() {
libraryDir = libDir;
});
bool success = await indexFile.loadIndexFile(libDir);
if (success) {
showCurrentStoryIndex();
state = PlayerState.indexFile;
}
} else {
dir = await getApplicationDocumentsDirectory();
logger.d("No library directory found");
Directory? dir;
if (Platform.isAndroid) {
dir = await getExternalStorageDirectory();
} else {
dir = await getApplicationDocumentsDirectory();
}
if (dir != null) {
setState(() {
libraryDir = '${dir.toString()}/stories';
});
}
}
setState(() {
myPath = dir.toString();
});
logger.d("===============+> $myPath");
logger.d("===============+> $libraryDir");
}
_MyHomePageState() {
@ -174,9 +195,10 @@ class _MyHomePageState extends State<MyHomePage> {
if (path != null) {
logger.d("Selected directory: $path");
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('library-directory', path);
setState(() {
myPath = path;
libraryDir = path;
});
bool success = await indexFile.loadIndexFile(path);
if (success) {
@ -244,10 +266,10 @@ class _MyHomePageState extends State<MyHomePage> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
myPath,
style: Theme.of(context).textTheme.bodySmall,
),
// Text(
// libraryDir,
// style: Theme.of(context).textTheme.bodySmall,
// ),
img,
],
),
@ -256,31 +278,6 @@ class _MyHomePageState extends State<MyHomePage> {
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),
@ -314,9 +311,7 @@ class _MyHomePageState extends State<MyHomePage> {
onPressed: () {
if (state == PlayerState.inStory) {
player.stop();
showCurrentStoryIndex();
state = PlayerState.indexFile;
StoryVm.homeButton();
} else if (state == PlayerState.indexFile) {
player.stop();
}

View file

@ -49,6 +49,9 @@ dependencies:
event_bus: ^2.0.0
external_path: ^1.0.3
permission_handler: ^10.4.5
device_info_plus: ^10.1.0
ffigen: ^12.0.0
shared_preferences: ^2.2.3
dev_dependencies:
flutter_test:
@ -105,3 +108,8 @@ flutter:
assets:
- assets/logo.png
ffigen:
output: 'lib/libstory/generated_story_machine.dart'
headers:
entry-points:
- '../shared/story_machine.h'

View file

@ -7,5 +7,5 @@ add_library(storyvm
../../firmware/chip32/chip32_vm.c
)
include_directories(../../firmware/chip32)
include_directories(../../firmware/chip32 ../../shared)

View file

@ -11,7 +11,7 @@
#include "chip32_vm.h"
#include "dlib_export.h"
#include "story_machine.h"
static char root_dir[260];
@ -31,12 +31,16 @@ static chip32_ctx_t chip32_ctx;
static chip32_result_t run_result;
static uint32_t event_mask = 0;
static uint8_t IndexBuf[260];
static uint8_t ImageBuf[100];
static uint8_t SoundBuf[100];
static bool IsValidEvent(uint32_t event) {
return (event_mask & event) != 0;
}
int get_filename_from_memory(chip32_ctx_t *ctx, uint32_t addr, char *filename_mem)
{
@ -106,11 +110,12 @@ uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code)
std::cout << "[STORYVM] No sound" << std::endl;
}
retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
retCode = SYSCALL_RET_OK; // set the VM in pause
}
else if (code == 2) // Wait Event
{
std::cout << "[STORYVM] Syscall 2 (wait for event)" << std::endl;
event_mask = ctx->registers[R0];
retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
}
return retCode;
@ -160,38 +165,16 @@ extern "C" void storyvm_initialize(media_callback cb)
storyvm_stop();
}
enum VmEventType {EvNoEvent, EvStep, EvOkButton, EvPreviousButton, EvNextButton, EvAudioFinished, EvStop};
extern "C" void storyvm_send_event(int event)
{
if (event == VmEventType::EvStep)
if (IsValidEvent(event))
{
chip32_ctx.registers[R0] = event;
run_result = VM_OK;
}
else if (event == VmEventType::EvOkButton)
else
{
chip32_ctx.registers[R0] = 0x01;
run_result = VM_OK;
}
else if (event == VmEventType::EvPreviousButton)
{
chip32_ctx.registers[R0] = 0x02;
run_result = VM_OK;
}
else if (event == VmEventType::EvNextButton)
{
chip32_ctx.registers[R0] = 0x04;
run_result = VM_OK;
}
else if (event == VmEventType::EvAudioFinished)
{
chip32_ctx.registers[R0] = 0x08;
run_result = VM_OK;
}
else if (event == VmEventType::EvStop)
{
run_result = VM_FINISHED;
std::cout << "[STORYVM] Invalid event" << std::endl;
}
}