plugins-workspace icon indicating copy to clipboard operation
plugins-workspace copied to clipboard

File save dialog is not implemented on mobile

Open songjiachao opened this issue 1 year ago • 8 comments

image

songjiachao avatar Jun 24 '24 11:06 songjiachao

@songjiachao @FabianLars In my application, I implemented the Android save dialog like this:

https://github.com/mikoto2000/OASIZ_TimeLogger2/commit/aea157f5abab98995a84455a3014b5620be60568

Are there any good ideas for applying this to the save method of the dialog plugin?

mikoto2000 avatar Jul 23 '24 17:07 mikoto2000

In the best case, you just have to duplicate showFilePicker and modify it to your code. Since the rest of the android implementation is a bit buggy though i don't know how good that'll work.

FabianLars avatar Jul 24 '24 08:07 FabianLars

@FabianLars Thank you for your comment. I didn't realize that open was partially supported with Android. It looks like I can create save by imitating the code below. I'll give it a try.

  • https://github.com/tauri-apps/plugins-workspace/blob/20a1d24ee004e77c2d12a0e20d258ce120216ed1/plugins/dialog/android/src/main/java/DialogPlugin.kt#L49
  • https://github.com/tauri-apps/plugins-workspace/blob/20a1d24ee004e77c2d12a0e20d258ce120216ed1/plugins/dialog/src/mobile.rs#L77
  • https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/dialog/src/lib.rs#L406
  • https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/dialog/src/lib.rs#L517
  • https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/dialog/src/commands.rs#L172

mikoto2000 avatar Jul 24 '24 09:07 mikoto2000

tauri-plugin-dialog-android_001 I implemented the part where the save dialog opens and the path is returned, but it seems that in Android, the file is created as soon as the file name is specified in the dialog...

Next, I need to create an Android version of the fs plugin's writeFile, otherwise this function itself will be meaningless.

Since it is necessary to obtain the OutputStream of the file entity from the URI using the code activity.getContentResolver().openOutputStream(uri), it is not possible to create a file in the Rust world.

Huh? Isn't there already a function to open a file on an Android device? The implementation of that seems to be helpful.

mikoto2000 avatar Jul 24 '24 15:07 mikoto2000

open used FilePickerUtils.getPathFromUri(activity, uri), I got real file path. image

mikoto2000 avatar Jul 25 '24 14:07 mikoto2000

I try writeTextFile with real file path, and I got this error.

E Tauri/Console: File: http://tauri.localhost/assets/index-Bg3l_cvu.js - Line 40 - Msg: Uncaught (in promise) failed to open file at path: /storage/emulated/0/Download/test.txt with error: Permission denied (os error 13)

It seems necessary to do activity.getContentResolver().openOutputStream(uri) on the Android layer.

mikoto2000 avatar Jul 25 '24 15:07 mikoto2000

tauri-plugin-dialog-android_002 We have successfully implemented the minimum version. We will organize the code and create a pull request.

mikoto2000 avatar Jul 26 '24 15:07 mikoto2000

I just temporarily implemented saving image to iOS Photos library. You can use code below before tauri-plugin-dialog implementing save. If necessary modify code below to save file to iOS Files

  1. run cargo add cc --build under src-tauri to add cc crate

  2. create ios_file.rs under src-tauri/src

    // src-tauri/src/ios_files.rs
    
    extern "C" {
        fn save_photo_to_photos(content: *const u8, length: usize);
    }
    
    pub fn save_photo(content: &[u8]) {
        unsafe {
            save_photo_to_photos(content.as_ptr(), content.len());
        }
    }
    
  3. create save_photo.m under src-tauri/src

    // src-tauri/src/save_photo.m
    
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    #import <Photos/Photos.h>
    
    void save_photo_to_photos(const uint8_t *content, size_t length) {
        NSData *imageData = [NSData dataWithBytes:content length:length];
        UIImage *image = [UIImage imageWithData:imageData];
    
        if (image == nil) {
            NSLog(@"Failed to create image from data");
            return;
        }
    
        [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
            PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
        } completionHandler:^(BOOL success, NSError * _Nullable error) {
            if (!success) {
                NSLog(@"Error saving image to Photos: %@", [error localizedDescription]);
            } else {
                NSLog(@"Image saved to Photos successfully!");
            }
        }];
    }
    
  4. compile objective-c code

    // src-tauri/build.rs
    
    fn main() {
        tauri_build::build();
    
        //
        cc::Build::new()
            .file("src/save_photo.m")
            .compile("libsave_photo.a");
        println!("cargo:rustc-link-lib=framework=Photos");
    }
    
  5. use ios_files::save_photo & register save_image_to_photos with tauri command

    //  src-tauri/src/lib.rs
    // ...
    
    #[cfg(target_os = "ios")]
    mod ios_files;
    
    #[cfg(target_os = "ios")]
    #[tauri::command]
    fn save_image_to_photos(content: Vec<u8>) {
        ios_files::save_photo(&content);
    }
    
    #[cfg_attr(mobile, tauri::mobile_entry_point)]
    pub fn run() {
        tauri::Builder::default()
             // ...
            .invoke_handler(tauri::generate_handler![save_image_to_photos])
            .run(tauri::generate_context!())
            .expect("error while running tauri application");
    }
    
  6. create src-tauri/Info.plist

     <?xml version="1.0" encoding="UTF-8"?>
     <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
     <plist version="1.0">
     <dict>
         <key>NSPhotoLibraryAddUsageDescription</key>
         <string>Need access to your photo library.</string>
     </dict>
     </plist>
    
  7. invoke save_image_to_photos in webview

    import { invoke } from '@tauri-apps/api/core';
    
    // ...
    const bin = new Uint8Array([]);
    invoke('save_image_to_photos', { content: Array.from(bin) });
    // ...
    

winjeysong avatar Aug 12 '24 10:08 winjeysong

I see commits for iOS and Android in the commit history. Is there anything missing or can this be closed?

cc @lucasfernog (your commits)

FabianLars avatar Sep 11 '24 13:09 FabianLars

nice catch, let's close it

lucasfernog avatar Sep 11 '24 13:09 lucasfernog