Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

🤩 Multiple Image Picker V.2.0 Release 🚀 #180

Merged
merged 75 commits into from
Dec 13, 2024
Merged
Changes from 1 commit
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
cce00e1
✨ feat: convert to HXPhotoPicker && using nitro module
baronha Oct 23, 2024
de1b9d7
✨ feat: doing nitro modules
baronha Oct 23, 2024
ee63bb3
✨ feat: add editor for picker
baronha Oct 24, 2024
e099257
✨ feat: done iOS
baronha Oct 24, 2024
db8afbd
✨ feat: config nitro on Android still very very bugs
baronha Oct 26, 2024
56c5e65
✨ migrate to com.margelo.nitro.multipleimagepicker
baronha Oct 27, 2024
d24cbf7
🐛 bug: .R on Android
baronha Oct 27, 2024
48e4d12
✨ feat: upgrade nitro modules 0.14.0
baronha Oct 30, 2024
fe83d76
✨ feat: upgrade nitro 0.17 && improve Picture Selector on Android
baronha Nov 27, 2024
9484dab
✨ feat: add more android
baronha Nov 28, 2024
5caab21
✨ feat: upgrade nitro 0.18 and add more drawable for android xml
baronha Nov 28, 2024
5be0f94
✨ feat: Add more option for Android and save status for asset
baronha Nov 28, 2024
09e8d7a
✨ feat: completed android
baronha Nov 29, 2024
f26d2d1
🐛 fix: handle empty selected
baronha Nov 29, 2024
267e74a
✨ feat: add /example
baronha Dec 2, 2024
a526143
✨ feat: upgrade nitro 0.18.1
baronha Dec 2, 2024
b92d6a4
✨ feat: handle selected assets
baronha Dec 3, 2024
a08b1de
♻️ refactor: remove originalPath
baronha Dec 3, 2024
de829aa
✨ feat: add light mode for iOS
baronha Dec 3, 2024
3159d21
✨ feat: Add button close light mode for iOS
baronha Dec 4, 2024
43753d4
✨ feat: custom more language for Android
baronha Dec 4, 2024
db04a71
♻️ refactor: convert to Language Code
baronha Dec 4, 2024
99b9cb8
♻️ refactor: remove max photo config
baronha Dec 4, 2024
e4d6d85
✨ feat: double check language
baronha Dec 4, 2024
f759ca8
✨ feat: add video quality on android
baronha Dec 4, 2024
dd2259c
✨ feat: add JSDoc for Typescript
baronha Dec 4, 2024
8b9161b
🐛 fix: solved conflict nitro type options
baronha Dec 5, 2024
b881413
✨ feat: Docusaurus
baronha Dec 5, 2024
39c1cf0
Merge pull request #173 from baronha/nitro
baronha Dec 5, 2024
9540401
✨ feat: adding docusaurus
baronha Dec 5, 2024
1358875
✨ feat: add config.mdx
baronha Dec 5, 2024
ad82858
✨ feat: add RESULT.mdx
baronha Dec 5, 2024
802afdf
✨ feat: doing add docs for getting started
baronha Dec 5, 2024
4505bda
✨ feat: migrate docs -> @gorhom/docusaurus-preset
baronha Dec 5, 2024
df9f484
✨ feat: add patch file for doc
baronha Dec 5, 2024
02a379f
🐛 fix: remove isShowAssetNumber option
baronha Dec 6, 2024
0134976
✨ feat: add more document
baronha Dec 6, 2024
52ba145
✨ feat: done /docs and refactor /example
baronha Dec 6, 2024
8f19472
✨ feat: add more component for /example
baronha Dec 6, 2024
c03d614
✨ feat: add more library for /example
baronha Dec 6, 2024
613b665
✨ feat: add more section /example
baronha Dec 6, 2024
e9db6ae
✨ feat: add more option for /example
baronha Dec 6, 2024
caf1c8f
✨ feat: add more option for /example
baronha Dec 7, 2024
1b7706f
✨ feat: add more option
baronha Dec 7, 2024
86aa84a
✨ feat: add minDuration for Android, add language on /example
baronha Dec 8, 2024
9232689
✨ feat: add minDuration, maxDuration
baronha Dec 8, 2024
7a98c6d
✨ feat: improve README
baronha Dec 9, 2024
ef54484
Update README.md
baronha Dec 9, 2024
31e4710
Update README.md
baronha Dec 9, 2024
309def8
feat: add video player document
baronha Dec 9, 2024
c2c50e8
✨ feat: deploy docs website
baronha Dec 9, 2024
ad5ea32
🚑 fix: document
baronha Dec 9, 2024
0362f9c
🚑 fix: Document Website
baronha Dec 9, 2024
dd12899
🚑 Fix: fix onPress Image /example
baronha Dec 9, 2024
40fbf0e
✨ feat: yarn instead of npm
baronha Dec 9, 2024
3ba760c
🚑 fix: base url docusaurus
baronha Dec 9, 2024
8fd06c6
🐛 fix: add README for example && fix document
baronha Dec 9, 2024
d2e8726
✨ feat: change editUrl
baronha Dec 9, 2024
d3cc6df
✨ feat: add more document
baronha Dec 9, 2024
8187c65
Merge pull request #183 from baronha/feature/open-crop
baronha Dec 9, 2024
604d85d
✨ feat: add custom crop ratio
baronha Dec 10, 2024
bed75ea
✨ feat: add more option for crop, ratio, default ratio for both platform
baronha Dec 11, 2024
9227ce5
✨ feat: add more option for crop, ratio, default ratio for both platform
baronha Dec 11, 2024
677b316
✨ feat: add check image for cropper
baronha Dec 11, 2024
3693506
Merge pull request #184 from baronha/feature/open-crop
baronha Dec 11, 2024
5297307
✨ feat: done open-crop
baronha Dec 11, 2024
a996d84
✨ feat: done handle preview on iOS
baronha Dec 12, 2024
c34170f
✨ feat: done open preview on Android
baronha Dec 12, 2024
f0b513e
♻️ refactor: change params openPreview | improve GIF on iOS
baronha Dec 12, 2024
ec5ba02
✨ feat: add doc for Preview
baronha Dec 12, 2024
9359353
Merge pull request #186 from baronha/feature/open-preview
baronha Dec 12, 2024
e50038b
⏪ fix: Lost code on Preview Android
baronha Dec 12, 2024
f6a44ab
♻️ refactor: remove compress when picker -> Too Slow
baronha Dec 13, 2024
b3244ef
✨ feat: add camera config, remove allowedCamera props
baronha Dec 13, 2024
c7bd60e
✨ feat: release 2.0.0
baronha Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
✨ feat: add editor for picker
  • Loading branch information
