From c31209f844d394de2fbc53fb71fbd51237e1a3af Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Mon, 5 Jan 2026 16:54:45 +1100 Subject: [PATCH 1/2] Fix the issue that the app cannot be loaded on web browser --- example/lib/main.dart | 33 +++++---- lib/src/utils/file_ops.dart | 90 ++++++++++++++++++----- lib/src/utils/platform_io.dart | 115 ++++++++++++++++++++++++++++++ lib/src/utils/platform_web.dart | 101 ++++++++++++++++++++++++++ lib/src/widgets/audio_widget.dart | 84 ++++++++++++++-------- lib/src/widgets/image_widget.dart | 81 ++++++++++++++++----- lib/src/widgets/video_widget.dart | 44 +++++++----- 7 files changed, 452 insertions(+), 96 deletions(-) create mode 100644 lib/src/utils/platform_io.dart create mode 100644 lib/src/utils/platform_web.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 5ab23e4..5b6ebc0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -31,11 +31,14 @@ library; import 'dart:async'; -import 'dart:io'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:markdown_widget_builder/markdown_widget_builder.dart'; +import 'package:markdown_widget_builder/src/utils/platform_io.dart' + if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' + as platform_utils; void main() { runApp(const MyApp()); @@ -65,7 +68,7 @@ class _MarkdownExamplePageState extends State { bool _isLoadingConfig = true; Object? _configLoadError; - StreamSubscription? _fileWatchSub; + StreamSubscription? _fileWatchSub; String _markdownContent = 'Loading...'; @override @@ -103,18 +106,20 @@ class _MarkdownExamplePageState extends State { ); setState(() => _markdownContent = content); - // Watch file changes (if local path is valid). - - final interpretedPath = await interpretPath(config.markdown.path); - final file = File(interpretedPath); - if (await file.exists()) { - _fileWatchSub?.cancel(); - _fileWatchSub = watchFileChanges( - interpretedPath, - onFileContentChanged: (newContent) { - setState(() => _markdownContent = newContent); - }, - ); + // Watch file changes (if local path is valid and not on web). + + if (!kIsWeb) { + final interpretedPath = await interpretPath(config.markdown.path); + final fileExists = await platform_utils.fileExists(interpretedPath); + if (fileExists) { + _fileWatchSub?.cancel(); + _fileWatchSub = watchFileChanges( + interpretedPath, + onFileContentChanged: (newContent) { + setState(() => _markdownContent = newContent); + }, + ); + } } } catch (e) { setState(() => _configLoadError = e); diff --git a/lib/src/utils/file_ops.dart b/lib/src/utils/file_ops.dart index 40edc6e..72619f7 100644 --- a/lib/src/utils/file_ops.dart +++ b/lib/src/utils/file_ops.dart @@ -32,13 +32,17 @@ library; import 'dart:async'; import 'dart:convert'; -import 'dart:io'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/services.dart' show rootBundle; import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart' - show getApplicationDocumentsDirectory; + +// Conditionally import dart:io for non-web platforms. + +import 'package:markdown_widget_builder/src/utils/platform_io.dart' + if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' + as platform_utils; import 'package:markdown_widget_builder/markdown_widget_builder.dart' show setMarkdownMediaPath; @@ -103,8 +107,14 @@ class Config { /// platforms. Future getAppDirectory() async { - final os = Platform.operatingSystem; - final exePath = Platform.resolvedExecutable; + if (kIsWeb) { + // On web, return a fallback path as there is no file system access. + + return ''; + } + + final os = platform_utils.getOperatingSystem(); + final exePath = platform_utils.getResolvedExecutable(); if (os == 'macos') { final exeDir = p.dirname(exePath); @@ -115,8 +125,8 @@ Future getAppDirectory() async { } else if (os == 'windows' || os == 'linux') { return p.dirname(exePath); } else if (os == 'android' || os == 'ios') { - final docDir = await getApplicationDocumentsDirectory(); - return docDir.path; + final docDirPath = await platform_utils.getApplicationDocumentsPath(); + return docDirPath; } else { return p.dirname(exePath); } @@ -172,6 +182,13 @@ Future loadMediaFiles( String rawMediaPath, { Function(String)? onError, }) async { + if (kIsWeb) { + // On web, always use asset path as there is no local file system access. + + setMarkdownMediaPath(mediaPath); + return; + } + if (rawMediaPath.trim().isEmpty) { // If the path is empty, fallback to assets. @@ -184,9 +201,9 @@ Future loadMediaFiles( } final interpretedMediaPath = await interpretPath(rawMediaPath); - Directory dir = Directory(interpretedMediaPath); + final dirExists = await platform_utils.directoryExists(interpretedMediaPath); - if (!await dir.exists()) { + if (!dirExists) { // If the media directory does not exist, fallback to the default. onError?.call('Media directory not found: $interpretedMediaPath. ' @@ -205,6 +222,24 @@ Future loadMarkdownContent( String rawPath, { Function(String)? onError, }) async { + if (kIsWeb) { + // On web, always load from assets. + + if (rawPath.trim().isEmpty) { + return rootBundle.loadString(mdPath); + } + try { + return await rootBundle.loadString(rawPath); + } catch (e) { + try { + return await rootBundle.loadString(mdPath); + } catch (e) { + onError?.call('Error loading asset: $e'); + return 'Error: Could not load asset.'; + } + } + } + if (rawPath.trim().isEmpty) { // If the path is empty, fallback to assets. @@ -214,9 +249,9 @@ Future loadMarkdownContent( return rootBundle.loadString(mdPath); } final interpretedPath = await interpretPath(rawPath); - File file = File(interpretedPath); + final fileExists = await platform_utils.fileExists(interpretedPath); - if (!await file.exists()) { + if (!fileExists) { // If the file does not exist, fallback to assets. onError?.call('Markdown file not found at $interpretedPath. ' @@ -228,23 +263,40 @@ Future loadMarkdownContent( return 'Error: Could not load fallback asset.'; } } else { - return file.readAsString(); + return platform_utils.readFileAsString(interpretedPath); } } /// File watcher. Returns a subscription object that the caller can cancel at /// an appropriate time. +/// Returns null on web platform as file watching is not supported. -StreamSubscription watchFileChanges( +StreamSubscription? watchFileChanges( String filePath, { required void Function(String newContent) onFileContentChanged, }) { - final parentDir = Directory(p.dirname(filePath)); - return parentDir.watch().listen((event) async { - if (event.type == FileSystemEvent.modify && event.path == filePath) { - final f = File(filePath); - if (await f.exists()) { - final updated = await f.readAsString(); + if (kIsWeb) { + // File watching is not supported on web. + + return null; + } + + final parentDirPath = p.dirname(filePath); + final watchStream = platform_utils.watchDirectory(parentDirPath); + + if (watchStream == null) { + return null; + } + + return watchStream.listen((event) async { + // On non-web platforms, event is FileSystemEvent. + + if (event.type == 2 && event.path == filePath) { + // type 2 is FileSystemEvent.modify + + final fileExists = await platform_utils.fileExists(filePath); + if (fileExists) { + final updated = await platform_utils.readFileAsString(filePath); onFileContentChanged(updated); } } diff --git a/lib/src/utils/platform_io.dart b/lib/src/utils/platform_io.dart new file mode 100644 index 0000000..b24e490 --- /dev/null +++ b/lib/src/utils/platform_io.dart @@ -0,0 +1,115 @@ +/// Platform utilities for non-web platforms (using dart:io). +/// +/// Copyright (C) 2026, Software Innovation Institute, ANU. +/// +/// Licensed under the MIT License (the "License"). +/// +/// License: https://choosealicense.com/licenses/mit/. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/// +/// Authors: Tony Chen + +library; + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:path_provider/path_provider.dart' + show getTemporaryDirectory, getApplicationDocumentsDirectory; + +/// Checks if a file exists at the given path. +/// Returns false on web platform. + +Future fileExists(String path) async { + final file = File(path); + return file.exists(); +} + +/// Writes bytes to a temporary file and returns the path. +/// Throws on web platform. + +Future writeBytesToTempFile(String filename, Uint8List bytes) async { + final tempDir = await getTemporaryDirectory(); + final fileNameOnly = filename.split('/').last; + final tempPath = '${tempDir.path}/$fileNameOnly'; + final tempFile = File(tempPath); + await tempFile.writeAsBytes(bytes); + return tempFile.path; +} + +/// Gets the temporary directory path. +/// Throws on web platform. + +Future getTemporaryDirectoryPath() async { + final tempDir = await getTemporaryDirectory(); + return tempDir.path; +} + +/// Returns the operating system name. +/// Returns 'web' on web platform. + +String getOperatingSystem() { + return Platform.operatingSystem; +} + +/// Returns the resolved executable path. +/// Returns empty string on web platform. + +String getResolvedExecutable() { + return Platform.resolvedExecutable; +} + +/// Returns the application documents directory path. +/// Throws on web platform. + +Future getApplicationDocumentsPath() async { + final docDir = await getApplicationDocumentsDirectory(); + return docDir.path; +} + +/// Checks if a directory exists at the given path. +/// Returns false on web platform. + +Future directoryExists(String path) async { + final dir = Directory(path); + return dir.exists(); +} + +/// Reads a file as a string. +/// Throws on web platform. + +Future readFileAsString(String path) async { + final file = File(path); + return file.readAsString(); +} + +/// Creates a Directory object for watching file changes. +/// Returns null on web platform. + +Stream? watchDirectory(String path) { + return Directory(path).watch(); +} + +/// Creates a File object from a path. +/// Throws on web platform. + +File createFile(String path) { + return File(path); +} diff --git a/lib/src/utils/platform_web.dart b/lib/src/utils/platform_web.dart new file mode 100644 index 0000000..5864db2 --- /dev/null +++ b/lib/src/utils/platform_web.dart @@ -0,0 +1,101 @@ +/// Platform utilities for web platform (stub implementations). +/// +/// Copyright (C) 2026, Software Innovation Institute, ANU. +/// +/// Licensed under the MIT License (the "License"). +/// +/// License: https://choosealicense.com/licenses/mit/. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/// +/// Authors: Tony Chen + +library; + +import 'dart:typed_data'; + +/// Checks if a file exists at the given path. +/// Always returns false on web platform as local file access is not available. + +Future fileExists(String path) async { + return false; +} + +/// Writes bytes to a temporary file and returns the path. +/// Throws UnsupportedError on web platform. + +Future writeBytesToTempFile(String filename, Uint8List bytes) async { + throw UnsupportedError('writeBytesToTempFile is not supported on web'); +} + +/// Gets the temporary directory path. +/// Throws UnsupportedError on web platform. + +Future getTemporaryDirectoryPath() async { + throw UnsupportedError('getTemporaryDirectoryPath is not supported on web'); +} + +/// Returns the operating system name. +/// Returns 'web' on web platform. + +String getOperatingSystem() { + return 'web'; +} + +/// Returns the resolved executable path. +/// Returns empty string on web platform. + +String getResolvedExecutable() { + return ''; +} + +/// Returns the application documents directory path. +/// Returns empty string on web platform. + +Future getApplicationDocumentsPath() async { + return ''; +} + +/// Checks if a directory exists at the given path. +/// Always returns false on web platform. + +Future directoryExists(String path) async { + return false; +} + +/// Reads a file as a string. +/// Throws UnsupportedError on web platform. + +Future readFileAsString(String path) async { + throw UnsupportedError('readFileAsString is not supported on web'); +} + +/// Creates a Directory object for watching file changes. +/// Returns null on web platform as file watching is not supported. + +Stream? watchDirectory(String path) { + return null; +} + +/// Stub for File creation on web platform. +/// Throws UnsupportedError as File is not available on web. + +dynamic createFile(String path) { + throw UnsupportedError('createFile is not supported on web'); +} diff --git a/lib/src/widgets/audio_widget.dart b/lib/src/widgets/audio_widget.dart index 1e1b10e..4621328 100644 --- a/lib/src/widgets/audio_widget.dart +++ b/lib/src/widgets/audio_widget.dart @@ -30,14 +30,19 @@ library; import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:audioplayers/audioplayers.dart'; -import 'package:path_provider/path_provider.dart'; + +// Conditionally import dart:io for non-web platforms. + +import 'package:markdown_widget_builder/src/utils/platform_io.dart' + if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' + as platform_utils; import 'package:markdown_widget_builder/src/constants/pkg.dart' show contentWidthFactor, mediaPath; @@ -87,41 +92,57 @@ class _AudioWidgetState extends State { Future _initAudioPlayer() async { final rawLocalPath = '$mediaPath/${widget.filename}'; - final localFile = File(rawLocalPath); - final isFileExists = await localFile.exists(); final isAssetLike = rawLocalPath.startsWith('assets/') || rawLocalPath.startsWith('assets\\'); - String sourcePath; - if (isFileExists && !isAssetLike) { - sourcePath = rawLocalPath.startsWith('file://') - ? Uri.parse(rawLocalPath).toFilePath() - : rawLocalPath; - } else { - try { - final ByteData data = await rootBundle.load(rawLocalPath); - final Uint8List bytes = data.buffer.asUint8List(); - - final tempDir = await getTemporaryDirectory(); - final fileNameOnly = widget.filename.split('/').last; - final tempPath = '${tempDir.path}/$fileNameOnly'; - - final tempFile = File(tempPath); - await tempFile.writeAsBytes(bytes); - - sourcePath = tempFile.path; - } catch (e) { - _failedToLoad = true; - setState(() {}); - return; + Source audioSource; + + try { + if (kIsWeb) { + // On web, AssetSource expects a path relative to the assets/ directory, + // without the 'assets/' prefix. + + String assetPath = rawLocalPath; + if (assetPath.startsWith('assets/')) { + assetPath = assetPath.substring(7); // Remove 'assets/' prefix. + } else if (assetPath.startsWith('assets\\')) { + assetPath = assetPath.substring(7); // Remove 'assets\\' prefix. + } + audioSource = AssetSource(assetPath); + _sourcePath = assetPath; + } else { + // On non-web platforms, check if file exists locally. + + final isFileExists = await platform_utils.fileExists(rawLocalPath); + + if (isFileExists && !isAssetLike) { + final sourcePath = rawLocalPath.startsWith('file://') + ? Uri.parse(rawLocalPath).toFilePath() + : rawLocalPath; + _sourcePath = sourcePath; + audioSource = DeviceFileSource(_sourcePath!); + } else { + final ByteData data = await rootBundle.load(rawLocalPath); + final Uint8List bytes = data.buffer.asUint8List(); + + final tempPath = await platform_utils.writeBytesToTempFile( + widget.filename, + bytes, + ); + + _sourcePath = tempPath; + audioSource = DeviceFileSource(_sourcePath!); + } } + } catch (e) { + _failedToLoad = true; + setState(() {}); + return; } - _sourcePath = sourcePath; - try { - await _player.setSource(DeviceFileSource(_sourcePath!)); + await _player.setSource(audioSource); } catch (e) { _failedToLoad = true; setState(() {}); @@ -193,7 +214,10 @@ class _AudioWidgetState extends State { // play from the start. if (_sourcePath != null) { - await _player.play(DeviceFileSource(_sourcePath!)); + final source = kIsWeb + ? AssetSource(_sourcePath!) + : DeviceFileSource(_sourcePath!); + await _player.play(source); } } } diff --git a/lib/src/widgets/image_widget.dart b/lib/src/widgets/image_widget.dart index 87bb7e8..6156341 100644 --- a/lib/src/widgets/image_widget.dart +++ b/lib/src/widgets/image_widget.dart @@ -27,14 +27,20 @@ // SOFTWARE. /// /// Authors: Tony Chen + library; -import 'dart:io'; +import 'dart:typed_data'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; -import 'package:path_provider/path_provider.dart'; +// Conditionally import dart:io for non-web platforms. + +import 'package:markdown_widget_builder/src/utils/platform_io.dart' + if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' + as platform_utils; import 'package:markdown_widget_builder/src/constants/pkg.dart' show contentWidthFactor, mediaPath; @@ -57,7 +63,10 @@ class ImageWidget extends StatefulWidget { class ImageWidgetState extends State { String? _localPath; + String? _assetPath; + Uint8List? _imageBytes; bool _failedToLoad = false; + bool _useAsset = false; @override void initState() { @@ -67,28 +76,43 @@ class ImageWidgetState extends State { Future _initializeImage() async { final rawLocalPath = '$mediaPath/${widget.filename}'; - final file = File(rawLocalPath); - final isFileExists = await file.exists(); final isAssetLike = rawLocalPath.startsWith('assets/') || rawLocalPath.startsWith('assets\\'); - if (isFileExists && !isAssetLike) { - _localPath = file.path; - } else { + if (kIsWeb) { + // On web, load image bytes from assets and use Image.memory. + try { final data = await rootBundle.load(rawLocalPath); - final bytes = data.buffer.asUint8List(); - final tempDir = await getTemporaryDirectory(); - final fileNameOnly = widget.filename.split('/').last; - final tempPath = '${tempDir.path}/$fileNameOnly'; - - final tempFile = File(tempPath); - await tempFile.writeAsBytes(bytes); - _localPath = tempFile.path; + _imageBytes = data.buffer.asUint8List(); + _assetPath = rawLocalPath; + _useAsset = true; } catch (e) { _failedToLoad = true; } + } else { + // On non-web platforms, check if file exists locally. + + final isFileExists = await platform_utils.fileExists(rawLocalPath); + + if (isFileExists && !isAssetLike) { + _localPath = rawLocalPath; + } else { + try { + final data = await rootBundle.load(rawLocalPath); + final bytes = data.buffer.asUint8List(); + + final tempPath = await platform_utils.writeBytesToTempFile( + widget.filename, + bytes, + ); + + _localPath = tempPath; + } catch (e) { + _failedToLoad = true; + } + } } setState(() {}); @@ -99,14 +123,37 @@ class ImageWidgetState extends State { if (_failedToLoad) { return const Center(child: Text('Image not found')); } + + if (kIsWeb) { + if (_imageBytes == null) { + return const Center(child: CircularProgressIndicator()); + } + return Center( + child: FractionallySizedBox( + widthFactor: contentWidthFactor, + child: Image.memory( + _imageBytes!, + width: widget.width, + height: widget.height, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return const Text('Image not found'); + }, + ), + ), + ); + } + + // Non-web platforms. + if (_localPath == null) { return const Center(child: CircularProgressIndicator()); } return Center( child: FractionallySizedBox( widthFactor: contentWidthFactor, - child: Image.file( - File(_localPath!), + child: Image( + image: FileImage(platform_utils.createFile(_localPath!)), width: widget.width, height: widget.height, fit: BoxFit.contain, diff --git a/lib/src/widgets/video_widget.dart b/lib/src/widgets/video_widget.dart index effb0a1..4ba71cb 100644 --- a/lib/src/widgets/video_widget.dart +++ b/lib/src/widgets/video_widget.dart @@ -29,15 +29,20 @@ /// Authors: Tony Chen library; -import 'dart:io'; import 'dart:typed_data'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; -import 'package:path_provider/path_provider.dart'; + +// Conditionally import dart:io for non-web platforms. + +import 'package:markdown_widget_builder/src/utils/platform_io.dart' + if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' + as platform_utils; import 'package:markdown_widget_builder/src/constants/pkg.dart' show contentWidthFactor, mediaPath; @@ -71,30 +76,37 @@ class _VideoWidgetState extends State { Future _initializeVideo() async { final rawLocalPath = '$mediaPath/${widget.filename}'; - final localFile = File(rawLocalPath); - final isFileExists = await localFile.exists(); final isAssetLike = rawLocalPath.startsWith('assets/') || rawLocalPath.startsWith('assets\\'); String mediaUri; try { - if (isFileExists && !isAssetLike) { - mediaUri = rawLocalPath.startsWith('file://') - ? Uri.parse(rawLocalPath).toFilePath() - : rawLocalPath; + if (kIsWeb) { + // On web, load directly from assets as a URL. + // media_kit on web uses HTML5 video which can load asset URLs. + + mediaUri = rawLocalPath; } else { - final ByteData data = await rootBundle.load(rawLocalPath); - final Uint8List bytes = data.buffer.asUint8List(); + // On non-web platforms, check if file exists locally. + + final isFileExists = await platform_utils.fileExists(rawLocalPath); - final tempDirPath = (await getTemporaryDirectory()).path; - final fileNameOnly = widget.filename.split('/').last; - final tempPath = '$tempDirPath/$fileNameOnly'; + if (isFileExists && !isAssetLike) { + mediaUri = rawLocalPath.startsWith('file://') + ? Uri.parse(rawLocalPath).toFilePath() + : rawLocalPath; + } else { + final ByteData data = await rootBundle.load(rawLocalPath); + final Uint8List bytes = data.buffer.asUint8List(); - final tempFile = File(tempPath); - await tempFile.writeAsBytes(bytes); + final tempPath = await platform_utils.writeBytesToTempFile( + widget.filename, + bytes, + ); - mediaUri = Uri.file(tempFile.path).toString(); + mediaUri = Uri.file(tempPath).toString(); + } } } catch (e) { _failedToLoad = true; From 880c982730772a826ecb38847ab8456f4d74679b Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Mon, 5 Jan 2026 17:14:35 +1100 Subject: [PATCH 2/2] Lint --- example/lib/main.dart | 21 ++++++++------------- lib/src/utils/file_ops.dart | 9 +++------ lib/src/utils/platform_io.dart | 8 -------- lib/src/utils/platform_web.dart | 7 ------- lib/src/widgets/audio_widget.dart | 7 ++----- lib/src/widgets/image_widget.dart | 11 ++--------- lib/src/widgets/video_widget.dart | 7 ++----- 7 files changed, 17 insertions(+), 53 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 5b6ebc0..5c02aa1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -36,9 +36,6 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:markdown_widget_builder/markdown_widget_builder.dart'; -import 'package:markdown_widget_builder/src/utils/platform_io.dart' - if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' - as platform_utils; void main() { runApp(const MyApp()); @@ -107,19 +104,17 @@ class _MarkdownExamplePageState extends State { setState(() => _markdownContent = content); // Watch file changes (if local path is valid and not on web). + // watchFileChanges returns null on web or if file watching is unavailable. if (!kIsWeb) { final interpretedPath = await interpretPath(config.markdown.path); - final fileExists = await platform_utils.fileExists(interpretedPath); - if (fileExists) { - _fileWatchSub?.cancel(); - _fileWatchSub = watchFileChanges( - interpretedPath, - onFileContentChanged: (newContent) { - setState(() => _markdownContent = newContent); - }, - ); - } + _fileWatchSub?.cancel(); + _fileWatchSub = watchFileChanges( + interpretedPath, + onFileContentChanged: (newContent) { + setState(() => _markdownContent = newContent); + }, + ); } } catch (e) { setState(() => _configLoadError = e); diff --git a/lib/src/utils/file_ops.dart b/lib/src/utils/file_ops.dart index 72619f7..1954969 100644 --- a/lib/src/utils/file_ops.dart +++ b/lib/src/utils/file_ops.dart @@ -38,16 +38,13 @@ import 'package:flutter/services.dart' show rootBundle; import 'package:path/path.dart' as p; -// Conditionally import dart:io for non-web platforms. - -import 'package:markdown_widget_builder/src/utils/platform_io.dart' - if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' - as platform_utils; - import 'package:markdown_widget_builder/markdown_widget_builder.dart' show setMarkdownMediaPath; import 'package:markdown_widget_builder/src/constants/pkg.dart' show defaultConfigFile, mdPath, mediaPath; +import 'package:markdown_widget_builder/src/utils/platform_io.dart' + if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' + as platform_utils; /// Structure of md_config.json. diff --git a/lib/src/utils/platform_io.dart b/lib/src/utils/platform_io.dart index b24e490..662a6f4 100644 --- a/lib/src/utils/platform_io.dart +++ b/lib/src/utils/platform_io.dart @@ -54,14 +54,6 @@ Future writeBytesToTempFile(String filename, Uint8List bytes) async { return tempFile.path; } -/// Gets the temporary directory path. -/// Throws on web platform. - -Future getTemporaryDirectoryPath() async { - final tempDir = await getTemporaryDirectory(); - return tempDir.path; -} - /// Returns the operating system name. /// Returns 'web' on web platform. diff --git a/lib/src/utils/platform_web.dart b/lib/src/utils/platform_web.dart index 5864db2..ea5c102 100644 --- a/lib/src/utils/platform_web.dart +++ b/lib/src/utils/platform_web.dart @@ -44,13 +44,6 @@ Future writeBytesToTempFile(String filename, Uint8List bytes) async { throw UnsupportedError('writeBytesToTempFile is not supported on web'); } -/// Gets the temporary directory path. -/// Throws UnsupportedError on web platform. - -Future getTemporaryDirectoryPath() async { - throw UnsupportedError('getTemporaryDirectoryPath is not supported on web'); -} - /// Returns the operating system name. /// Returns 'web' on web platform. diff --git a/lib/src/widgets/audio_widget.dart b/lib/src/widgets/audio_widget.dart index 4621328..938b982 100644 --- a/lib/src/widgets/audio_widget.dart +++ b/lib/src/widgets/audio_widget.dart @@ -38,15 +38,12 @@ import 'package:flutter/services.dart' show rootBundle; import 'package:audioplayers/audioplayers.dart'; -// Conditionally import dart:io for non-web platforms. - +import 'package:markdown_widget_builder/src/constants/pkg.dart' + show contentWidthFactor, mediaPath; import 'package:markdown_widget_builder/src/utils/platform_io.dart' if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' as platform_utils; -import 'package:markdown_widget_builder/src/constants/pkg.dart' - show contentWidthFactor, mediaPath; - class AudioWidget extends StatefulWidget { final String filename; diff --git a/lib/src/widgets/image_widget.dart b/lib/src/widgets/image_widget.dart index 6156341..141bea5 100644 --- a/lib/src/widgets/image_widget.dart +++ b/lib/src/widgets/image_widget.dart @@ -36,15 +36,12 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; -// Conditionally import dart:io for non-web platforms. - +import 'package:markdown_widget_builder/src/constants/pkg.dart' + show contentWidthFactor, mediaPath; import 'package:markdown_widget_builder/src/utils/platform_io.dart' if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' as platform_utils; -import 'package:markdown_widget_builder/src/constants/pkg.dart' - show contentWidthFactor, mediaPath; - class ImageWidget extends StatefulWidget { final String filename; final double? width; @@ -63,10 +60,8 @@ class ImageWidget extends StatefulWidget { class ImageWidgetState extends State { String? _localPath; - String? _assetPath; Uint8List? _imageBytes; bool _failedToLoad = false; - bool _useAsset = false; @override void initState() { @@ -86,8 +81,6 @@ class ImageWidgetState extends State { try { final data = await rootBundle.load(rawLocalPath); _imageBytes = data.buffer.asUint8List(); - _assetPath = rawLocalPath; - _useAsset = true; } catch (e) { _failedToLoad = true; } diff --git a/lib/src/widgets/video_widget.dart b/lib/src/widgets/video_widget.dart index 4ba71cb..32e5515 100644 --- a/lib/src/widgets/video_widget.dart +++ b/lib/src/widgets/video_widget.dart @@ -38,15 +38,12 @@ import 'package:flutter/services.dart' show rootBundle; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; -// Conditionally import dart:io for non-web platforms. - +import 'package:markdown_widget_builder/src/constants/pkg.dart' + show contentWidthFactor, mediaPath; import 'package:markdown_widget_builder/src/utils/platform_io.dart' if (dart.library.html) 'package:markdown_widget_builder/src/utils/platform_web.dart' as platform_utils; -import 'package:markdown_widget_builder/src/constants/pkg.dart' - show contentWidthFactor, mediaPath; - class VideoWidget extends StatefulWidget { final String filename;