diff --git a/packages/path_provider_watchos/CHANGELOG.md b/packages/path_provider_watchos/CHANGELOG.md index de49a7f..8650371 100644 --- a/packages/path_provider_watchos/CHANGELOG.md +++ b/packages/path_provider_watchos/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.1.1 + +* `getDownloadsPath` returns the app's `NSDownloadsDirectory` path, and the + external storage getters throw `UnsupportedError`, as on iOS. Before, all + four threw `UnimplementedError`. +* Example: `path_provider`'s own example app and its official integration + test, unchanged, replace a hand-written demo and a trimmed copy of the + test (`AUTHORING.md` §3). + ## 0.1.0 * The version moves from 0.0.1 to 0.1.0, so that a `^0.1.0` constraint diff --git a/packages/path_provider_watchos/PORTING_REPORT.md b/packages/path_provider_watchos/PORTING_REPORT.md index 89484cc..944001a 100644 --- a/packages/path_provider_watchos/PORTING_REPORT.md +++ b/packages/path_provider_watchos/PORTING_REPORT.md @@ -5,9 +5,9 @@ | Aspect | Result | |---|---| | Implementation | ✅ Working (FFI) | -| watchOS capability | Partial — no Downloads / Android external dirs (throw `UnimplementedError`) | +| watchOS capability | Full, as on iOS — Downloads is the app's `NSDownloadsDirectory` path; the Android-only external directories throw `UnsupportedError` | | Host unit tests (`flutter-watchos test`) | ✅ pass | -| Upstream integration test | ✅ passes verbatim | +| Upstream integration test | ✅ passes verbatim on the watchOS 27.0 Simulator (16 tests, 2026-10-24) | | Internal unified demo | ✅ included | Marking: ✅ full / passes · ◐ partial — reason given · ○ not applicable (no upstream test) · ✗ unsupported on watchOS. @@ -28,6 +28,13 @@ is that skeleton **finished by hand** as a working FFI implementation. `path_provider_platform_interface` and resolves the symbols via `DynamicLibrary.process()`. +## Example + +`path_provider`'s own example app and its official integration test, +ported unchanged with `flutter-watchos plugin port --include-example`. There +are no watchOS deviations. On the watchOS 27.0 Simulator the example starts +with no exception in its first 15 seconds and no overflow. + ## Interface coverage Every public member of `path_provider_platform_interface` 2.1.3 @@ -43,8 +50,8 @@ watchOS" table lists the ✗ rows. | `getLibraryPath` | ✅ `NSLibraryDirectory` | | `getApplicationDocumentsPath` | ✅ `NSDocumentDirectory` | | `getApplicationCachePath` | ✅ `NSCachesDirectory` | -| `getDownloadsPath` | ✗ throws `UnimplementedError`: watchOS has no Downloads directory | -| `getExternalStoragePath` / `getExternalStoragePaths` / `getExternalCachePaths` | ✗ throw `UnimplementedError`: external storage is an Android concept | +| `getDownloadsPath` | ✅ `NSDownloadsDirectory`, as on iOS (not created) | +| `getExternalStoragePath` / `getExternalStoragePaths` / `getExternalCachePaths` | ✗ throw `UnsupportedError`, as on iOS: external storage is an Android concept | --- diff --git a/packages/path_provider_watchos/README.md b/packages/path_provider_watchos/README.md index ccb2249..373c1c7 100644 --- a/packages/path_provider_watchos/README.md +++ b/packages/path_provider_watchos/README.md @@ -34,8 +34,17 @@ These members of `path_provider_platform_interface` 2.1.3 throw or fail on watch | Member | On watchOS | Why | |---|---|---| -| `getDownloadsPath` | throws `UnimplementedError` | watchOS has no Downloads directory | -| `getExternalStoragePath` / `getExternalStoragePaths` / `getExternalCachePaths` | throw `UnimplementedError` | external storage is an Android concept | +| `getExternalStoragePath` / `getExternalStoragePaths` / `getExternalCachePaths` | throw `UnsupportedError`, as on iOS | external storage is an Android concept | + +`getDownloadsPath` returns the app's `NSDownloadsDirectory` path, as on iOS. +The directory is not created. + +## Example + +The example is `path_provider`'s own example app, and +`example/integration_test/path_provider_test.dart` is its official test, +both unchanged, ported with `flutter-watchos plugin port --include-example`. +There are no watchOS deviations. ## License diff --git a/packages/path_provider_watchos/example/integration_test/path_provider_test.dart b/packages/path_provider_watchos/example/integration_test/path_provider_test.dart index 103e14b..c311258 100644 --- a/packages/path_provider_watchos/example/integration_test/path_provider_test.dart +++ b/packages/path_provider_watchos/example/integration_test/path_provider_test.dart @@ -1,13 +1,8 @@ -// Copyright 2026 The FlutterWatch Authors. All rights reserved. +// Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// -// Runs on the watch simulator against the real FFI implementation -// (path_provider_watchos). Adapted from the upstream path_provider example's -// integration_test, trimmed to the directories that exist on watchOS. import 'dart:io'; - import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path_provider/path_provider.dart'; @@ -15,37 +10,136 @@ import 'package:path_provider/path_provider.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('getTemporaryDirectory returns a usable directory', - (WidgetTester _) async { - final Directory dir = await getTemporaryDirectory(); - expect(dir.path, isNotEmpty); - expect(dir.existsSync(), isTrue); + testWidgets('getTemporaryDirectory', (WidgetTester tester) async { + final Directory result = await getTemporaryDirectory(); + // getTemporaryDirectory does not guarantee that the returned directory exists. + _verifySampleFile(result, 'temporaryDirectory', createDirectory: true); + }); + + testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { + final Directory result = await getApplicationDocumentsDirectory(); + if (Platform.isMacOS) { + // _verifySampleFile causes hangs in driver when sandboxing is disabled + // because the path changes from an app specific directory to + // ~/Documents, which requires additional permissions to access on macOS. + // Instead, validate that a non-empty path was returned. + expect(result.path, isNotEmpty); + } else { + _verifySampleFile(result, 'applicationDocuments'); + } + }); + + testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { + final Directory result = await getApplicationSupportDirectory(); + _verifySampleFile(result, 'applicationSupport'); + }); + + testWidgets('getApplicationCacheDirectory', (WidgetTester tester) async { + final Directory result = await getApplicationCacheDirectory(); + _verifySampleFile(result, 'applicationCache'); }); - testWidgets('getApplicationSupportDirectory is created on demand', - (WidgetTester _) async { - final Directory dir = await getApplicationSupportDirectory(); - expect(dir.path, isNotEmpty); - expect(dir.existsSync(), isTrue); + testWidgets('getLibraryDirectory', (WidgetTester tester) async { + if (Platform.isIOS) { + final Directory result = await getLibraryDirectory(); + _verifySampleFile(result, 'library'); + } else if (Platform.isAndroid) { + final Future result = getLibraryDirectory(); + await expectLater(result, throwsA(isInstanceOf())); + } }); - testWidgets('getApplicationDocumentsDirectory returns a path', - (WidgetTester _) async { - final Directory dir = await getApplicationDocumentsDirectory(); - expect(dir.path, isNotEmpty); + testWidgets('getExternalStorageDirectory', (WidgetTester tester) async { + if (Platform.isIOS) { + final Future result = getExternalStorageDirectory(); + await expectLater(result, throwsA(isInstanceOf())); + } else if (Platform.isAndroid) { + final Directory? result = await getExternalStorageDirectory(); + _verifySampleFile(result, 'externalStorage'); + } }); - testWidgets('getApplicationCacheDirectory returns a path', - (WidgetTester _) async { - final Directory dir = await getApplicationCacheDirectory(); - expect(dir.path, isNotEmpty); + testWidgets('getExternalCacheDirectories', (WidgetTester tester) async { + if (Platform.isIOS) { + final Future?> result = getExternalCacheDirectories(); + await expectLater(result, throwsA(isInstanceOf())); + } else if (Platform.isAndroid) { + final List? directories = await getExternalCacheDirectories(); + expect(directories, isNotNull); + for (final Directory result in directories!) { + _verifySampleFile(result, 'externalCache'); + } + } }); - testWidgets('temp directory round-trips a file', (WidgetTester _) async { - final Directory dir = await getTemporaryDirectory(); - final File f = File('${dir.path}/probe.txt'); - await f.writeAsString('hello watch'); - expect(await f.readAsString(), 'hello watch'); - await f.delete(); + final allDirs = [ + null, + StorageDirectory.music, + StorageDirectory.podcasts, + StorageDirectory.ringtones, + StorageDirectory.alarms, + StorageDirectory.notifications, + StorageDirectory.pictures, + StorageDirectory.movies, + ]; + + for (final type in allDirs) { + testWidgets('getExternalStorageDirectories (type: $type)', + (WidgetTester tester) async { + if (Platform.isIOS) { + final Future?> result = getExternalStorageDirectories(); + await expectLater(result, throwsA(isInstanceOf())); + } else if (Platform.isAndroid) { + final List? directories = + await getExternalStorageDirectories(type: type); + expect(directories, isNotNull); + for (final Directory result in directories!) { + _verifySampleFile(result, '$type'); + } + } + }); + } + + testWidgets('getDownloadsDirectory', (WidgetTester tester) async { + final Directory? result = await getDownloadsDirectory(); + // On recent versions of macOS, actually using the downloads directory + // requires a user prompt (so will fail on CI), and on some platforms the + // directory may not exist. Instead of verifying that it exists, just + // check that it returned a path. + expect(result?.path, isNotEmpty); }); } + +/// Verify a file called [name] in [directory] by recreating it with test +/// contents when necessary. +/// +/// If [createDirectory] is true, the directory will be created if missing. +void _verifySampleFile(Directory? directory, String name, + {bool createDirectory = false}) { + expect(directory, isNotNull); + if (directory == null) { + return; + } + final file = File('${directory.path}/$name'); + + if (file.existsSync()) { + file.deleteSync(); + expect(file.existsSync(), isFalse); + } + + if (createDirectory && !directory.existsSync()) { + directory.createSync(recursive: true); + } + + file.writeAsStringSync('Hello world!'); + expect(file.readAsStringSync(), 'Hello world!'); + // This check intentionally avoids using Directory.listSync on Android due to + // https://github.com/dart-lang/sdk/issues/54287. + if (Platform.isAndroid) { + expect( + Process.runSync('ls', [directory.path]).stdout, contains(name)); + } else { + expect(directory.listSync(), isNotEmpty); + } + file.deleteSync(); +} diff --git a/packages/path_provider_watchos/example/lib/main.dart b/packages/path_provider_watchos/example/lib/main.dart index 1f11b4a..d64c324 100644 --- a/packages/path_provider_watchos/example/lib/main.dart +++ b/packages/path_provider_watchos/example/lib/main.dart @@ -1,65 +1,300 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; -void main() => runApp(const ExampleApp()); +void main() { + runApp(const MyApp()); +} -class ExampleApp extends StatelessWidget { - const ExampleApp({super.key}); +class MyApp extends StatelessWidget { + const MyApp({super.key}); @override Widget build(BuildContext context) { - return const MaterialApp(home: PathList()); + return MaterialApp( + title: 'Path Provider', + theme: ThemeData(primarySwatch: Colors.blue), + home: const MyHomePage(title: 'Path Provider'), + ); } } -class PathList extends StatefulWidget { - const PathList({super.key}); +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + final String title; @override - State createState() => _PathListState(); + State createState() => _MyHomePageState(); } -class _PathListState extends State { - final Map _paths = {}; +class _MyHomePageState extends State { + Future? _tempDirectory; + Future? _appSupportDirectory; + Future? _appLibraryDirectory; + Future? _appDocumentsDirectory; + Future? _appCacheDirectory; + Future? _externalDocumentsDirectory; + Future?>? _externalStorageDirectories; + Future?>? _externalCacheDirectories; + Future? _downloadsDirectory; - @override - void initState() { - super.initState(); - _load(); + void _requestTempDirectory() { + setState(() { + _tempDirectory = getTemporaryDirectory(); + }); } - Future _load() async { - Future put(String label, Future Function() f) async { - try { - final Object? dir = await f(); - _paths[label] = dir?.toString() ?? '(null)'; - } catch (e) { - _paths[label] = 'unsupported'; + Widget _buildDirectory( + BuildContext context, AsyncSnapshot snapshot) { + var text = const Text(''); + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + text = Text('Error: ${snapshot.error}'); + } else if (snapshot.hasData) { + text = Text('path: ${snapshot.data!.path}'); + } else { + text = const Text('path unavailable'); } - if (mounted) { - setState(() {}); + } + return Padding(padding: const EdgeInsets.all(16.0), child: text); + } + + Widget _buildDirectories( + BuildContext context, AsyncSnapshot?> snapshot) { + var text = const Text(''); + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + text = Text('Error: ${snapshot.error}'); + } else if (snapshot.hasData) { + final String combined = + snapshot.data!.map((Directory d) => d.path).join(', '); + text = Text('paths: $combined'); + } else { + text = const Text('path unavailable'); } } + return Padding(padding: const EdgeInsets.all(16.0), child: text); + } + + void _requestAppDocumentsDirectory() { + setState(() { + _appDocumentsDirectory = getApplicationDocumentsDirectory(); + }); + } + + void _requestAppSupportDirectory() { + setState(() { + _appSupportDirectory = getApplicationSupportDirectory(); + }); + } + + void _requestAppLibraryDirectory() { + setState(() { + _appLibraryDirectory = getLibraryDirectory(); + }); + } - await put('temp', getTemporaryDirectory); - await put('support', getApplicationSupportDirectory); - await put('library', getLibraryDirectory); - await put('documents', getApplicationDocumentsDirectory); - await put('cache', getApplicationCacheDirectory); + void _requestAppCacheDirectory() { + setState(() { + _appCacheDirectory = getApplicationCacheDirectory(); + }); + } + + void _requestExternalStorageDirectory() { + setState(() { + _externalDocumentsDirectory = getExternalStorageDirectory(); + }); + } + + void _requestExternalStorageDirectories(StorageDirectory type) { + setState(() { + _externalStorageDirectories = getExternalStorageDirectories(type: type); + }); + } + + void _requestExternalCacheDirectories() { + setState(() { + _externalCacheDirectories = getExternalCacheDirectories(); + }); + } + + void _requestDownloadsDirectory() { + setState(() { + _downloadsDirectory = getDownloadsDirectory(); + }); } @override Widget build(BuildContext context) { return Scaffold( - body: ListView( - children: [ - for (final MapEntry e in _paths.entries) - ListTile( - dense: true, - title: Text(e.key, style: const TextStyle(fontSize: 12)), - subtitle: Text(e.value, style: const TextStyle(fontSize: 9)), + appBar: AppBar(title: Text(widget.title)), + body: Center( + child: ListView( + children: [ + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: _requestTempDirectory, + child: const Text('Get Temporary Directory'), + ), + ), + FutureBuilder( + future: _tempDirectory, builder: _buildDirectory), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: _requestAppDocumentsDirectory, + child: const Text('Get Application Documents Directory'), + ), + ), + FutureBuilder( + future: _appDocumentsDirectory, builder: _buildDirectory), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: _requestAppSupportDirectory, + child: const Text('Get Application Support Directory'), + ), + ), + FutureBuilder( + future: _appSupportDirectory, builder: _buildDirectory), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: + Platform.isAndroid ? null : _requestAppLibraryDirectory, + child: Text( + Platform.isAndroid + ? 'Application Library Directory unavailable' + : 'Get Application Library Directory', + ), + ), + ), + FutureBuilder( + future: _appLibraryDirectory, builder: _buildDirectory), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: _requestAppCacheDirectory, + child: const Text('Get Application Cache Directory'), + ), + ), + FutureBuilder( + future: _appCacheDirectory, builder: _buildDirectory), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: !Platform.isAndroid + ? null + : _requestExternalStorageDirectory, + child: Text( + !Platform.isAndroid + ? 'External storage is unavailable' + : 'Get External Storage Directory', + ), + ), + ), + FutureBuilder( + future: _externalDocumentsDirectory, + builder: _buildDirectory, + ), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: !Platform.isAndroid + ? null + : () { + _requestExternalStorageDirectories( + StorageDirectory.music); + }, + child: Text( + !Platform.isAndroid + ? 'External directories are unavailable' + : 'Get External Storage Directories', + ), + ), + ), + FutureBuilder?>( + future: _externalStorageDirectories, + builder: _buildDirectories, + ), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: !Platform.isAndroid + ? null + : _requestExternalCacheDirectories, + child: Text( + !Platform.isAndroid + ? 'External directories are unavailable' + : 'Get External Cache Directories', + ), + ), + ), + FutureBuilder?>( + future: _externalCacheDirectories, + builder: _buildDirectories, + ), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: Platform.isAndroid || Platform.isIOS + ? null + : _requestDownloadsDirectory, + child: Text( + Platform.isAndroid || Platform.isIOS + ? 'Downloads directory is unavailable' + : 'Get Downloads Directory', + ), + ), + ), + FutureBuilder( + future: _downloadsDirectory, builder: _buildDirectory), + ], ), - ], + ], + ), ), ); } diff --git a/packages/path_provider_watchos/example/lib/readme_excerpts.dart b/packages/path_provider_watchos/example/lib/readme_excerpts.dart new file mode 100644 index 0000000..0c85da5 --- /dev/null +++ b/packages/path_provider_watchos/example/lib/readme_excerpts.dart @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:path_provider/path_provider.dart'; + +/// Runs example code for README.md. +Future> readmeSnippets() async { + // #docregion Example + final Directory tempDir = await getTemporaryDirectory(); + + final Directory appDocumentsDir = await getApplicationDocumentsDirectory(); + + final Directory? downloadsDir = await getDownloadsDirectory(); + // #enddocregion Example + + return [tempDir, appDocumentsDir, downloadsDir]; +} diff --git a/packages/path_provider_watchos/lib/path_provider_watchos.dart b/packages/path_provider_watchos/lib/path_provider_watchos.dart index 16dd7ce..c40db3b 100644 --- a/packages/path_provider_watchos/lib/path_provider_watchos.dart +++ b/packages/path_provider_watchos/lib/path_provider_watchos.dart @@ -49,6 +49,10 @@ class PathProviderWatchosBindings { .lookupFunction Function(), Pointer Function()>( 'path_provider_watchos_cache_path'); + late final Pointer Function() _downloadsPath = _lib! + .lookupFunction Function(), Pointer Function()>( + 'path_provider_watchos_downloads_path'); + String? _string(Pointer Function() f) { final Pointer p = f(); // Native pointers are cached per-process and owned by the plugin — never @@ -70,6 +74,9 @@ class PathProviderWatchosBindings { /// `NSCachesDirectory`. String? get cachePath => _string(_cachePath); + + /// `NSDownloadsDirectory`, as iOS path_provider reports it; not created. + String? get downloadsPath => _string(_downloadsPath); } /// watchOS implementation of [PathProviderPlatform]. @@ -104,7 +111,28 @@ base class PathProviderWatchos extends PathProviderPlatform { @override Future getApplicationCachePath() async => _b.cachePath; - // The remaining interface methods (external storage, downloads) describe - // directories that do not exist on watchOS; the base class's - // UnimplementedError defaults are the correct behaviour. + @override + Future getDownloadsPath() async => _b.downloadsPath; + + // External storage is an Android concept. As on iOS (path_provider_foundation), + // these getters throw UnsupportedError. + + @override + Future getExternalStoragePath() async { + throw UnsupportedError( + 'getExternalStoragePath is not supported on watchOS'); + } + + @override + Future?> getExternalCachePaths() async { + throw UnsupportedError('getExternalCachePaths is not supported on watchOS'); + } + + @override + Future?> getExternalStoragePaths({ + StorageDirectory? type, + }) async { + throw UnsupportedError( + 'getExternalStoragePaths is not supported on watchOS'); + } } diff --git a/packages/path_provider_watchos/pubspec.yaml b/packages/path_provider_watchos/pubspec.yaml index 13a757d..7c43447 100644 --- a/packages/path_provider_watchos/pubspec.yaml +++ b/packages/path_provider_watchos/pubspec.yaml @@ -1,6 +1,6 @@ name: path_provider_watchos description: "watchOS implementation of the path_provider plugin, using dart:ffi over Foundation to resolve the temporary, application-support, documents, and cache directories." -version: 0.1.0 +version: 0.1.1 homepage: https://flutterwatch.dev repository: https://github.com/flutterwatch/plugins/tree/main/packages/path_provider_watchos issue_tracker: https://github.com/flutterwatch/plugins/issues @@ -49,3 +49,4 @@ flutter: - path_provider_watchos_library_path - path_provider_watchos_documents_path - path_provider_watchos_cache_path + - path_provider_watchos_downloads_path diff --git a/packages/path_provider_watchos/test/path_provider_watchos_test.dart b/packages/path_provider_watchos/test/path_provider_watchos_test.dart index b8cec5b..5889c86 100644 --- a/packages/path_provider_watchos/test/path_provider_watchos_test.dart +++ b/packages/path_provider_watchos/test/path_provider_watchos_test.dart @@ -20,6 +20,8 @@ class _FakeBindings extends PathProviderWatchosBindings { String? get documentsPath => '/sandbox/Documents'; @override String? get cachePath => '/sandbox/Library/Caches'; + @override + String? get downloadsPath => '/sandbox/Downloads'; } void main() { @@ -40,11 +42,21 @@ void main() { expect(await provider.getLibraryPath(), '/sandbox/Library'); expect(await provider.getApplicationDocumentsPath(), '/sandbox/Documents'); expect(await provider.getApplicationCachePath(), '/sandbox/Library/Caches'); + expect(await provider.getDownloadsPath(), '/sandbox/Downloads'); }); - test('watchOS-inapplicable directories keep throwing defaults', () { + test('the external storage getters throw UnsupportedError, as on iOS', + () async { final PathProviderWatchos provider = PathProviderWatchos(); - expect(provider.getDownloadsPath, throwsUnimplementedError); - expect(provider.getExternalStoragePath, throwsUnimplementedError); + await expectLater( + provider.getExternalStoragePath(), throwsUnsupportedError); + await expectLater(provider.getExternalCachePaths(), throwsUnsupportedError); + for (final StorageDirectory? type in [ + null, + ...StorageDirectory.values, + ]) { + await expectLater( + provider.getExternalStoragePaths(type: type), throwsUnsupportedError); + } }); } diff --git a/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.h b/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.h index 11869b6..70aa811 100644 --- a/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.h +++ b/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.h @@ -38,4 +38,9 @@ PATH_PROVIDER_WATCHOS_EXPORT const char* path_provider_watchos_documents_path(vo /// `NSCachesDirectory` in the user domain. PATH_PROVIDER_WATCHOS_EXPORT const char* path_provider_watchos_cache_path(void); +/// `NSDownloadsDirectory` in the user domain, as +/// `NSSearchPathForDirectoriesInDomains` reports it. This is the path iOS +/// path_provider returns; the directory is not created. +PATH_PROVIDER_WATCHOS_EXPORT const char* path_provider_watchos_downloads_path(void); + #endif // PATH_PROVIDER_WATCHOS_FFI_H diff --git a/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.m b/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.m index 6823c3c..438a6e5 100644 --- a/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.m +++ b/packages/path_provider_watchos/watchos/Classes/path_provider_watchos_ffi.m @@ -83,3 +83,20 @@ }); return path; } + +const char* path_provider_watchos_downloads_path(void) { + static const char* path = NULL; + static dispatch_once_t once; + dispatch_once(&once, ^{ + @autoreleasepool { + // The lookup iOS path_provider makes: the user-domain path, + // whether or not the directory exists yet. + NSString* downloads = NSSearchPathForDirectoriesInDomains( + NSDownloadsDirectory, NSUserDomainMask, YES).firstObject; + if (downloads != nil) { + path = strdup(downloads.UTF8String); + } + } + }); + return path; +}