Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d26d406
Add dependency Pocketpy
CNlongY-Py May 11, 2026
124f2e1
Create python_service.dart
CNlongY-Py May 11, 2026
e561d21
Create python_service_stub.dart
CNlongY-Py May 11, 2026
86ef5f6
Merge pull request #1 from CNlongY-Py/v3-plug-beta
CNlongY-Py May 11, 2026
e59d2f5
Merge pull request #2 from CNlongY-Py/v3-plug-beta-1
CNlongY-Py May 11, 2026
5cf3c6a
Merge pull request #3 from CNlongY-Py/v3-plug-beta-2
CNlongY-Py May 11, 2026
006614c
Create python_service_native.dart
CNlongY-Py May 11, 2026
275ba0c
Update main.dart
CNlongY-Py May 11, 2026
4b346db
Update main.dart
CNlongY-Py May 12, 2026
88f5590
Update python_service_native.dart
CNlongY-Py May 12, 2026
2936d3b
Update python_service_native.dart
CNlongY-Py May 12, 2026
883857a
Update main.dart
CNlongY-Py May 12, 2026
6b59270
Update main.dart
CNlongY-Py May 12, 2026
f4e8f17
Update python_service_native.dart
CNlongY-Py May 12, 2026
5e88bb9
Update python_service_stub.dart
CNlongY-Py May 12, 2026
ae323dc
Merge branch 'Solsynth:v3' into v3
CNlongY-Py May 13, 2026
2289fc3
Update main.dart
CNlongY-Py May 13, 2026
b6acc2a
Update python_service_native.dart
CNlongY-Py May 13, 2026
7c89033
Update app_startup_splash.dart
CNlongY-Py May 13, 2026
3a29fcf
Update python_service_native.dart
CNlongY-Py May 13, 2026
3e6cc8e
Update python_service_native.dart
CNlongY-Py May 13, 2026
2455984
Update python_service_native.dart
CNlongY-Py May 13, 2026
1a36ae7
Merge branch 'Solsynth:v3' into v3
CNlongY-Py May 15, 2026
35c4507
Create loader.py
CNlongY-Py May 15, 2026
bf71e7a
Delete assets/python directory
CNlongY-Py May 15, 2026
73736ad
Create loader.py
CNlongY-Py May 15, 2026
d2cfe39
Update pubspec.yaml
CNlongY-Py May 15, 2026
9e78d50
Update python_service_native.dart
CNlongY-Py May 15, 2026
2de2d13
Update loader.py
CNlongY-Py May 15, 2026
3b17c39
Create event.py
CNlongY-Py May 15, 2026
ea33e2e
Update loader.py
CNlongY-Py May 15, 2026
38f6b06
Update python_service_native.dart
CNlongY-Py May 15, 2026
851ed1b
Update main.dart
CNlongY-Py May 16, 2026
66ee792
Update main.dart
CNlongY-Py May 16, 2026
7231ace
Update app_startup_splash.dart
CNlongY-Py May 17, 2026
8f501e0
Update main.dart
CNlongY-Py May 17, 2026
1ad2755
Update app_wrapper.dart
CNlongY-Py May 17, 2026
15b3620
Update python_service_native.dart
CNlongY-Py May 17, 2026
dddc218
Update python_service_native.dart
CNlongY-Py May 17, 2026
b056d56
Update python_service_native.dart
CNlongY-Py May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions assets/scripts/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
_handlers = {}

def listen(event_name, callback):
if event_name not in _handlers:
_handlers[event_name] = []
_handlers[event_name].append(callback)

def call(event_name, *args):
if event_name in _handlers:
for cb in _handlers[event_name]:
try:
cb(*args)
except Exception as e:
print(f"Error in event {event_name}: {e}")
17 changes: 17 additions & 0 deletions assets/scripts/loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import os
import sys

def load_plugins():
plugins_dir = os.path.join(sys.path[0], 'plugins')
if not os.path.exists(plugins_dir):
print(f"Plugins directory not found: {plugins_dir}")
return

