From 03a6e892f77e5231dfa6199596641184d8cbb18b Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 18 Mar 2021 20:03:42 +0000 Subject: [PATCH] First pass for iOS file save Working for 'On this Phone' file locations. Seems to have an issue with DropBox export. Fixes #2076 --- go.mod | 2 +- go.sum | 4 +- internal/driver/gomobile/file_ios.go | 39 ++++++++++++++++++- internal/driver/gomobile/file_ios.m | 11 ++++++ .../fyne-io/mobile/app/darwin_ios.go | 33 ++++++++++------ .../fyne-io/mobile/app/darwin_ios.m | 33 ++++++++++++++-- vendor/modules.txt | 2 +- 7 files changed, 104 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 155caf609d..b3f2d40bfd 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/akavel/rsrc v0.8.0 // indirect github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 github.com/fsnotify/fsnotify v1.4.9 - github.com/fyne-io/mobile v0.1.3-0.20210312180903-f9a21000f5dc + github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 github.com/godbus/dbus/v5 v5.0.3 diff --git a/go.sum b/go.sum index 166547191f..a0be01e87a 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d h1:WfVxpuVm+5Gr3ipAoWrxV8lJFYkaBWoEwFRrWThWRSU= github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/fyne-io/mobile v0.1.3-0.20210312180903-f9a21000f5dc h1:A5hFL3tVUfFHhVpjmLGPs8SdVIcSkTRpFMm1Vsio5+k= -github.com/fyne-io/mobile v0.1.3-0.20210312180903-f9a21000f5dc/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= +github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI= +github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= diff --git a/internal/driver/gomobile/file_ios.go b/internal/driver/gomobile/file_ios.go index b08c0ca46d..d399832157 100644 --- a/internal/driver/gomobile/file_ios.go +++ b/internal/driver/gomobile/file_ios.go @@ -10,10 +10,10 @@ package gomobile void* iosParseUrl(const char* url); const void* iosReadFromURL(void* url, int* len); +const int iosWriteToURL(void* url, const void* bytes, int len); */ import "C" import ( - "errors" "io" "unsafe" @@ -64,6 +64,31 @@ func (s *secureReadCloser) Close() error { return nil } +type secureWriteCloser struct { + url unsafe.Pointer + closer func() + + offset int +} + +// Declare conformity to WriteCloser interface +var _ io.WriteCloser = (*secureWriteCloser)(nil) + +func (s *secureWriteCloser) Write(p []byte) (int, error) { + count := int(C.iosWriteToURL(s.url, C.CBytes(p), C.int(len(p)))) + s.offset += count + + return count, nil +} + +func (s *secureWriteCloser) Close() error { + if s.closer != nil { + s.closer() + } + s.url = nil + return nil +} + func nativeFileOpen(f *fileOpen) (io.ReadCloser, error) { if f.uri == nil || f.uri.String() == "" { return nil, nil @@ -79,7 +104,17 @@ func nativeFileOpen(f *fileOpen) (io.ReadCloser, error) { } func nativeFileSave(f *fileSave) (io.WriteCloser, error) { - return nil, errors.New("Currently unsupported.") + if f.uri == nil || f.uri.String() == "" { + return nil, nil + } + + cStr := C.CString(f.uri.String()) + defer C.free(unsafe.Pointer(cStr)) + + url := C.iosParseUrl(cStr) + + fileStruct := &secureWriteCloser{url: url, closer: f.done} + return fileStruct, nil } func registerRepository(d *mobileDriver) { diff --git a/internal/driver/gomobile/file_ios.m b/internal/driver/gomobile/file_ios.m index 864e2093d4..613cf0a228 100644 --- a/internal/driver/gomobile/file_ios.m +++ b/internal/driver/gomobile/file_ios.m @@ -16,3 +16,14 @@ *len = data.length; return data.bytes; } + +const int iosWriteToURL(void* urlPtr, const void* bytes, int len) { + NSURL* url = (NSURL*)urlPtr; + NSData *data = [NSData dataWithBytes:bytes length:len]; + BOOL ok = [data writeToURL:url atomically:YES]; + + if (!ok) { + return 0; + } + return data.length; +} diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go index 7c82b50d27..dc30a97438 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go +++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go @@ -28,6 +28,7 @@ void showKeyboard(int keyboardType); void hideKeyboard(); void showFileOpenPicker(char* mimes, char *exts); +void showFileSavePicker(char* mimes, char *exts); void closeFileResource(void* urlPtr); */ import "C" @@ -258,6 +259,19 @@ func (a *app) loop(ctx C.GLintptr) { } } +func cStringsForFilter(filter *FileFilter) (*C.char, *C.char) { + mimes := strings.Join(filter.MimeTypes, "|") + + // extensions must have the '.' removed for UTI lookups on iOS + extList := []string{} + for _, ext := range filter.Extensions { + extList = append(extList, ext[1:]) + } + exts := strings.Join(extList, "|") + + return C.CString(mimes), C.CString(exts) +} + // driverShowVirtualKeyboard requests the driver to show a virtual keyboard for text input func driverShowVirtualKeyboard(keyboard KeyboardType) { C.showKeyboard(C.int(int32(keyboard))) @@ -285,22 +299,19 @@ func filePickerReturned(str *C.char, urlPtr unsafe.Pointer) { func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) { fileCallback = callback - mimes := strings.Join(filter.MimeTypes, "|") - - // extensions must have the '.' removed for UTI lookups on iOS - extList := []string{} - for _, ext := range filter.Extensions { - extList = append(extList, ext[1:]) - } - exts := strings.Join(extList, "|") - - mimeStr := C.CString(mimes) + mimeStr, extStr := cStringsForFilter(filter) defer C.free(unsafe.Pointer(mimeStr)) - extStr := C.CString(exts) defer C.free(unsafe.Pointer(extStr)) C.showFileOpenPicker(mimeStr, extStr) } func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter) { + fileCallback = callback + + mimeStr, extStr := cStringsForFilter(filter) + defer C.free(unsafe.Pointer(mimeStr)) + defer C.free(unsafe.Pointer(extStr)) + + C.showFileSavePicker(mimeStr, extStr) } diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_ios.m b/vendor/github.com/fyne-io/mobile/app/darwin_ios.m index 7e9441a4a5..0081dc8630 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.m +++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.m @@ -294,9 +294,7 @@ void hideKeyboard() { }); } -void showFileOpenPicker(char* mimes, char *exts) { - GoAppAppDelegate *appDelegate = (GoAppAppDelegate *)[[UIApplication sharedApplication] delegate]; - +NSMutableArray *docTypesForMimeExts(char *mimes, char *exts) { NSMutableArray *docTypes = [NSMutableArray array]; if (mimes != NULL && strlen(mimes) > 0) { NSString *mimeList = [NSString stringWithUTF8String:mimes]; @@ -325,6 +323,14 @@ void showFileOpenPicker(char* mimes, char *exts) { [docTypes addObject:@"public.data"]; } + return docTypes; +} + +void showFileOpenPicker(char* mimes, char *exts) { + GoAppAppDelegate *appDelegate = (GoAppAppDelegate *)[[UIApplication sharedApplication] delegate]; + + NSMutableArray *docTypes = docTypesForMimeExts(mimes, exts); + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:docTypes inMode:UIDocumentPickerModeOpen]; documentPicker.delegate = appDelegate; @@ -334,6 +340,27 @@ void showFileOpenPicker(char* mimes, char *exts) { }); } +void showFileSavePicker(char* mimes, char *exts) { + GoAppAppDelegate *appDelegate = (GoAppAppDelegate *)[[UIApplication sharedApplication] delegate]; + + NSMutableArray *docTypes = docTypesForMimeExts(mimes, exts); + + NSURL *temporaryDirectoryURL = [NSURL fileURLWithPath: NSTemporaryDirectory() isDirectory: YES]; + NSURL *temporaryFileURL = [temporaryDirectoryURL URLByAppendingPathComponent:@"filename"]; + + char* bytes = "\n"; + NSData *data = [NSData dataWithBytes:bytes length:1]; + BOOL ok = [data writeToURL:temporaryFileURL atomically:YES]; + + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] + initWithURL:temporaryFileURL inMode:UIDocumentPickerModeMoveToService]; + documentPicker.delegate = appDelegate; + + dispatch_async(dispatch_get_main_queue(), ^{ + [appDelegate.controller presentViewController:documentPicker animated:YES completion:nil]; + }); +} + void closeFileResource(void* urlPtr) { if (urlPtr == NULL) { return; diff --git a/vendor/modules.txt b/vendor/modules.txt index 49f9dc5234..79a1e924fd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/davecgh/go-spew/spew github.com/fredbi/uri # github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify -# github.com/fyne-io/mobile v0.1.3-0.20210312180903-f9a21000f5dc +# github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f github.com/fyne-io/mobile/app github.com/fyne-io/mobile/app/internal/callfn github.com/fyne-io/mobile/event/key