tauri-apps / plugins-workspace

All of the official Tauri plugins in one place!
https://tauri.app
Apache License 2.0
962 stars 270 forks source link

File save dialog is not implemented on mobile #1494

Closed songjiachao closed 2 months ago

songjiachao commented 4 months ago

image

mikoto2000 commented 3 months ago

@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?

FabianLars commented 3 months ago

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.

mikoto2000 commented 3 months ago

@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.

mikoto2000 commented 3 months ago

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 commented 3 months ago

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

mikoto2000 commented 3 months ago

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 commented 3 months ago

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

winjeysong commented 3 months ago

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) });
    // ...
FabianLars commented 2 months ago

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

cc @lucasfernog (your commits)

lucasfernog commented 2 months ago

nice catch, let's close it