for filename in os.listdir(plugins_dir):
if filename.endswith('.py'):
module_name = filename[:-3]
try:
__import__(module_name)
print(f"Imported plugin: {module_name}")
except Exception as e:
print(f"Failed to import plugin {module_name}: {e}")
2 changes: 2 additions & 0 deletions lib/core/services/python_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'python_service_stub.dart'
if (dart.library.io) 'python_service_native.dart';
74 changes: 74 additions & 0 deletions lib/core/services/python_service_native.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/services.dart';
import 'package:pocketpy/pocketpy.dart' as pkpy;
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:logging/logging.dart';

pkpy.VM? _vm;
bool _isInitialized = false;

bool isPythonAvailable() => _isInitialized;

Future<void> initPython() async {
if (_isInitialized) return;

try {
Directory baseDir;
if (kIsWeb) {
Logger.root.info('[python_service] Web platform, skipping');
return;
} else if (Platform.isAndroid || Platform.isIOS) {
baseDir = await getApplicationSupportDirectory();
} else {
final exeDir = path.dirname(Platform.resolvedExecutable);
baseDir = Directory(exeDir);
}

final pluginsDir = Directory(path.join(baseDir.path, 'plugins'));
if (!await pluginsDir.exists()) {
await pluginsDir.create(recursive: true);
Logger.root.info('[python_service] Created plugins directory: ${pluginsDir.path}');
}

final manifest = await AssetManifest.loadFromAssetBundle(rootBundle);
final assets = manifest.listAssets();
final scriptPaths = assets.where((asset) => asset.startsWith('assets/scripts/') && asset.endsWith('.py')).toList();

for (final assetPath in scriptPaths) {
final fileName = path.basename(assetPath);
final destFile = File(path.join(baseDir.path, fileName));
final content = await rootBundle.loadString(assetPath);
await destFile.writeAsString(content);
Logger.root.info('[python_service] Wrote $fileName to ${destFile.path}');
}

_vm = pkpy.VM();

_vm!.exec('import sys');
_vm!.exec('sys.path.insert(0, r"${baseDir.path}")');

_vm!.exec('import loader');
_vm!.exec('loader.load_plugins()');

final out = _vm!.read_output();
if (out.stdout.isNotEmpty) Logger.root.info('[Python stdout] ${out.stdout}');
if (out.stderr.isNotEmpty) Logger.root.warning('[Python stderr] ${out.stderr}');

_isInitialized = true;
Logger.root.info('[python_service] Initialized, base dir: ${baseDir.path}');
} catch (e) {
Logger.root.severe('[python_service] Init failed: $e');
_isInitialized = false;
_vm = null;
}
}

Future<void> evalPythonCode(String code) async {
if (!_isInitialized || _vm == null) return;
_vm!.exec(code);
final out = _vm!.read_output();
if (out.stdout.isNotEmpty) Logger.root.info('[Python stdout] ${out.stdout}');
if (out.stderr.isNotEmpty) Logger.root.warning('[Python stderr] ${out.stderr}');
}
6 changes: 6 additions & 0 deletions lib/core/services/python_service_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Web 平台的空实现
Future<void> initPython() async {}

bool isPythonAvailable() => false;

Future<void> evalPythonCode(String code) async {}
54 changes: 27 additions & 27 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'dart:developer';
import 'dart:developer' as developer;
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
Expand Down Expand Up @@ -38,24 +38,20 @@ import 'package:protocol_handler/protocol_handler.dart';
import 'package:island/core/services/unifiedpush_service.dart';
import 'package:media_kit/media_kit.dart';

// 注意:不再导入 python_service

final List<LogRecord> _earlyLogs = [];

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
Logger.root.info('Handling a background message: ${message.messageId}');
}

void main(List<String> args) async {
// Initialize logging
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
log(
[
'[${record.time}] [${record.level}] ${record.message}',
if (record.error != null) 'Error: ${record.error}',
?record.stackTrace,
].join('\n'),
time: record.time,
level: record.level.value,
);
_earlyLogs.add(record);
});