baronha committed Oct 24, 2024
commit ee63bb337890577d98dbc96eed861279abab9939
1 change: 1 addition & 0 deletions MultipleImagePicker.podspec
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ Pod::Spec.new do |s|
]

s.dependency "HXPhotoPicker/Picker/Lite", "4.2.3"
s.dependency "HXPhotoPicker/Editor/Lite", "4.2.3"

s.pod_target_xcconfig = {
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
39 changes: 35 additions & 4 deletions ios/HybridMultipleImagePicker+Config.swift
Original file line number Diff line number Diff line change
@@ -50,9 +50,6 @@ extension HybridMultipleImagePicker {
photoList.allowAddLimit = allowedLimit
}

config.videoSelectionTapAction = .quickSelect
config.photoSelectionTapAction = .quickSelect

// check media type
switch options.mediaType {
case .image:
@@ -110,14 +107,45 @@ extension HybridMultipleImagePicker {

config.navigationTitleColor = .systemBackground

if let isPreview = options.isPreview {
let isPreview = options.isPreview

if let isPreview {
config.previewView.bottomView.isShowPreviewList = isPreview
config.photoList.bottomView.isHiddenPreviewButton = !isPreview
config.photoList.allowHapticTouchPreview = !isPreview
config.photoList.bottomView.previewListTickColor = .clear
config.photoList.bottomView.isShowSelectedView = isPreview

if isPreview {
config.videoSelectionTapAction = .preview
config.photoSelectionTapAction = .preview
} else {
config.videoSelectionTapAction = .quickSelect
config.photoSelectionTapAction = .quickSelect
}
}

if let crop = options.crop {
config.editorOptions = [.photo, .gifPhoto, .livePhoto]

var editor = config.editor

editor.cropSize.isRoundCrop = crop.circle ?? false

editor.photo.defaultSelectedToolOption = .cropSize
editor.toolsView = .init(toolOptions: [.init(imageType: config.editor.imageResource.editor.tools.cropSize, type: .cropSize)])

editor.isFixedCropSizeState = true
editor.cropSize.isFixedRatio = true

config.editor = editor

} else {
config.previewView.bottomView.isHiddenEditButton = true
}

config.photoList.finishSelectionAfterTakingPhoto = true

setLanguage(options)

switch Int(options.presentation.rawValue) {
@@ -136,10 +164,13 @@ extension HybridMultipleImagePicker {
if let text = options.text {
if let finish = text.finish {
config.textManager.picker.photoList.bottomView.finishTitle = .custom(finish)
config.textManager.picker.preview.bottomView.finishTitle = .custom(finish)
config.editor.textManager.editor.crop.maskListFinishTitle = .custom(finish)
}

if let original = text.original {
config.textManager.picker.photoList.bottomView.originalTitle = .custom(original)
config.textManager.picker.preview.bottomView.originalTitle = .custom(original)
}

if let preview = text.preview {
43 changes: 43 additions & 0 deletions ios/HybridMultipleImagePicker+Result.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// HybridMultipleImagePicker+Result.swift
// Pods
//
// Created by BAO HA on 24/10/24.
//

import HXPhotoPicker
import Photos

extension HybridMultipleImagePicker {
func getResult(_ asset: PhotoAsset, _ url: URL) -> Result {
let creationDate = Int(asset.phAsset?.creationDate?.timeIntervalSince1970 ?? 0)

let mime = url.getMimeType()

let fileName = {
if let phAsset = asset.phAsset, let resources = PHAssetResource.assetResources(for: phAsset).first {
return resources.originalFilename
}

return ""
}()

let type: ResultType? = .init(fromString: asset.mediaType == .video ? "video" : "image") ?? nil
let thumbnail = asset.phAsset?.getVideoThumbnail(from: url.absoluteString, in: 1)

return Result(path: url.absoluteString,
fileName: fileName,
localIdentifier: asset.localAssetIdentifier,
width: asset.imageSize.width,
height: asset.imageSize.height,
mime: mime,
size: Double(asset.fileSize),
bucketId: nil,
realPath: nil,
parentFolderName: nil,
creationDate: creationDate > 0 ? Double(creationDate) : nil,
type: type,
duration: asset.videoDuration,
thumbnail: thumbnail)
}
}
73 changes: 39 additions & 34 deletions ios/HybridMultipleImagePicker.swift
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
import Foundation
import HXPhotoPicker
import NitroModules
import Photos

class HybridMultipleImagePicker: HybridMultipleImagePickerSpec {
var hybridContext = margelo.nitro.HybridContext()
@@ -37,48 +38,58 @@ class HybridMultipleImagePicker: HybridMultipleImagePickerSpec {
return 10
}()

// add loading view
let alert = UIAlertController(title: nil, message: "Loading...", preferredStyle: .alert)
func onFinish() {
// show alert view
let alert = UIAlertController(title: nil, message: "Loading...", preferredStyle: .alert)

alert.showLoading()
alert.showLoading()

controller.present(alert, animated: true)
controller.present(alert, animated: true)

controller.autoDismiss = false
controller.autoDismiss = false

let compression: PhotoAsset.Compression = .init(imageCompressionQuality: imageQuality, videoExportParameter: .init(preset: .highQuality, quality: videoQuality))
let compression: PhotoAsset.Compression = .init(imageCompressionQuality: imageQuality, videoExportParameter: .init(preset: .highQuality, quality: videoQuality))

var data: [Result] = []
var data: [Result] = []

let group = DispatchGroup()
let group = DispatchGroup()

pickerResult.photoAssets.forEach { photo in
Task {
group.enter()
let assetResult = try await photo.urlResult(compression)
photo.getImageData { result in
switch result {
case .success(let data):
print("data: ", data.dataUTI)
case .failure:
break
pickerResult.photoAssets.forEach { response in
Task {
group.enter()

let urlResult = try await response.urlResult(compression)

response.getImageData { result in
switch result {
case .success(let imageData):

let resultData = self.getResult(response, urlResult.url)

data.append(resultData)

case .failure:
break
}
}
}
// let result = self.getResult(photo, assetURLResult: assetResult)
//
// data.append(result)

group.leave()
group.leave()
}
}
}

group.notify(queue: .main) {
DispatchQueue.main.async {
alert.dismiss(animated: true) {
controller.dismiss(true)
group.notify(queue: .main) {
DispatchQueue.main.async {
alert.dismiss(animated: true) {
controller.dismiss(true)
resolved(data)
}
}
}
}

if config.singleSelectedMode

onFinish()

} cancel: { cancel in

@@ -91,12 +102,6 @@ class HybridMultipleImagePicker: HybridMultipleImagePickerSpec {
}
}

extension HybridMultipleImagePicker {
func getResult(_ asset: PhotoAsset, assetURLResult: AssetURLResult) -> Result {
return Result(path: assetURLResult.url.absoluteString, fileName: "file", localIdentifier: asset.localAssetIdentifier, width: asset.imageSize.width, height: asset.imageSize.height, mime: "", size: Double(asset.fileSize), bucketId: nil, realPath: nil, parentFolderName: nil, creationDate: asset.phAsset?.creationDate.)
}
}

extension UIAlertController {
func showLoading() {
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
57 changes: 57 additions & 0 deletions ios/PHAsset+Thumbnail.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// PHAsset+Thumbnail.swift
// Pods
//
// Created by BAO HA on 24/10/24.
//

import Photos

extension PHAsset {
func getVideoThumbnail(from moviePath: String, in seconds: Double) -> String? {
let filepath = moviePath.replacingOccurrences(of: "file://", with: "")
let vidURL = URL(fileURLWithPath: filepath)

let asset = AVURLAsset(url: vidURL, options: nil)
let generator = AVAssetImageGenerator(asset: asset)
generator.appliesPreferredTrackTransform = true

let time = CMTime(seconds: seconds, preferredTimescale: 600)

var thumbnail: UIImage?

do {
let imgRef = try generator.copyCGImage(at: time, actualTime: nil)
thumbnail = UIImage(cgImage: imgRef)
} catch {
print("Lỗi khi tạo thumbnail: \(error)")
return nil
}

if let thumbnail {
return getImagePathFromUIImage(uiImage: thumbnail, prefix: "thumb")
}

return nil
}

private func getImagePathFromUIImage(uiImage: UIImage, prefix: String? = "thumb") -> String? {
let fileManager = FileManager.default

guard
let tempDirectory = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask).map(\.path).last
else {
return nil
}

let data = uiImage.jpegData(compressionQuality: 1.0)

let fullPath = URL(fileURLWithPath: tempDirectory).appendingPathComponent("\(prefix ?? "thumb")-\(ProcessInfo.processInfo.globallyUniqueString).jpg").path

fileManager.createFile(atPath: fullPath, contents: data, attributes: nil)

return "file://" + fullPath
}
}
34 changes: 34 additions & 0 deletions ios/URL+Mime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// URL+Mime.swift
// Pods
//
// Created by BAO HA on 23/10/24.
//

import Foundation
import MobileCoreServices
import UniformTypeIdentifiers

extension URL {
func getMimeType() -> String {
let pathExtension = self.pathExtension.lowercased()

if #available(iOS 14.0, *) {
// Sử dụng UniformTypeIdentifiers (UTType) cho iOS 14+
if let utType = UTType(filenameExtension: pathExtension) {
return utType.preferredMIMEType ?? "application/octet-stream"
}

} else {
// Sử dụng MobileCoreServices (kUTType) cho iOS 13 trở xuống
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
let mimeType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue()
{
return mimeType as String
}
}

// Trả về MIME type mặc định nếu không tìm thấy
return "application/octet-stream"
}
}
52 changes: 52 additions & 0 deletions nitrogen/generated/android/c++/JCropConfig.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
///
/// JCropConfig.hpp
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
/// https://github.com/mrousavy/nitro
/// Copyright © 2024 Marc Rousavy @ Margelo
///

#pragma once

#include <fbjni/fbjni.h>
#include "CropConfig.hpp"

#include <optional>

namespace margelo::nitro::imagepicker {

using namespace facebook;

/**
* The C++ JNI bridge between the C++ struct "CropConfig" and the the Kotlin data class "CropConfig".
*/
struct JCropConfig final: public jni::JavaClass<JCropConfig> {
public:
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/imagepicker/CropConfig;";

public:
/**
* Convert this Java/Kotlin-based struct to the C++ struct CropConfig by copying all values to C++.
*/
[[maybe_unused]]
CropConfig toCpp() const {
static const auto clazz = javaClassStatic();
static const auto fieldCircle = clazz->getField<jni::JBoolean>("circle");
jni::local_ref<jni::JBoolean> circle = this->getFieldValue(fieldCircle);
return CropConfig(
circle != nullptr ? std::make_optional(circle->value()) : std::nullopt
);
}

public:
/**
* Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java.
*/
[[maybe_unused]]
static jni::local_ref<JCropConfig::javaobject> fromCpp(const CropConfig& value) {
return newInstance(
value.circle.has_value() ? jni::JBoolean::valueOf(value.circle.value()) : nullptr
);
}
};

} // namespace margelo::nitro::imagepicker
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@
#include "JResult.hpp"
#include <string>
#include <optional>
#include "ResultType.hpp"
#include "JResultType.hpp"

namespace margelo::nitro::imagepicker {

Loading