final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
Expand Down Expand Up @@ -84,7 +80,6 @@ void main(List<String> args) async {

try {
await EasyLocalization.ensureInitialized();
// Disable logs
EasyLocalization.logger.enableBuildModes = [];

if (kIsWeb || !Platform.isLinux) {
Expand All @@ -94,9 +89,6 @@ void main(List<String> args) async {
FirebaseMessaging.onBackgroundMessage(
_firebaseMessagingBackgroundHandler,
);
// Although previous if case checked this. Still check is web or not
// Otherwise the web platform will broke due to there is no Platform api on the web
// Skip crashlytics setup on debug mode to prevent unexpected report to firebase
if ((kIsWeb || !Platform.isWindows) && !kDebugMode) {
FlutterError.onError =
FirebaseCrashlytics.instance.recordFlutterFatalError;
Expand Down Expand Up @@ -147,12 +139,12 @@ void main(List<String> args) async {

final prefs = await SharedPreferences.getInstance();

// 移除 Python 初始化代码

if (!kIsWeb && (Platform.isMacOS || Platform.isLinux || Platform.isWindows)) {
await windowManager.ensureInitialized();

const defaultSize = Size(360, 640);

// Get saved window size from preferences
final savedSizeString = prefs.getString(kAppWindowSize);
Size initialSize = defaultSize;

Expand Down Expand Up @@ -218,6 +210,23 @@ void main(List<String> args) async {
Logger.root.info("[SplashScreen] Now hiding splash screen...");
}

Logger.root.onRecord.listen((record) {
developer.log(
record.message,
time: record.time,
level: record.level.value,
name: record.loggerName,
);
});
for (final record in _earlyLogs) {
developer.log(
record.message,
time: record.time,
level: record.level.value,
name: record.loggerName,
);
}

runApp(
ProviderScope(
retry: (retryCount, error) {
Expand Down Expand Up @@ -254,8 +263,7 @@ void main(List<String> args) async {
);
}

// Router will be provided through Riverpod

// 以下是 IslandApp 等代码保持不变...
final globalOverlay = GlobalKey<OverlayState>();
final globalScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

Expand All @@ -264,14 +272,11 @@ class IslandApp extends HookConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
// Make sure it's active
final _ = ref.read(logsProvider);

// Theme data and prefs
final theme = ref.watch(themeProvider);
final settings = ref.watch(appSettingsProvider);

// Convert string theme mode to ThemeMode enum
ThemeMode getThemeMode() {
final themeMode = settings.themeMode ?? 'system';
switch (themeMode) {
Expand All @@ -289,11 +294,9 @@ class IslandApp extends HookConsumerWidget {
if (notification.data['meta']?['action_uri'] != null) {
var uri = notification.data['meta']['action_uri'] as String;
if (uri.startsWith('/')) {
// In-app routes
final router = ref.read(routerProvider);
router.push(notification.data['meta']['action_uri']);
} else {
// External links
launchUrlString(uri);
}
}
Expand All @@ -304,19 +307,16 @@ class IslandApp extends HookConsumerWidget {
return null;
}

// When the app is opened from a terminated state.
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) {
handleMessage(message);
}
});

// When the app is in the background and opened.
final onMessageOpenedAppSubscription = FirebaseMessaging
.onMessageOpenedApp
.listen(handleMessage);

// When the app is in the foreground.
final onMessageSubscription = FirebaseMessaging.onMessage.listen((
message,
) {
Expand Down
9 changes: 9 additions & 0 deletions lib/shared/widgets/app_startup_splash.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:island/core/audio.dart';
import 'package:island/core/network.dart';
import 'package:island/core/services/notify.dart';
import 'package:island/core/websocket.dart';
import 'package:island/core/services/python_service.dart' as python;

const kDefaultBootstrapRetryTimeouts = <Duration>[
Duration(milliseconds: 1000),
Expand Down Expand Up @@ -98,6 +99,14 @@ class StartupSplashScreen extends HookConsumerWidget {
await ref.read(websocketStateProvider.notifier).connect();
},
),
// Python 初始化阶段 - 放在网络连接之后,推送通知之前
_BootstrapStage(
label: 'Initializing Python scripts',
isCritical: false,
action: () async {
await python.initPython();
},
),
_BootstrapStage(
label: 'Registering push notifications',
isCritical: false,
Expand Down
Loading
Loading