作者 mingming

修改ios13拍摄视频奔溃的问题

正在显示 84 个修改的文件 包含 4762 行增加0 行删除

要显示太多修改。

为保证性能只显示 84 of 84+ 个文件。

# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pusic007@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
... ...
# Contribute
## Introduction
First, thank you for considering contributing to react-native-image-crop-picker! It's people like you that make the open source community such a great community! 😊
We welcome any type of contribution, not only code. You can help with
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, printing stickers, ...
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
- **Code**: take a look at the [open issues](https://github.com/ivpusic/react-native-image-crop-picker/issues). Even if you can't write code, commenting on them, showing that you care about a given
matters. It helps us triage them.
- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/react-native-image-crop-picker).
## Your First Contribution
Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
## Code review process
The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
## Financial contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/react-native-image-crop-picker).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
## Questions
If you have any questions, create an [issue](https://github.com/ivpusic/react-native-image-crop-picker/issues) (protip: do a quick search first to see if someone else didn't ask the same question before!).
You can also reach us at hello@react-native-image-crop-picker.opencollective.com.
## Credits
### Contributors
Thank you to all the people who have already contributed to react-native-image-crop-picker!
<a href="graphs/contributors"><img src="https://opencollective.com/react-native-image-crop-picker/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/react-native-image-crop-picker#backer)]
<a href="https://opencollective.com/react-native-image-crop-picker#backers" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/backers.svg?width=890"></a>
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/react-native-image-crop-picker#sponsor))
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/9/avatar.svg"></a>
<!-- This `CONTRIBUTING.md` is based on @nayafia's template https://github.com/nayafia/contributing-template -->
... ...
### Version
Tell us which versions you are using:
- react-native-image-crop-picker v0.?.?
- react-native v0.?.?
### Platform
Tell us to which platform this issue is related
- iOS
- Android
### Expected behaviour
### Actual behaviour
### Steps to reproduce
1.
2.
3.
### Attachments
// stacktrace or any other useful debug info
Love react-native-image-crop-picker? Please consider supporting our collective:
👉 https://opencollective.com/react-native-image-crop-picker/donate
... ...
MIT License
Copyright (c) 2017 Ivan Pusic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
... ...
# react-native-image-crop-picker
[![Backers on Open Collective](https://opencollective.com/react-native-image-crop-picker/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/react-native-image-crop-picker/sponsors/badge.svg)](#sponsors)
<img src="svg.svg" width="50%" height="50%" />
iOS/Android image picker with support for camera, video, configurable compression, multiple images and cropping
## Result
<p align="left">
<img width=200 title="iOS Single Pick" src="https://github.com/ivpusic/react-native-image-crop-picker/blob/master/images/ios_single_pick.png">
<img width=200 title="iOS Crop" src="https://github.com/ivpusic/react-native-image-crop-picker/blob/master/images/ios_crop.png">
<img width=200 title="iOS Multiple Pick" src="https://github.com/ivpusic/react-native-image-crop-picker/blob/master/images/ios_multiple_pick.png">
</p>
## Usage
Import library
```javascript
import ImagePicker from 'react-native-image-crop-picker';
```
### Select from gallery
Call single image picker with cropping
```javascript
ImagePicker.openPicker({
width: 300,
height: 400,
cropping: true
}).then(image => {
console.log(image);
});
```
Call multiple image picker
```javascript
ImagePicker.openPicker({
multiple: true
}).then(images => {
console.log(images);
});
```
Select video only from gallery
```javascript
ImagePicker.openPicker({
mediaType: "video",
}).then((video) => {
console.log(video);
});
```
**Android: The prop 'cropping' has been known to cause videos not to be display in the gallery on Android. Please do not set cropping to true when selecting videos.**
### Select from camera
#### Image
```javascript
ImagePicker.openCamera({
width: 300,
height: 400,
cropping: true,
}).then(image => {
console.log(image);
});
```
#### Video
```javascript
ImagePicker.openCamera({
mediaType: 'video',
}).then(image => {
console.log(image);
});
```
### Crop picture
```javascript
ImagePicker.openCropper({
path: 'my-file-path.jpg',
width: 300,
height: 400
}).then(image => {
console.log(image);
});
```
### Optional cleanup
Module is creating tmp images which are going to be cleaned up automatically somewhere in the future. If you want to force cleanup, you can use `clean` to clean all tmp files, or `cleanSingle(path)` to clean single tmp file.
```javascript
ImagePicker.clean().then(() => {
console.log('removed all tmp images from tmp directory');
}).catch(e => {
alert(e);
});
```
### Request Object
| Property | Type | Description |
| --------------------------------------- | :--------------------------------------: | :--------------------------------------- |
| cropping | bool (default false) | Enable or disable cropping |
| width | number | Width of result image when used with `cropping` option |
| height | number | Height of result image when used with `cropping` option |
| multiple | bool (default false) | Enable or disable multiple image selection |
| writeTempFile (ios only) | bool (default true) | When set to false, does not write temporary files for the selected images. This is useful to improve performance when you are retrieving file contents with the `includeBase64` option and don't need to read files from disk. |
| includeBase64 | bool (default false) | When set to true, the image file content will be available as a base64-encoded string in the `data` property. Hint: To use this string as an image source, use it like: ``<Image source={{uri: `data:${image.mime};base64,${image.data}`}} />`` |
| includeExif | bool (default false) | Include image exif data in the response |
| avoidEmptySpaceAroundImage | bool (default true) | When set to true, the image will always fill the mask space. |
| cropperActiveWidgetColor (android only) | string (default `"#424242"`) | When cropping image, determines ActiveWidget color. |
| cropperStatusBarColor (android only) | string (default `#424242`) | When cropping image, determines the color of StatusBar. |
| cropperToolbarColor (android only) | string (default `#424242`) | When cropping image, determines the color of Toolbar. |
| freeStyleCropEnabled (android only) | bool (default false) | Enables user to apply custom rectangle area for cropping |
| cropperToolbarTitle | string (default `Edit Photo`) | When cropping image, determines the title of Toolbar. |
| cropperCircleOverlay | bool (default false) | Enable or disable circular cropping mask. |
| disableCropperColorSetters (android only)| bool (default false) | When cropping image, disables the color setters for cropping library. |
| minFiles (ios only) | number (default 1) | Min number of files to select when using `multiple` option |
| maxFiles (ios only) | number (default 5) | Max number of files to select when using `multiple` option |
| waitAnimationEnd (ios only) | bool (default true) | Promise will resolve/reject once ViewController `completion` block is called |
| smartAlbums (ios only) | array ([supported values](https://github.com/ivpusic/react-native-image-crop-picker/blob/master/README.md#smart-album-types-ios)) (default ['UserLibrary', 'PhotoStream', 'Panoramas', 'Videos', 'Bursts']) | List of smart albums to choose from |
| useFrontCamera | bool (default false) | Whether to default to the front/'selfie' camera when opened |
| compressVideoPreset (ios only) | string (default MediumQuality) | Choose which preset will be used for video compression |
| compressImageMaxWidth | number (default none) | Compress image with maximum width |
| compressImageMaxHeight | number (default none) | Compress image with maximum height |
| compressImageQuality | number (default 1 (Android)/0.8 (iOS)) | Compress image with quality (from 0 to 1, where 1 is best quality). On iOS, values larger than 0.8 don't produce a noticable quality increase in most images, while a value of 0.8 will reduce the file size by about half or less compared to a value of 1. |
| loadingLabelText (ios only) | string (default "Processing assets...") | Text displayed while photo is loading in picker |
| mediaType | string (default any) | Accepted mediaType for image selection, can be one of: 'photo', 'video', or 'any' |
| showsSelectedCount (ios only) | bool (default true) | Whether to show the number of selected assets |
| forceJpg (ios only) | bool (default false) | Whether to convert photos to JPG. This will also convert any Live Photo into its JPG representation |
| showCropGuidelines (android only) | bool (default true) | Whether to show the 3x3 grid on top of the image during cropping |
| showCropFrame (android only) | bool (default true) | Whether to show crop frame during cropping |
| hideBottomControls (android only) | bool (default false) | Whether to display bottom controls |
| enableRotationGesture (android only) | bool (default false) | Whether to enable rotating the image by hand gesture |
| cropperChooseText (ios only)  |           string (default choose)        | Choose button text |
| cropperCancelText (ios only) | string (default Cancel) | Cancel button text |
#### Smart Album Types (ios)
```
['PhotoStream', 'Generic', 'Panoramas', 'Videos', 'Favorites', 'Timelapses', 'AllHidden', 'RecentlyAdded', 'Bursts', 'SlomoVideos', 'UserLibrary', 'SelfPortraits', 'Screenshots', 'DepthEffect', 'LivePhotos', 'Animated', 'LongExposure']
```
### Response Object
| Property | Type | Description |
| ------------------------- | :----: | :--------------------------------------- |
| path | string | Selected image location. This is null when the `writeTempFile` option is set to false. |
| localIdentifier(ios only) | string | Selected images' localidentifier, used for PHAsset searching |
| sourceURL(ios only) | string | Selected images' source path, do not have write access |
| filename(ios only) | string | Selected images' filename |
| width | number | Selected image width |
| height | number | Selected image height |
| mime | string | Selected image MIME type (image/jpeg, image/png) |
| size | number | Selected image size in bytes |
| data | base64 | Optional base64 selected file representation |
| exif | object | Extracted exif data from image. Response format is platform specific |
| cropRect | object | Cropped image rectangle (width, height, x, y) |
| creationDate (ios only) | string | UNIX timestamp when image was created |
| modificationDate | string | UNIX timestamp when image was last modified |
# Install
## Step 1
```bash
npm i react-native-image-crop-picker --save
```
## Step 2
### iOS
#### - If you use Cocoapods which is highly recommended:
```bash
cd ios
pod init
```
After this edit Podfile. Example content is following:
```bash
platform :ios, '8.0'
target '<project_name>' do
# this is very important to have!
rn_path = '../node_modules/react-native'
pod 'yoga', path: "#{rn_path}/ReactCommon/yoga/yoga.podspec"
pod 'React', path: rn_path, subspecs: [
'Core',
'RCTActionSheet',
'RCTAnimation',
'RCTGeolocation',
'RCTImage',
'RCTLinkingIOS',
'RCTNetwork',
'RCTSettings',
'RCTText',
'RCTVibration',
'RCTWebSocket'
]
pod 'RNImageCropPicker', :path => '../node_modules/react-native-image-crop-picker'
end
# very important to have, unless you removed React dependencies for Libraries
# and you rely on Cocoapods to manage it
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == "React"
target.remove_from_project
end
end
end
```
After this run:
```bash
pod install
```
After this use `ios/<project_name>.xcworkspace`. **Do not use** `ios/<project_name>.xcodeproj`.
#### - If you are not using Cocoapods which is not recommended:
```bash
react-native link react-native-image-crop-picker
```
### Android
```bash
react-native link react-native-image-crop-picker
```
## Post-install steps
### iOS
#### Step 1
In Xcode open Info.plist and add string key `NSPhotoLibraryUsageDescription` with value that describes why you need access to user photos. More info here https://forums.developer.apple.com/thread/62229. Depending on what features you use, you also may need `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` keys.
#### Step 2
##### Only if you are not using Cocoapods
- Click on project General tab
- Under `Deployment Info` set `Deployment Target` to `8.0`
- Under `Embedded Binaries` click `+` and add `RSKImageCropper.framework` and `QBImagePicker.framework`
#### Step Optional - To localizate the camera / gallery text buttons
- Open your Xcode project
- Go to your project settings by opening the project name on the Navigation (left side)
- Select your project in the project list
- Should be into the Info tab and add in Localizations the language your app was missing throughout the +
- Rebuild and you should now have your app camera and gallery with the classic ios text in the language you added.
### Android
- Make sure you are using Gradle >= `2.2.x` (android/build.gradle)
```gradle
buildscript {
...
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
...
}
...
}
```
- **VERY IMPORTANT** Add the following to your `build.gradle`'s repositories section. (android/build.gradle)
```gradle
allprojects {
repositories {
mavenLocal()
jcenter()
maven { url "$rootDir/../node_modules/react-native/android" }
// ADD THIS
maven { url 'https://maven.google.com' }
// ADD THIS
maven { url "https://jitpack.io" }
}
}
```
- Add `useSupportLibrary` (android/app/build.gradle)
```gradle
android {
...
defaultConfig {
...
vectorDrawables.useSupportLibrary = true
...
}
...
}
```
- Use Android SDK >= 26 (android/app/build.gradle)
```gradle
android {
compileSdkVersion 27
buildToolsVersion "27.0.3"
...
defaultConfig {
...
targetSdkVersion 27
...
}
...
}
```
- [Optional] If you want to use camera picker in your project, add following to `app\src\main\AndroidManifest.xml`
- `<uses-permission android:name="android.permission.CAMERA"/>`
- [Optional] If you want to use front camera, also add following to `app\src\main\AndroidManifest.xml`
- `<uses-feature android:name="android.hardware.camera" android:required="false" />`
- `<uses-feature android:name="android.hardware.camera.front" android:required="false" />`
## Production build
### iOS
#### Cocoapods (Highly recommended)
- You are already done
#### Manual
If you are using pre-built frameworks from `ios/ImageCropPickerSDK`, then before deploying app to production you should strip off simulator ARCHs from these, or you can add frameworks from `Libraries/imageCropPicker/Libraries/_framework_name_.xcodeproj/Products/_framework_name_.framework` to Embedded Binaries instead of pre-built ones.
Related issue: https://github.com/ivpusic/react-native-image-crop-picker/issues/61.
Details for second approach:
1. Remove the pre-built frameworks from `Embedded Binaries`
2. Build for Device
3. Add the newly built binaries for both frameworks to `Embedded Binaries` (located at `Libraries/imageCropPicker/Libraries/_framework_name_.xcodeproj/Products/_framework_name_.framework`)
## TO DO
- [ ] [Android] Standardize multiple select
- [ ] [Android] Video compression
## Contributors
This project exists thanks to all the people who contribute. [[Contribute]](CONTRIBUTING.md).
<a href="graphs/contributors"><img src="https://opencollective.com/react-native-image-crop-picker/contributors.svg?width=890" /></a>
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/react-native-image-crop-picker#backer)]
<a href="https://opencollective.com/react-native-image-crop-picker#backers" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/react-native-image-crop-picker#sponsor)]
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/react-native-image-crop-picker/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-native-image-crop-picker/sponsor/9/avatar.svg"></a>
## License
*MIT*
... ...
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
version = package['version']
Pod::Spec.new do |s|
s.name = "RNImageCropPicker"
s.version = version
s.summary = package["description"]
s.requires_arc = true
s.license = 'MIT'
s.homepage = 'n/a'
s.authors = { "ivpusic" => "" }
s.source = { :git => "https://github.com/ivpusic/react-native-image-crop-picker", :tag => 'v#{version}'}
s.source_files = 'ios/src/*.{h,m}'
s.platform = :ios, "8.0"
s.dependency 'RSKImageCropper'
s.dependency 'QBImagePickerController'
s.dependency 'React/Core'
end
... ...
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotations" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/23.3.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.3.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.0.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.3.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/23.3.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.facebook.fresco/drawee/0.8.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.facebook.fresco/fbcore/0.8.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.facebook.fresco/fresco/0.8.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.facebook.fresco/imagepipeline-okhttp/0.8.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.facebook.fresco/imagepipeline/0.8.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.facebook.react/react-native/0.20.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.yalantis/ucrop/1.5.0/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/org.webkit/android-jsc/r174650/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="okhttp-ws-2.5.0" level="project" />
<orderEntry type="library" exported="" name="okio-1.6.0" level="project" />
<orderEntry type="library" exported="" name="okhttp-2.5.0" level="project" />
<orderEntry type="library" exported="" name="stetho-1.2.0" level="project" />
<orderEntry type="library" exported="" name="jsr305-3.0.0" level="project" />
<orderEntry type="library" exported="" name="fbcore-0.8.1" level="project" />
<orderEntry type="library" exported="" name="commons-cli-1.2" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-23.0.1" level="project" />
<orderEntry type="library" exported="" name="imagepipeline-0.8.1" level="project" />
<orderEntry type="library" exported="" name="android-jsc-r174650" level="project" />
<orderEntry type="library" exported="" name="fresco-0.8.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.3.0" level="project" />
<orderEntry type="library" exported="" name="imagepipeline-okhttp-0.8.1" level="project" />
<orderEntry type="library" exported="" name="bolts-android-1.1.4" level="project" />
<orderEntry type="library" exported="" name="drawee-0.8.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.3.0" level="project" />
<orderEntry type="library" exported="" name="support-vector-drawable-23.3.0" level="project" />
<orderEntry type="library" exported="" name="animated-vector-drawable-23.3.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.3.0" level="project" />
<orderEntry type="library" exported="" name="library-2.4.0" level="project" />
<orderEntry type="library" exported="" name="stetho-okhttp-1.2.0" level="project" />
<orderEntry type="library" exported="" name="ucrop-1.5.0" level="project" />
<orderEntry type="library" exported="" name="jackson-core-2.2.3" level="project" />
<orderEntry type="library" exported="" name="okhttp-3.2.0" level="project" />
<orderEntry type="library" exported="" name="react-native-0.20.1" level="project" />
</component>
</module>
\ No newline at end of file
... ...
apply plugin: 'com.android.library'
def DEFAULT_COMPILE_SDK_VERSION = 28
def DEFAULT_BUILD_TOOLS_VERSION = "28.0.3"
def DEFAULT_TARGET_SDK_VERSION = 28
def DEFAULT_MIN_SDK_VERSION = 16
android {
compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : DEFAULT_MIN_SDK_VERSION
targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION
versionCode 1
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'com.github.yalantis:ucrop:2.2.2-native'
}
... ...
#Tue May 17 14:28:43 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
... ...
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
... ...
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
... ...
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":react-native-image-crop-picker" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/../../../android" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":react-native-image-crop-picker" />
<option name="LAST_SUCCESSFUL_SYNC_AGP_VERSION" value="3.5.2" />
<option name="LAST_KNOWN_AGP_VERSION" value="3.5.2" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="TEST_RES_FOLDERS_RELATIVE_PATH" value="" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="PROJECT_TYPE" value="1" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
<output url="file://$MODULE_DIR$/build/intermediates/javac/debug/classes" />
<output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/ap_generated_sources/debug/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debug/compileDebugRenderscript/out" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/ap_generated_sources/debugAndroidTest/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/aidl_source_output_dir/debugAndroidTest/compileDebugAndroidTestAidl/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/renderscript_source_output_dir/debugAndroidTest/compileDebugAndroidTestRenderscript/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/ap_generated_sources/debugUnitTest/out" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTestDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: com.facebook.infer.annotation:infer-annotation:0.11.2@jar" level="project" />
<orderEntry type="library" name="Gradle: com.google.code.findbugs:jsr305:3.0.2@jar" level="project" />
<orderEntry type="library" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:collections:28.0.0@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:common:1.1.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-annotations:28.0.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp-urlconnection:3.12.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.12.1@jar" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.15.0@jar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.react:react-native:0.59.9@aar" level="project" />
<orderEntry type="library" name="Gradle: com.github.yalantis:ucrop:2.2.2-native@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:appcompat-v7:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-fragment:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-ui:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-core-utils:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-vector-drawable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:loader:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:viewpager:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:coordinatorlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:drawerlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:slidingpanelayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:customview:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:swiperefreshlayout:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:asynclayoutinflater:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:support-compat:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:versionedparcelable:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:cursoradapter:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:documentfile:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:localbroadcastmanager:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:print:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: android.arch.core:runtime:1.1.1@aar" level="project" />
<orderEntry type="library" name="Gradle: com.android.support:interpolator:28.0.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fresco:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:fbcore:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:drawee:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-base:1.10.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.soloader:soloader:0.6.0@aar" level="project" />
<orderEntry type="library" name="Gradle: com.facebook.fresco:imagepipeline-okhttp3:1.10.0@aar" level="project" />
</component>
</module>
\ No newline at end of file
... ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnative.ivpusic.imagepicker">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
</application>
</manifest>
... ...
package com.reactnative.ivpusic.imagepicker;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.os.Environment;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
/**
* Created by ipusic on 12/27/16.
*/
class Compression {
File resize(String originalImagePath, int maxWidth, int maxHeight, int quality) throws IOException {
Bitmap original = BitmapFactory.decodeFile(originalImagePath);
int width = original.getWidth();
int height = original.getHeight();
// Use original image exif orientation data to preserve image orientation for the resized bitmap
ExifInterface originalExif = new ExifInterface(originalImagePath);
int originalOrientation = originalExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
Matrix rotationMatrix = new Matrix();
int rotationAngleInDegrees = getRotationInDegreesForOrientationTag(originalOrientation);
rotationMatrix.postRotate(rotationAngleInDegrees);
float ratioBitmap = (float) width / (float) height;
float ratioMax = (float) maxWidth / (float) maxHeight;
int finalWidth = maxWidth;
int finalHeight = maxHeight;
if (ratioMax > 1) {
finalWidth = (int) ((float) maxHeight * ratioBitmap);
} else {
finalHeight = (int) ((float) maxWidth / ratioBitmap);
}
Bitmap resized = Bitmap.createScaledBitmap(original, finalWidth, finalHeight, true);
resized = Bitmap.createBitmap(resized, 0, 0, finalWidth, finalHeight, rotationMatrix, true);
File resizeImageFile = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), UUID.randomUUID() + ".jpg");
OutputStream os = new BufferedOutputStream(new FileOutputStream(resizeImageFile));
resized.compress(Bitmap.CompressFormat.JPEG, quality, os);
os.close();
original.recycle();
resized.recycle();
return resizeImageFile;
}
int getRotationInDegreesForOrientationTag(int orientationTag) {
switch(orientationTag){
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_270:
return -90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
default:
return 0;
}
}
File compressImage(final ReadableMap options, final String originalImagePath, final BitmapFactory.Options bitmapOptions) throws IOException {
Integer maxWidth = options.hasKey("compressImageMaxWidth") ? options.getInt("compressImageMaxWidth") : null;
Integer maxHeight = options.hasKey("compressImageMaxHeight") ? options.getInt("compressImageMaxHeight") : null;
Double quality = options.hasKey("compressImageQuality") ? options.getDouble("compressImageQuality") : null;
boolean isLossLess = (quality == null || quality == 1.0);
boolean useOriginalWidth = (maxWidth == null || maxWidth >= bitmapOptions.outWidth);
boolean useOriginalHeight = (maxHeight == null || maxHeight >= bitmapOptions.outHeight);
List knownMimes = Arrays.asList("image/jpeg", "image/jpg", "image/png", "image/gif", "image/tiff");
boolean isKnownMimeType = (bitmapOptions.outMimeType != null && knownMimes.contains(bitmapOptions.outMimeType.toLowerCase()));
if (isLossLess && useOriginalWidth && useOriginalHeight && isKnownMimeType) {
Log.d("image-crop-picker", "Skipping image compression");
return new File(originalImagePath);
}
Log.d("image-crop-picker", "Image compression activated");
// compression quality
int targetQuality = quality != null ? (int) (quality * 100) : 100;
Log.d("image-crop-picker", "Compressing image with quality " + targetQuality);
if (maxWidth == null) {
maxWidth = bitmapOptions.outWidth;
} else {
maxWidth = Math.min(maxWidth, bitmapOptions.outWidth);
}
if (maxHeight == null) {
maxHeight = bitmapOptions.outHeight;
} else {
maxHeight = Math.min(maxHeight, bitmapOptions.outHeight);
}
return resize(originalImagePath, maxWidth, maxHeight, targetQuality);
}
synchronized void compressVideo(final Activity activity, final ReadableMap options, final String originalVideo, final String compressedVideo, final Promise promise) {
// todo: video compression
// failed attempt 1: ffmpeg => slow and licensing issues
promise.resolve(originalVideo);
}
}
... ...
package com.reactnative.ivpusic.imagepicker;
import android.media.ExifInterface;
import android.os.Build;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static android.media.ExifInterface.*;
class ExifExtractor {
static WritableMap extract(String path) throws IOException {
WritableMap exifData = new WritableNativeMap();
List<String> attributes = getBasicAttributes();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
attributes.addAll(getLevel23Attributes());
}
ExifInterface exif = new ExifInterface(path);
for (String attribute : attributes) {
String value = exif.getAttribute(attribute);
exifData.putString(attribute, value);
}
return exifData;
}
private static List<String> getBasicAttributes() {
return new ArrayList<>(Arrays.asList(
TAG_APERTURE,
TAG_DATETIME,
TAG_EXPOSURE_TIME,
TAG_FLASH,
TAG_FOCAL_LENGTH,
TAG_GPS_ALTITUDE,
TAG_GPS_ALTITUDE_REF,
TAG_GPS_DATESTAMP,
TAG_GPS_LATITUDE,
TAG_GPS_LATITUDE_REF,
TAG_GPS_LONGITUDE,
TAG_GPS_LONGITUDE_REF,
TAG_GPS_PROCESSING_METHOD,
TAG_GPS_TIMESTAMP,
TAG_IMAGE_LENGTH,
TAG_IMAGE_WIDTH,
TAG_ISO,
TAG_MAKE,
TAG_MODEL,
TAG_ORIENTATION,
TAG_WHITE_BALANCE
));
}
private static List<String> getLevel23Attributes() {
return new ArrayList<>(Arrays.asList(
TAG_DATETIME_DIGITIZED,
TAG_SUBSEC_TIME,
TAG_SUBSEC_TIME_DIG,
TAG_SUBSEC_TIME_ORIG
));
}
}
\ No newline at end of file
... ...
package com.reactnative.ivpusic.imagepicker;
import android.Manifest;
import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.util.Base64;
import android.webkit.MimeTypeMap;
import android.content.ContentResolver;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.PromiseImpl;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropActivity;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
class PickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
private static final int IMAGE_PICKER_REQUEST = 61110;
private static final int CAMERA_PICKER_REQUEST = 61111;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_PICKER_CANCELLED_KEY = "E_PICKER_CANCELLED";
private static final String E_PICKER_CANCELLED_MSG = "User cancelled image selection";
private static final String E_CALLBACK_ERROR = "E_CALLBACK_ERROR";
private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
private static final String E_FAILED_TO_OPEN_CAMERA = "E_FAILED_TO_OPEN_CAMERA";
private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";
private static final String E_CAMERA_IS_NOT_AVAILABLE = "E_CAMERA_IS_NOT_AVAILABLE";
private static final String E_CANNOT_LAUNCH_CAMERA = "E_CANNOT_LAUNCH_CAMERA";
private static final String E_PERMISSIONS_MISSING = "E_PERMISSION_MISSING";
private static final String E_ERROR_WHILE_CLEANING_FILES = "E_ERROR_WHILE_CLEANING_FILES";
private String mediaType = "any";
private boolean multiple = false;
private boolean includeBase64 = false;
private boolean includeExif = false;
private boolean cropping = false;
private boolean cropperCircleOverlay = false;
private boolean freeStyleCropEnabled = false;
private boolean showCropGuidelines = true;
private boolean showCropFrame = true;
private boolean hideBottomControls = false;
private boolean enableRotationGesture = false;
private boolean disableCropperColorSetters = false;
private boolean useFrontCamera = false;
private ReadableMap options;
//Grey 800
private final String DEFAULT_TINT = "#424242";
private String cropperActiveWidgetColor = DEFAULT_TINT;
private String cropperStatusBarColor = DEFAULT_TINT;
private String cropperToolbarColor = DEFAULT_TINT;
private String cropperToolbarTitle = null;
//Light Blue 500
private final String DEFAULT_WIDGET_COLOR = "#03A9F4";
private int width = 0;
private int height = 0;
private Uri mCameraCaptureURI;
private String mCurrentMediaPath;
private ResultCollector resultCollector = new ResultCollector();
private Compression compression = new Compression();
private ReactApplicationContext reactContext;
PickerModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(this);
this.reactContext = reactContext;
}
private String getTmpDir(Activity activity) {
String tmpDir = activity.getCacheDir() + "/react-native-image-crop-picker";
new File(tmpDir).mkdir();
return tmpDir;
}
@Override
public String getName() {
return "ImageCropPicker";
}
private void setConfiguration(final ReadableMap options) {
mediaType = options.hasKey("mediaType") ? options.getString("mediaType") : "any";
multiple = options.hasKey("multiple") && options.getBoolean("multiple");
includeBase64 = options.hasKey("includeBase64") && options.getBoolean("includeBase64");
includeExif = options.hasKey("includeExif") && options.getBoolean("includeExif");
width = options.hasKey("width") ? options.getInt("width") : 0;
height = options.hasKey("height") ? options.getInt("height") : 0;
cropping = options.hasKey("cropping") && options.getBoolean("cropping");
cropperActiveWidgetColor = options.hasKey("cropperActiveWidgetColor") ? options.getString("cropperActiveWidgetColor") : DEFAULT_TINT;
cropperStatusBarColor = options.hasKey("cropperStatusBarColor") ? options.getString("cropperStatusBarColor") : DEFAULT_TINT;
cropperToolbarColor = options.hasKey("cropperToolbarColor") ? options.getString("cropperToolbarColor") : DEFAULT_TINT;
cropperToolbarTitle = options.hasKey("cropperToolbarTitle") ? options.getString("cropperToolbarTitle") : null;
cropperCircleOverlay = options.hasKey("cropperCircleOverlay") && options.getBoolean("cropperCircleOverlay");
freeStyleCropEnabled = options.hasKey("freeStyleCropEnabled") && options.getBoolean("freeStyleCropEnabled");
showCropGuidelines = !options.hasKey("showCropGuidelines") || options.getBoolean("showCropGuidelines");
showCropFrame = !options.hasKey("showCropFrame") || options.getBoolean("showCropFrame");
hideBottomControls = options.hasKey("hideBottomControls") && options.getBoolean("hideBottomControls");
enableRotationGesture = options.hasKey("enableRotationGesture") && options.getBoolean("enableRotationGesture");
disableCropperColorSetters = options.hasKey("disableCropperColorSetters") && options.getBoolean("disableCropperColorSetters");
useFrontCamera = options.hasKey("useFrontCamera") && options.getBoolean("useFrontCamera");
this.options = options;
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
}
}
fileOrDirectory.delete();
}
@ReactMethod
public void clean(final Promise promise) {
final Activity activity = getCurrentActivity();
final PickerModule module = this;
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
permissionsCheck(activity, promise, Collections.singletonList(Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() {
try {
File file = new File(module.getTmpDir(activity));
if (!file.exists()) throw new Exception("File does not exist");
module.deleteRecursive(file);
promise.resolve(null);
} catch (Exception ex) {
ex.printStackTrace();
promise.reject(E_ERROR_WHILE_CLEANING_FILES, ex.getMessage());
}
return null;
}
});
}
@ReactMethod
public void cleanSingle(final String pathToDelete, final Promise promise) {
if (pathToDelete == null) {
promise.reject(E_ERROR_WHILE_CLEANING_FILES, "Cannot cleanup empty path");
return;
}
final Activity activity = getCurrentActivity();
final PickerModule module = this;
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
permissionsCheck(activity, promise, Collections.singletonList(Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
String path = pathToDelete;
final String filePrefix = "file://";
if (path.startsWith(filePrefix)) {
path = path.substring(filePrefix.length());
}
File file = new File(path);
if (!file.exists()) throw new Exception("File does not exist. Path: " + path);
module.deleteRecursive(file);
promise.resolve(null);
} catch (Exception ex) {
ex.printStackTrace();
promise.reject(E_ERROR_WHILE_CLEANING_FILES, ex.getMessage());
}
return null;
}
});
}
private void permissionsCheck(final Activity activity, final Promise promise, final List<String> requiredPermissions, final Callable<Void> callback) {
List<String> missingPermissions = new ArrayList<>();
for (String permission : requiredPermissions) {
int status = ActivityCompat.checkSelfPermission(activity, permission);
if (status != PackageManager.PERMISSION_GRANTED) {
missingPermissions.add(permission);
}
}
if (!missingPermissions.isEmpty()) {
((PermissionAwareActivity) activity).requestPermissions(missingPermissions.toArray(new String[missingPermissions.size()]), 1, new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == 1) {
for (int grantResult : grantResults) {
if (grantResult == PackageManager.PERMISSION_DENIED) {
promise.reject(E_PERMISSIONS_MISSING, "Required permission missing");
return true;
}
}
try {
callback.call();
} catch (Exception e) {
promise.reject(E_CALLBACK_ERROR, "Unknown error", e);
}
}
return true;
}
});
return;
}
// all permissions granted
try {
callback.call();
} catch (Exception e) {
promise.reject(E_CALLBACK_ERROR, "Unknown error", e);
}
}
@ReactMethod
public void openCamera(final ReadableMap options, final Promise promise) {
final Activity activity = getCurrentActivity();
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
if (!isCameraAvailable(activity)) {
promise.reject(E_CAMERA_IS_NOT_AVAILABLE, "Camera not available");
return;
}
setConfiguration(options);
resultCollector.setup(promise, false);
permissionsCheck(activity, promise, Arrays.asList(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() {
initiateCamera(activity);
return null;
}
});
}
private void initiateCamera(Activity activity) {
try {
String intent;
File dataFile;
if (mediaType.equals("video")) {
intent = MediaStore.ACTION_VIDEO_CAPTURE;
dataFile = createVideoFile();
} else {
intent = MediaStore.ACTION_IMAGE_CAPTURE;
dataFile = createImageFile();
}
Intent cameraIntent = new Intent(intent);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mCameraCaptureURI = Uri.fromFile(dataFile);
} else {
mCameraCaptureURI = FileProvider.getUriForFile(activity,
activity.getApplicationContext().getPackageName() + ".provider",
dataFile);
}
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCameraCaptureURI);
if (this.useFrontCamera) {
cameraIntent.putExtra("android.intent.extras.CAMERA_FACING", 1);
cameraIntent.putExtra("android.intent.extras.LENS_FACING_FRONT", 1);
cameraIntent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true);
}
if (cameraIntent.resolveActivity(activity.getPackageManager()) == null) {
resultCollector.notifyProblem(E_CANNOT_LAUNCH_CAMERA, "Cannot launch camera");
return;
}
activity.startActivityForResult(cameraIntent, CAMERA_PICKER_REQUEST);
} catch (Exception e) {
resultCollector.notifyProblem(E_FAILED_TO_OPEN_CAMERA, e);
}
}
private void initiatePicker(final Activity activity) {
try {
final Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
if (cropping || mediaType.equals("photo")) {
final Intent libraryIntent = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
activity.startActivityForResult(libraryIntent, IMAGE_PICKER_REQUEST);
return;
}
// if (cropping || mediaType.equals("photo")) {
// galleryIntent.setType("image/*");
// } else
if (mediaType.equals("video")) {
galleryIntent.setType("video/*");
} else {
galleryIntent.setType("*/*");
String[] mimetypes = {"image/*", "video/*"};
galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes);
}
galleryIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
galleryIntent.addCategory(Intent.CATEGORY_OPENABLE);
final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");
activity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST);
} catch (Exception e) {
resultCollector.notifyProblem(E_FAILED_TO_SHOW_PICKER, e);
}
}
@ReactMethod
public void openPicker(final ReadableMap options, final Promise promise) {
final Activity activity = getCurrentActivity();
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
setConfiguration(options);
resultCollector.setup(promise, multiple);
permissionsCheck(activity, promise, Collections.singletonList(Manifest.permission.WRITE_EXTERNAL_STORAGE), new Callable<Void>() {
@Override
public Void call() {
initiatePicker(activity);
return null;
}
});
}
@ReactMethod
public void openCropper(final ReadableMap options, final Promise promise) {
final Activity activity = getCurrentActivity();
if (activity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
setConfiguration(options);
resultCollector.setup(promise, false);
Uri uri = Uri.parse(options.getString("path"));
startCropping(activity, uri);
}
private String getBase64StringFromFile(String absoluteFilePath) {
InputStream inputStream;
try {
inputStream = new FileInputStream(new File(absoluteFilePath));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
byte[] bytes;
byte[] buffer = new byte[8192];
int bytesRead;
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
while ((bytesRead = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
bytes = output.toByteArray();
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
private String getMimeType(String url) {
String mimeType = null;
Uri uri = Uri.fromFile(new File(url));
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
ContentResolver cr = this.reactContext.getContentResolver();
mimeType = cr.getType(uri);
} else {
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
.toString());
if (fileExtension != null) {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase());
}
}
return mimeType;
}
private WritableMap getSelection(Activity activity, Uri uri, boolean isCamera) throws Exception {
String path = resolveRealPath(activity, uri, isCamera);
if (path == null || path.isEmpty()) {
throw new Exception("Cannot resolve asset path.");
}
String mime = getMimeType(path);
if (mime != null && mime.startsWith("video/")) {
getVideo(activity, path, mime);
return null;
}
return getImage(activity, path);
}
private void getAsyncSelection(final Activity activity, Uri uri, boolean isCamera) throws Exception {
String path = resolveRealPath(activity, uri, isCamera);
if (path == null || path.isEmpty()) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot resolve asset path.");
return;
}
String mime = getMimeType(path);
if (mime != null && mime.startsWith("video/")) {
getVideo(activity, path, mime);
return;
}
resultCollector.notifySuccess(getImage(activity, path));
}
private Bitmap validateVideo(String path) throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
Bitmap bmp = retriever.getFrameAtTime();
if (bmp == null) {
throw new Exception("Cannot retrieve video data");
}
return bmp;
}
private void getVideo(final Activity activity, final String path, final String mime) throws Exception {
validateVideo(path);
final String compressedVideoPath = getTmpDir(activity) + "/" + UUID.randomUUID().toString() + ".mp4";
new Thread(new Runnable() {
@Override
public void run() {
compression.compressVideo(activity, options, path, compressedVideoPath, new PromiseImpl(new Callback() {
@Override
public void invoke(Object... args) {
String videoPath = (String) args[0];
try {
Bitmap bmp = validateVideo(videoPath);
long modificationDate = new File(videoPath).lastModified();
WritableMap video = new WritableNativeMap();
video.putInt("width", bmp.getWidth());
video.putInt("height", bmp.getHeight());
video.putString("mime", mime);
video.putInt("size", (int) new File(videoPath).length());
video.putString("path", "file://" + videoPath);
video.putString("modificationDate", String.valueOf(modificationDate));
resultCollector.notifySuccess(video);
} catch (Exception e) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, e);
}
}
}, new Callback() {
@Override
public void invoke(Object... args) {
WritableNativeMap ex = (WritableNativeMap) args[0];
resultCollector.notifyProblem(ex.getString("code"), ex.getString("message"));
}
}));
}
}).run();
}
private String resolveRealPath(Activity activity, Uri uri, boolean isCamera) throws IOException {
String path;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
path = RealPathUtil.getRealPathFromURI(activity, uri);
} else {
if (isCamera) {
Uri mediaUri = Uri.parse(mCurrentMediaPath);
path = mediaUri.getPath();
} else {
path = RealPathUtil.getRealPathFromURI(activity, uri);
}
}
return path;
}
private BitmapFactory.Options validateImage(String path) throws Exception {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inDither = true;
BitmapFactory.decodeFile(path, options);
if (options.outMimeType == null || options.outWidth == 0 || options.outHeight == 0) {
throw new Exception("Invalid image selected");
}
return options;
}
private WritableMap getImage(final Activity activity, String path) throws Exception {
WritableMap image = new WritableNativeMap();
if (path.startsWith("http://") || path.startsWith("https://")) {
throw new Exception("Cannot select remote files");
}
BitmapFactory.Options original = validateImage(path);
// if compression options are provided image will be compressed. If none options is provided,
// then original image will be returned
File compressedImage = compression.compressImage(options, path, original);
String compressedImagePath = compressedImage.getPath();
BitmapFactory.Options options = validateImage(compressedImagePath);
long modificationDate = new File(path).lastModified();
image.putString("path", "file://" + compressedImagePath);
image.putInt("width", options.outWidth);
image.putInt("height", options.outHeight);
image.putString("mime", options.outMimeType);
image.putInt("size", (int) new File(compressedImagePath).length());
image.putString("modificationDate", String.valueOf(modificationDate));
if (includeBase64) {
image.putString("data", getBase64StringFromFile(compressedImagePath));
}
if (includeExif) {
try {
WritableMap exif = ExifExtractor.extract(path);
image.putMap("exif", exif);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return image;
}
private void configureCropperColors(UCrop.Options options) {
int activeWidgetColor = Color.parseColor(cropperActiveWidgetColor);
int toolbarColor = Color.parseColor(cropperToolbarColor);
int statusBarColor = Color.parseColor(cropperStatusBarColor);
options.setToolbarColor(toolbarColor);
options.setStatusBarColor(statusBarColor);
if (activeWidgetColor == Color.parseColor(DEFAULT_TINT)) {
/*
Default tint is grey => use a more flashy color that stands out more as the call to action
Here we use 'Light Blue 500' from https://material.google.com/style/color.html#color-color-palette
*/
options.setActiveWidgetColor(Color.parseColor(DEFAULT_WIDGET_COLOR));
} else {
//If they pass a custom tint color in, we use this for everything
options.setActiveWidgetColor(activeWidgetColor);
}
}
private void startCropping(Activity activity, Uri uri) {
UCrop.Options options = new UCrop.Options();
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);
options.setCompressionQuality(100);
options.setCircleDimmedLayer(cropperCircleOverlay);
options.setFreeStyleCropEnabled(freeStyleCropEnabled);
options.setShowCropGrid(showCropGuidelines);
options.setShowCropFrame(showCropFrame);
options.setHideBottomControls(hideBottomControls);
if (cropperToolbarTitle != null) {
options.setToolbarTitle(cropperToolbarTitle);
}
if (enableRotationGesture) {
// UCropActivity.ALL = enable both rotation & scaling
options.setAllowedGestures(
UCropActivity.ALL, // When 'scale'-tab active
UCropActivity.ALL, // When 'rotate'-tab active
UCropActivity.ALL // When 'aspect ratio'-tab active
);
}
if (!disableCropperColorSetters) {
configureCropperColors(options);
}
UCrop uCrop = UCrop
.of(uri, Uri.fromFile(new File(this.getTmpDir(activity), UUID.randomUUID().toString() + ".jpg")))
.withOptions(options);
if (width > 0 && height > 0) {
uCrop.withAspectRatio(width, height);
}
uCrop.start(activity);
}
private void imagePickerResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (resultCode == Activity.RESULT_CANCELED) {
resultCollector.notifyProblem(E_PICKER_CANCELLED_KEY, E_PICKER_CANCELLED_MSG);
} else if (resultCode == Activity.RESULT_OK) {
if (multiple) {
ClipData clipData = data.getClipData();
try {
// only one image selected
if (clipData == null) {
resultCollector.setWaitCount(1);
getAsyncSelection(activity, data.getData(), false);
} else {
resultCollector.setWaitCount(clipData.getItemCount());
for (int i = 0; i < clipData.getItemCount(); i++) {
getAsyncSelection(activity, clipData.getItemAt(i).getUri(), false);
}
}
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
} else {
Uri uri = data.getData();
if (uri == null) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url");
return;
}
if (cropping) {
startCropping(activity, uri);
} else {
try {
getAsyncSelection(activity, uri, false);
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
}
}
}
}
private void cameraPickerResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (resultCode == Activity.RESULT_CANCELED) {
resultCollector.notifyProblem(E_PICKER_CANCELLED_KEY, E_PICKER_CANCELLED_MSG);
} else if (resultCode == Activity.RESULT_OK) {
Uri uri = mCameraCaptureURI;
if (uri == null) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot resolve image url");
return;
}
if (cropping) {
UCrop.Options options = new UCrop.Options();
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);
startCropping(activity, uri);
} else {
try {
resultCollector.setWaitCount(1);
WritableMap result = getSelection(activity, uri, true);
// If recording a video getSelection handles resultCollector part itself and returns null
if (result != null) {
resultCollector.notifySuccess(result);
}
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
}
}
}
private void croppingResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (data != null) {
Uri resultUri = UCrop.getOutput(data);
if (resultUri != null) {
try {
if (width > 0 && height > 0) {
resultUri = Uri.fromFile(compression.resize(resultUri.getPath(), width, height, 100));
}
WritableMap result = getSelection(activity, resultUri, false);
if (result != null) {
result.putMap("cropRect", PickerModule.getCroppedRectMap(data));
resultCollector.setWaitCount(1);
resultCollector.notifySuccess(result);
} else {
throw new Exception("Cannot crop video files");
}
} catch (Exception ex) {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, ex.getMessage());
}
} else {
resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot find image data");
}
} else {
resultCollector.notifyProblem(E_PICKER_CANCELLED_KEY, E_PICKER_CANCELLED_MSG);
}
}
@Override
public void onActivityResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
if (requestCode == IMAGE_PICKER_REQUEST) {
imagePickerResult(activity, requestCode, resultCode, data);
} else if (requestCode == CAMERA_PICKER_REQUEST) {
cameraPickerResult(activity, requestCode, resultCode, data);
} else if (requestCode == UCrop.REQUEST_CROP) {
croppingResult(activity, requestCode, resultCode, data);
}
}
@Override
public void onNewIntent(Intent intent) {
}
private boolean isCameraAvailable(Activity activity) {
return activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
|| activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
}
private File createImageFile() throws IOException {
String imageFileName = "image-" + UUID.randomUUID().toString();
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
if (!path.exists() && !path.isDirectory()) {
path.mkdirs();
}
File image = File.createTempFile(imageFileName, ".jpg", path);
// Save a file: path for use with ACTION_VIEW intents
mCurrentMediaPath = "file:" + image.getAbsolutePath();
return image;
}
private File createVideoFile() throws IOException {
String videoFileName = "video-" + UUID.randomUUID().toString();
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
if (!path.exists() && !path.isDirectory()) {
path.mkdirs();
}
File video = File.createTempFile(videoFileName, ".mp4", path);
// Save a file: path for use with ACTION_VIEW intents
mCurrentMediaPath = "file:" + video.getAbsolutePath();
return video;
}
private static WritableMap getCroppedRectMap(Intent data) {
final int DEFAULT_VALUE = -1;
final WritableMap map = new WritableNativeMap();
map.putInt("x", data.getIntExtra(UCrop.EXTRA_OUTPUT_OFFSET_X, DEFAULT_VALUE));
map.putInt("y", data.getIntExtra(UCrop.EXTRA_OUTPUT_OFFSET_Y, DEFAULT_VALUE));
map.putInt("width", data.getIntExtra(UCrop.EXTRA_OUTPUT_IMAGE_WIDTH, DEFAULT_VALUE));
map.putInt("height", data.getIntExtra(UCrop.EXTRA_OUTPUT_IMAGE_HEIGHT, DEFAULT_VALUE));
return map;
}
}
... ...
package com.reactnative.ivpusic.imagepicker;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by ipusic on 5/16/16.
*/
public class PickerPackage implements ReactPackage {
// Deprecated RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new PickerModule(reactContext));
return modules;
}
}
... ...
package com.reactnative.ivpusic.imagepicker;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
class RealPathUtil {
static String getRealPathFromURI(final Context context, final Uri uri) throws IOException {
final boolean isKitKat = Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else {
final int splitIndex = docId.indexOf(':', 1);
final String tag = docId.substring(0, splitIndex);
final String path = docId.substring(splitIndex + 1);
String nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag);
if (nonPrimaryVolume != null) {
String result = nonPrimaryVolume + "/" + path;
File file = new File(result);
if (file.exists() && file.canRead()) {
return result;
}
return null;
}
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* If an image/video has been selected from a cloud storage, this method
* should be call to download the file in the cache folder.
*
* @param context The context
* @param fileName donwloaded file's name
* @param uri file's URI
* @return file that has been written
*/
private static File writeToFile(Context context, String fileName, Uri uri) {
String tmpDir = context.getCacheDir() + "/react-native-image-crop-picker";
Boolean created = new File(tmpDir).mkdir();
fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
File path = new File(tmpDir);
File file = new File(path, fileName);
try {
FileOutputStream oos = new FileOutputStream(file);
byte[] buf = new byte[8192];
InputStream is = context.getContentResolver().openInputStream(uri);
int c = 0;
while ((c = is.read(buf, 0, buf.length)) > 0) {
oos.write(buf, 0, c);
oos.flush();
}
oos.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
private static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String[] projection = {
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DISPLAY_NAME,
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
// Fall back to writing to file if _data column does not exist
final int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
String path = index > -1 ? cursor.getString(index) : null;
if (path != null) {
return cursor.getString(index);
} else {
final int indexDisplayName = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
String fileName = cursor.getString(indexDisplayName);
File fileWritten = writeToFile(context, fileName, uri);
return fileWritten.getAbsolutePath();
}
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
private static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
private static String getPathToNonPrimaryVolume(Context context, String tag) {
File[] volumes = context.getExternalCacheDirs();
if (volumes != null) {
for (File volume : volumes) {
if (volume != null) {
String path = volume.getAbsolutePath();
if (path != null) {
int index = path.indexOf(tag);
if (index != -1) {
return path.substring(0, index) + tag;
}
}
}
}
}
return null;
}
}
... ...
package com.reactnative.ivpusic.imagepicker;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ipusic on 12/28/16.
*/
class ResultCollector {
private Promise promise;
private int waitCount;
private boolean multiple;
private AtomicInteger waitCounter;
private WritableArray arrayResult;
private boolean resultSent;
synchronized void setup(Promise promise, boolean multiple) {
this.promise = promise;
this.multiple = multiple;
this.resultSent = false;
this.waitCount = 0;
this.waitCounter = new AtomicInteger(0);
if (multiple) {
this.arrayResult = new WritableNativeArray();
}
}
// if user has provided "multiple" option, we will wait for X number of result to come,
// and also return result as an array
synchronized void setWaitCount(int waitCount) {
this.waitCount = waitCount;
this.waitCounter = new AtomicInteger(0);
}
synchronized private boolean isRequestValid() {
if (resultSent) {
Log.w("image-crop-picker", "Skipping result, already sent...");
return false;
}
if (promise == null) {
Log.w("image-crop-picker", "Trying to notify success but promise is not set");
return false;
}
return true;
}
synchronized void notifySuccess(WritableMap result) {
if (!isRequestValid()) {
return;
}
if (multiple) {
arrayResult.pushMap(result);
int currentCount = waitCounter.addAndGet(1);
if (currentCount == waitCount) {
promise.resolve(arrayResult);
resultSent = true;
}
} else {
promise.resolve(result);
resultSent = true;
}
}
synchronized void notifyProblem(String code, String message) {
if (!isRequestValid()) {
return;
}
Log.e("image-crop-picker", "Promise rejected. " + message);
promise.reject(code, message);
resultSent = true;
}
synchronized void notifyProblem(String code, Throwable throwable) {
if (!isRequestValid()) {
return;
}
Log.e("image-crop-picker", "Promise rejected. " + throwable.getMessage());
promise.reject(code, throwable);
resultSent = true;
}
}
... ...
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/java" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
... ...
declare module "react-native-image-crop-picker" {
export interface Options {
cropping?: boolean;
width?: number;
height?: number;
multiple?: boolean;
path?: string;
includeBase64?: boolean;
includeExif?: boolean;
avoidEmptySpaceAroundImage?: boolean;
cropperActiveWidgetColor?: string;
cropperStatusBarColor?: string;
cropperToolbarColor?: string;
cropperToolbarTitle?: string;
freeStyleCropEnabled?: boolean;
cropperTintColor?: string;
cropperCircleOverlay?: boolean;
disableCropperColorSetters?: boolean;
maxFiles?: number;
waitAnimationEnd?: boolean;
smartAlbums?: string[];
useFrontCamera?: boolean;
compressVideoPreset?: string;
compressImageMaxWidth?: number;
compressImageMaxHeight?: number;
compressImageQuality?: number;
loadingLabelText?: string;
mediaType?: string;
showsSelectedCount?: boolean;
forceJpg?: boolean;
showCropGuidelines?: boolean;
hideBottomControls?: boolean;
enableRotationGesture?: boolean;
cropperCancelText?: string;
cropperChooseText?: string;
}
export interface Image {
path: string;
size: number;
data: null | string;
width: number;
height: number;
mime: string;
exif: null | object;
cropRect: null | CropRect;
filename: string;
creationDate: string;
modificationDate?: string;
}
export interface CropRect {
x: number;
y: number;
width: number;
height: number;
}
export function openPicker(options: Options): Promise<Image | Image[]>;
export function openCamera(options: Options): Promise<Image | Image[]>;
export function openCropper(options: Options): Promise<Image>;
export function clean(): Promise<void>;
export function cleanSingle(path: string): Promise<void>;
export interface ImageCropPicker {
openPicker(options: Options): Promise<Image | Image[]>;
openCamera(options: Options): Promise<Image | Image[]>;
openCropper(options: Options): Promise<Image>;
clean(): Promise<void>;
cleanSingle(path: string): Promise<void>;
}
const ImageCropPicker: ImageCropPicker;
export default ImageCropPicker;
}
... ...
import React from 'react';
import {NativeModules} from 'react-native';
export default NativeModules.ImageCropPicker;
... ...
//
// QBImagePicker.h
// QBImagePicker
//
// Created by Katsuma Tanaka on 2015/04/03.
// Copyright (c) 2015 Katsuma Tanaka. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for QBImagePicker.
FOUNDATION_EXPORT double QBImagePickerVersionNumber;
//! Project version string for QBImagePicker.
FOUNDATION_EXPORT const unsigned char QBImagePickerVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <QBImagePicker/PublicHeader.h>
#import <QBImagePicker/QBImagePickerController.h>
... ...
//
// QBImagePickerController.h
// QBImagePicker
//
// Created by Katsuma Tanaka on 2015/04/03.
// Copyright (c) 2015 Katsuma Tanaka. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Photos/Photos.h>
@class QBImagePickerController;
@protocol QBImagePickerControllerDelegate <NSObject>
@optional
- (void)qb_imagePickerController:(QBImagePickerController *)imagePickerController didFinishPickingAssets:(NSArray *)assets;
- (void)qb_imagePickerControllerDidCancel:(QBImagePickerController *)imagePickerController;
- (BOOL)qb_imagePickerController:(QBImagePickerController *)imagePickerController shouldSelectAsset:(PHAsset *)asset;
- (void)qb_imagePickerController:(QBImagePickerController *)imagePickerController didSelectAsset:(PHAsset *)asset;
- (void)qb_imagePickerController:(QBImagePickerController *)imagePickerController didDeselectAsset:(PHAsset *)asset;
@end
typedef NS_ENUM(NSUInteger, QBImagePickerMediaType) {
QBImagePickerMediaTypeAny = 0,
QBImagePickerMediaTypeImage,
QBImagePickerMediaTypeVideo
};
@interface QBImagePickerController : UIViewController
@property (nonatomic, weak) id<QBImagePickerControllerDelegate> delegate;
@property (nonatomic, strong, readonly) NSMutableOrderedSet *selectedAssets;
@property (nonatomic, copy) NSArray *assetCollectionSubtypes;
@property (nonatomic, assign) QBImagePickerMediaType mediaType;
@property (nonatomic, assign) BOOL allowsMultipleSelection;
@property (nonatomic, assign) NSUInteger minimumNumberOfSelection;
@property (nonatomic, assign) NSUInteger maximumNumberOfSelection;
@property (nonatomic, copy) NSString *prompt;
@property (nonatomic, assign) BOOL showsNumberOfSelectedAssets;
@property (nonatomic, assign) NSUInteger numberOfColumnsInPortrait;
@property (nonatomic, assign) NSUInteger numberOfColumnsInLandscape;
@end
... ...
framework module QBImagePicker {
umbrella header "QBImagePicker.h"
export *
module * { export * }
}
... ...
<?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>files</key>
<dict>
<key>Headers/QBImagePicker.h</key>
<data>
wIb/otiZ7bd2vyPJQF6JEeoqhqo=
</data>
<key>Headers/QBImagePickerController.h</key>
<data>
Pj/03SZs3R39/KDJC/Sw28hOcV4=
</data>
<key>Info.plist</key>
<data>
IcCz/8UZ07kkFONGwju3BBUrdSA=
</data>
<key>Modules/module.modulemap</key>
<data>
b8XazA/6z0jSXdJ08gOuLFgiplM=
</data>
<key>QBImagePicker.storyboardc/Info.plist</key>
<data>
iwZHbHPu3m3FYRI+FCDLbHeFs44=
</data>
<key>QBImagePicker.storyboardc/QBAlbumsNavigationController.nib</key>
<data>
5MCnX6u0y0SAVdmtiddUomh9GyY=
</data>
<key>QBImagePicker.storyboardc/QBAlbumsViewController.nib</key>
<data>
pY80pANgQzeIQuYM9GWFeoXUSxQ=
</data>
<key>QBImagePicker.storyboardc/QBAssetsViewController.nib</key>
<data>
7+OaszKW0bhc6RotWxC0o0SijKw=
</data>
<key>QBImagePicker.storyboardc/QL5-wR-LYt-view-66K-TS-Yoc.nib</key>
<data>
0xNFW/713RpJkWqwnLEP8bUWcuI=
</data>
<key>QBImagePicker.storyboardc/QiH-NZ-ZGN-view-sD2-zK-ryo.nib</key>
<data>
Fj2aIfr+Es6yrxOSQzP/3Uhvvvs=
</data>
<key>de.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
aEwah75TRZyK1k7zsKbitGOHsEE=
</data>
<key>optional</key>
<true/>
</dict>
<key>en.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
/bN8mAXwM04zgDQj19s7yTHxKQs=
</data>
<key>optional</key>
<true/>
</dict>
<key>es.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
3E8xykBQDKx9XRx4cigrfFeoUwo=
</data>
<key>optional</key>
<true/>
</dict>
<key>ja.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
7j4cVfYmTo9Yfo4fA9g4AvzTLhw=
</data>
<key>optional</key>
<true/>
</dict>
<key>pl.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
LfadkhKMCL8TGzZnnEP7Pbsb0I4=
</data>
<key>optional</key>
<true/>
</dict>
<key>zh-Hans.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
0F5a3/dLVWvW3skcaXrrOpnhPv0=
</data>
<key>optional</key>
<true/>
</dict>
</dict>
<key>files2</key>
<dict>
<key>Headers/QBImagePicker.h</key>
<dict>
<key>hash</key>
<data>
wIb/otiZ7bd2vyPJQF6JEeoqhqo=
</data>
<key>hash2</key>
<data>
Fh9fdGZCY2P54hZbKvAZwwcML6IHufU9QAmHXrcmAMI=
</data>
</dict>
<key>Headers/QBImagePickerController.h</key>
<dict>
<key>hash</key>
<data>
Pj/03SZs3R39/KDJC/Sw28hOcV4=
</data>
<key>hash2</key>
<data>
WcCD5/uhcGaZMLBPYHrRY/JkiB7BPQZqn5rkufhmY8c=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash</key>
<data>
b8XazA/6z0jSXdJ08gOuLFgiplM=
</data>
<key>hash2</key>
<data>
t0ZEP5nTTzs177dyaKMwGF/sZUJu1h9MPbMCcGQ7acw=
</data>
</dict>
<key>QBImagePicker.storyboardc/Info.plist</key>
<dict>
<key>hash</key>
<data>
iwZHbHPu3m3FYRI+FCDLbHeFs44=
</data>
<key>hash2</key>
<data>
gyFhkTx7M92K1jKJpSG0Yl9R+mQfrd/YtnmTaP54ukY=
</data>
</dict>
<key>QBImagePicker.storyboardc/QBAlbumsNavigationController.nib</key>
<dict>
<key>hash</key>
<data>
5MCnX6u0y0SAVdmtiddUomh9GyY=
</data>
<key>hash2</key>
<data>
SLNRGZnuwERXFNcJLu1791ZVDMDsNnnk49Xb01ZjoZo=
</data>
</dict>
<key>QBImagePicker.storyboardc/QBAlbumsViewController.nib</key>
<dict>
<key>hash</key>
<data>
pY80pANgQzeIQuYM9GWFeoXUSxQ=
</data>
<key>hash2</key>
<data>
KiZiF6NMbTf2YFOEQs4U1Yef9CSUk8GUiApZAjwtnOM=
</data>
</dict>
<key>QBImagePicker.storyboardc/QBAssetsViewController.nib</key>
<dict>
<key>hash</key>
<data>
7+OaszKW0bhc6RotWxC0o0SijKw=
</data>
<key>hash2</key>
<data>
PLJdoPEFJT4ItZgiMWCnK0GDoCTxbI/2wAwBBb8l3wM=
</data>
</dict>
<key>QBImagePicker.storyboardc/QL5-wR-LYt-view-66K-TS-Yoc.nib</key>
<dict>
<key>hash</key>
<data>
0xNFW/713RpJkWqwnLEP8bUWcuI=
</data>
<key>hash2</key>
<data>
ey42W/ZWm6W0Eea8Q4i9o79eTvpujgSK8HU9PTkbS/4=
</data>
</dict>
<key>QBImagePicker.storyboardc/QiH-NZ-ZGN-view-sD2-zK-ryo.nib</key>
<dict>
<key>hash</key>
<data>
Fj2aIfr+Es6yrxOSQzP/3Uhvvvs=
</data>
<key>hash2</key>
<data>
0iH5b4oLNN+sXyFq/BdGICtJzSnj4ll873t0puG4aqM=
</data>
</dict>
<key>de.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
aEwah75TRZyK1k7zsKbitGOHsEE=
</data>
<key>hash2</key>
<data>
5NH6glXlP7txoHgxKL83/BqWApGAb0poqKByJ1yo+9c=
</data>
<key>optional</key>
<true/>
</dict>
<key>en.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
/bN8mAXwM04zgDQj19s7yTHxKQs=
</data>
<key>hash2</key>
<data>
xZGH0lOWeKlYEMMZNToG0j+vQVTgUTNoydCOJBG+Kjs=
</data>
<key>optional</key>
<true/>
</dict>
<key>es.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
3E8xykBQDKx9XRx4cigrfFeoUwo=
</data>
<key>hash2</key>
<data>
T0LWvjKc9Wmuxznj7gCFXRNeGYuFiF92kDcjOVI/WPo=
</data>
<key>optional</key>
<true/>
</dict>
<key>ja.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
7j4cVfYmTo9Yfo4fA9g4AvzTLhw=
</data>
<key>hash2</key>
<data>
jxfNgX8MVlO7C53RGOjJwPc1dcSj9DxTyWW8N37BkaE=
</data>
<key>optional</key>
<true/>
</dict>
<key>pl.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
LfadkhKMCL8TGzZnnEP7Pbsb0I4=
</data>
<key>hash2</key>
<data>
BQCDVBKWaLio8bCJuurR2oc3ENu7K8c6N9ECEh98owU=
</data>
<key>optional</key>
<true/>
</dict>
<key>zh-Hans.lproj/QBImagePicker.strings</key>
<dict>
<key>hash</key>
<data>
0F5a3/dLVWvW3skcaXrrOpnhPv0=
</data>
<key>hash2</key>
<data>
smMz2/UyQzPo54Zq6lLJj+H42kPbfO4Bfy0iP1jCyf8=
</data>
<key>optional</key>
<true/>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
... ...
//
// CGGeometry+RSKImageCropper.h
//
// Copyright (c) 2015 Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <CoreGraphics/CoreGraphics.h>
#import <tgmath.h>
// tgmath functions aren't used on iOS when modules are enabled.
// Open Radar - http://www.openradar.me/16744288
// Work around this by redeclaring things here.
#undef cos
#define cos(__x) __tg_cos(__tg_promote1((__x))(__x))
#undef sin
#define sin(__x) __tg_sin(__tg_promote1((__x))(__x))
#undef atan2
#define atan2(__x, __y) __tg_atan2(__tg_promote2((__x), (__y))(__x), \
__tg_promote2((__x), (__y))(__y))
#undef pow
#define pow(__x, __y) __tg_pow(__tg_promote2((__x), (__y))(__x), \
__tg_promote2((__x), (__y))(__y))
#undef sqrt
#define sqrt(__x) __tg_sqrt(__tg_promote1((__x))(__x))
#undef fabs
#define fabs(__x) __tg_fabs(__tg_promote1((__x))(__x))
#undef ceil
#define ceil(__x) __tg_ceil(__tg_promote1((__x))(__x))
#undef floor
#define floor(__x) __tg_floor(__tg_promote1((__x))(__x))
#undef round
#define round(__x) __tg_round(__tg_promote1((__x))(__x))
#ifdef CGFLOAT_IS_DOUBLE
#define RSK_EPSILON DBL_EPSILON
#define RSK_MIN DBL_MIN
#else
#define RSK_EPSILON FLT_EPSILON
#define RSK_MIN FLT_MIN
#endif
// Line segments.
struct RSKLineSegment {
CGPoint start;
CGPoint end;
};
typedef struct RSKLineSegment RSKLineSegment;
// The "empty" point. This is the point returned when, for example, we
// intersect two disjoint line segments. Note that the null point is not the
// same as the zero point.
CG_EXTERN const CGPoint RSKPointNull;
// Returns the exact center point of the given rectangle.
CGPoint RSKRectCenterPoint(CGRect rect);
// Returns the `rect` scaled around the `point` by `sx` and `sy`.
CGRect RSKRectScaleAroundPoint(CGRect rect, CGPoint point, CGFloat sx, CGFloat sy);
// Returns true if `point' is the null point, false otherwise.
bool RSKPointIsNull(CGPoint point);
// Returns the `point` rotated around the `pivot` by `angle`.
CGPoint RSKPointRotateAroundPoint(CGPoint point, CGPoint pivot, CGFloat angle);
// Returns the distance between two points.
CGFloat RSKPointDistance(CGPoint p1, CGPoint p2);
// Make a line segment from two points `start` and `end`.
RSKLineSegment RSKLineSegmentMake(CGPoint start, CGPoint end);
// Returns the line segment rotated around the `pivot` by `angle`.
RSKLineSegment RSKLineSegmentRotateAroundPoint(RSKLineSegment lineSegment, CGPoint pivot, CGFloat angle);
// Returns the intersection of `ls1' and `ls2'. This may return a null point.
CGPoint RSKLineSegmentIntersection(RSKLineSegment ls1, RSKLineSegment ls2);
... ...
//
// RSKImageCropViewController+Protected.h
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
The methods in the RSKImageCropViewControllerProtectedMethods category
typically should only be called by subclasses which are implementing new
image crop view controllers. They may be overridden but must call super.
*/
@interface RSKImageCropViewController (RSKImageCropViewControllerProtectedMethods)
/**
Asynchronously crops the original image in accordance with the current settings and tells the delegate that the original image will be / has been cropped.
*/
- (void)cropImage;
/**
Tells the delegate that the crop has been canceled.
*/
- (void)cancelCrop;
/**
Resets the rotation angle, the position and the zoom scale of the original image to the default values.
@param animated Set this value to YES to animate the reset.
*/
- (void)reset:(BOOL)animated;
/**
Sets the current rotation angle of the image in radians.
@param rotationAngle The rotation angle of the image in radians.
*/
- (void)setRotationAngle:(CGFloat)rotationAngle;
/**
Sets the current scale factor for the image.
@param zoomScale The scale factor for the image.
*/
- (void)setZoomScale:(CGFloat)zoomScale;
@end
NS_ASSUME_NONNULL_END
... ...
//
// RSKImageCropViewController.h
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol RSKImageCropViewControllerDataSource;
@protocol RSKImageCropViewControllerDelegate;
/**
Types of supported crop modes.
*/
typedef NS_ENUM(NSUInteger, RSKImageCropMode) {
RSKImageCropModeCircle,
RSKImageCropModeSquare,
RSKImageCropModeCustom
};
@interface RSKImageCropViewController : UIViewController
/**
Designated initializer. Initializes and returns a newly allocated view controller object with the specified image.
@param originalImage The image for cropping.
*/
- (instancetype)initWithImage:(UIImage *)originalImage;
/**
Initializes and returns a newly allocated view controller object with the specified image and the specified crop mode.
@param originalImage The image for cropping.
@param cropMode The mode for cropping.
*/
- (instancetype)initWithImage:(UIImage *)originalImage cropMode:(RSKImageCropMode)cropMode;
///-----------------------------
/// @name Accessing the Delegate
///-----------------------------
/**
The receiver's delegate.
@discussion A `RSKImageCropViewControllerDelegate` delegate responds to messages sent by completing / canceling crop the image in the image crop view controller.
*/
@property (weak, nonatomic, nullable) id<RSKImageCropViewControllerDelegate> delegate;
/**
The receiver's data source.
@discussion A `RSKImageCropViewControllerDataSource` data source provides a custom rect and a custom path for the mask.
*/
@property (weak, nonatomic, nullable) id<RSKImageCropViewControllerDataSource> dataSource;
///--------------------------
/// @name Accessing the Image
///--------------------------
/**
The image for cropping.
*/
@property (strong, nonatomic) UIImage *originalImage;
/// -----------------------------------
/// @name Accessing the Mask Attributes
/// -----------------------------------
/**
The color of the layer with the mask. Default value is [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.7f].
*/
@property (copy, nonatomic) UIColor *maskLayerColor;
/**
The line width used when stroking the path of the mask layer. Default value is 1.0.
*/
@property (assign, nonatomic) CGFloat maskLayerLineWidth;
/**
The color to fill the stroked outline of the path of the mask layer, or nil for no stroking. Default valus is nil.
*/
@property (copy, nonatomic, nullable) UIColor *maskLayerStrokeColor;
/**
The rect of the mask.
@discussion Updating each time before the crop view lays out its subviews.
*/
@property (assign, readonly, nonatomic) CGRect maskRect;
/**
The path of the mask.
@discussion Updating each time before the crop view lays out its subviews.
*/
@property (copy, readonly, nonatomic) UIBezierPath *maskPath;
/// -----------------------------------
/// @name Accessing the Crop Attributes
/// -----------------------------------
/**
The mode for cropping. Default value is `RSKImageCropModeCircle`.
*/
@property (assign, nonatomic) RSKImageCropMode cropMode;
/**
The crop rectangle.
@discussion The value is calculated at run time.
*/
@property (readonly, nonatomic) CGRect cropRect;
/**
A value that specifies the current rotation angle of the image in radians.
@discussion The value is calculated at run time.
*/
@property (readonly, nonatomic) CGFloat rotationAngle;
/**
A floating-point value that specifies the current scale factor applied to the image.
@discussion The value is calculated at run time.
*/
@property (readonly, nonatomic) CGFloat zoomScale;
/**
A Boolean value that determines whether the image will always fill the mask space. Default value is `NO`.
*/
@property (assign, nonatomic) BOOL avoidEmptySpaceAroundImage;
/**
A Boolean value that determines whether the mask applies to the image after cropping. Default value is `NO`.
*/
@property (assign, nonatomic) BOOL applyMaskToCroppedImage;
/**
A Boolean value that controls whether the rotaion gesture is enabled. Default value is `NO`.
@discussion To support the rotation when `cropMode` is `RSKImageCropModeCustom` you must implement the data source method `imageCropViewControllerCustomMovementRect:`.
*/
@property (assign, getter=isRotationEnabled, nonatomic) BOOL rotationEnabled;
/// -------------------------------
/// @name Accessing the UI Elements
/// -------------------------------
/**
The Title Label.
*/
@property (strong, nonatomic, readonly) UILabel *moveAndScaleLabel;
/**
The Cancel Button.
*/
@property (strong, nonatomic, readonly) UIButton *cancelButton;
/**
The Choose Button.
*/
@property (strong, nonatomic, readonly) UIButton *chooseButton;
/// -------------------------------------------
/// @name Checking of the Interface Orientation
/// -------------------------------------------
/**
Returns a Boolean value indicating whether the user interface is currently presented in a portrait orientation.
@return YES if the interface orientation is portrait, otherwise returns NO.
*/
- (BOOL)isPortraitInterfaceOrientation;
/// -------------------------------------
/// @name Accessing the Layout Attributes
/// -------------------------------------
/**
The inset of the circle mask rect's area within the crop view's area in portrait orientation. Default value is `15.0f`.
*/
@property (assign, nonatomic) CGFloat portraitCircleMaskRectInnerEdgeInset;
/**
The inset of the square mask rect's area within the crop view's area in portrait orientation. Default value is `20.0f`.
*/
@property (assign, nonatomic) CGFloat portraitSquareMaskRectInnerEdgeInset;
/**
The vertical space between the top of the 'Move and Scale' label and the top of the crop view in portrait orientation. Default value is `64.0f`.
*/
@property (assign, nonatomic) CGFloat portraitMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
/**
The vertical space between the bottom of the crop view and the bottom of the 'Cancel' button in portrait orientation. Default value is `21.0f`.
*/
@property (assign, nonatomic) CGFloat portraitCropViewBottomAndCancelButtonBottomVerticalSpace;
/**
The vertical space between the bottom of the crop view and the bottom of the 'Choose' button in portrait orientation. Default value is `21.0f`.
*/
@property (assign, nonatomic) CGFloat portraitCropViewBottomAndChooseButtonBottomVerticalSpace;
/**
The horizontal space between the leading of the 'Cancel' button and the leading of the crop view in portrait orientation. Default value is `13.0f`.
*/
@property (assign, nonatomic) CGFloat portraitCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
/**
The horizontal space between the trailing of the crop view and the trailing of the 'Choose' button in portrait orientation. Default value is `13.0f`.
*/
@property (assign, nonatomic) CGFloat portraitCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
/**
The inset of the circle mask rect's area within the crop view's area in landscape orientation. Default value is `45.0f`.
*/
@property (assign, nonatomic) CGFloat landscapeCircleMaskRectInnerEdgeInset;
/**
The inset of the square mask rect's area within the crop view's area in landscape orientation. Default value is `45.0f`.
*/
@property (assign, nonatomic) CGFloat landscapeSquareMaskRectInnerEdgeInset;
/**
The vertical space between the top of the 'Move and Scale' label and the top of the crop view in landscape orientation. Default value is `12.0f`.
*/
@property (assign, nonatomic) CGFloat landscapeMoveAndScaleLabelTopAndCropViewTopVerticalSpace;
/**
The vertical space between the bottom of the crop view and the bottom of the 'Cancel' button in landscape orientation. Default value is `12.0f`.
*/
@property (assign, nonatomic) CGFloat landscapeCropViewBottomAndCancelButtonBottomVerticalSpace;
/**
The vertical space between the bottom of the crop view and the bottom of the 'Choose' button in landscape orientation. Default value is `12.0f`.
*/
@property (assign, nonatomic) CGFloat landscapeCropViewBottomAndChooseButtonBottomVerticalSpace;
/**
The horizontal space between the leading of the 'Cancel' button and the leading of the crop view in landscape orientation. Default value is `13.0f`.
*/
@property (assign, nonatomic) CGFloat landscapeCancelButtonLeadingAndCropViewLeadingHorizontalSpace;
/**
The horizontal space between the trailing of the crop view and the trailing of the 'Choose' button in landscape orientation. Default value is `13.0f`.
*/
@property (assign, nonatomic) CGFloat landscapeCropViewTrailingAndChooseButtonTrailingHorizontalSpace;
@end
/**
The `RSKImageCropViewControllerDataSource` protocol is adopted by an object that provides a custom rect and a custom path for the mask.
*/
@protocol RSKImageCropViewControllerDataSource <NSObject>
/**
Asks the data source a custom rect for the mask.
@param controller The crop view controller object to whom a rect is provided.
@return A custom rect for the mask.
@discussion Only valid if `cropMode` is `RSKImageCropModeCustom`.
*/
- (CGRect)imageCropViewControllerCustomMaskRect:(RSKImageCropViewController *)controller;
/**
Asks the data source a custom path for the mask.
@param controller The crop view controller object to whom a path is provided.
@return A custom path for the mask.
@discussion Only valid if `cropMode` is `RSKImageCropModeCustom`.
*/
- (UIBezierPath *)imageCropViewControllerCustomMaskPath:(RSKImageCropViewController *)controller;
@optional
/**
Asks the data source a custom rect in which the image can be moved.
@param controller The crop view controller object to whom a rect is provided.
@return A custom rect in which the image can be moved.
@discussion Only valid if `cropMode` is `RSKImageCropModeCustom`. If you want to support the rotation when `cropMode` is `RSKImageCropModeCustom` you must implement it. Will be marked as `required` in version `2.0.0`.
*/
- (CGRect)imageCropViewControllerCustomMovementRect:(RSKImageCropViewController *)controller;
@end
/**
The `RSKImageCropViewControllerDelegate` protocol defines messages sent to a image crop view controller delegate when crop image was canceled or the original image was cropped.
*/
@protocol RSKImageCropViewControllerDelegate <NSObject>
@optional
/**
Tells the delegate that crop image has been canceled.
*/
- (void)imageCropViewControllerDidCancelCrop:(RSKImageCropViewController *)controller;
/**
Tells the delegate that the original image will be cropped.
*/
- (void)imageCropViewController:(RSKImageCropViewController *)controller willCropImage:(UIImage *)originalImage;
/**
Tells the delegate that the original image has been cropped. Additionally provides a crop rect used to produce image.
*/
- (void)imageCropViewController:(RSKImageCropViewController *)controller didCropImage:(UIImage *)croppedImage usingCropRect:(CGRect)cropRect;
/**
Tells the delegate that the original image has been cropped. Additionally provides a crop rect and a rotation angle used to produce image.
*/
- (void)imageCropViewController:(RSKImageCropViewController *)controller didCropImage:(UIImage *)croppedImage usingCropRect:(CGRect)cropRect rotationAngle:(CGFloat)rotationAngle;
@end
NS_ASSUME_NONNULL_END
... ...
//
// RSKImageCropper.h
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/**
`RSKImageCropper` is an image cropper for iOS like in the Contacts app with support for landscape orientation.
*/
#import <Foundation/Foundation.h>
//! Project version number for RSKImageCropper.
FOUNDATION_EXPORT double RSKImageCropperVersionNumber;
//! Project version string for RSKImageCropper.
FOUNDATION_EXPORT const unsigned char RSKImageCropperVersionString[];
#import <RSKImageCropper/CGGeometry+RSKImageCropper.h>
#import <RSKImageCropper/RSKImageCropViewController.h>
#import <RSKImageCropper/RSKImageCropViewController+Protected.h>
#import <RSKImageCropper/RSKImageScrollView.h>
#import <RSKImageCropper/RSKInternalUtility.h>
#import <RSKImageCropper/RSKTouchView.h>
#import <RSKImageCropper/UIApplication+RSKImageCropper.h>
#import <RSKImageCropper/UIImage+RSKImageCropper.h>
... ...
/*
File: RSKImageScrollView.h
Abstract: Centers image within the scroll view and configures image sizing and display.
Version: 1.3 modified by Ruslan Skorb on 8/24/14.
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2012 Apple Inc. All Rights Reserved.
*/
#import <UIKit/UIKit.h>
@interface RSKImageScrollView : UIScrollView
@property (nonatomic, strong) UIImageView *zoomView;
@property (nonatomic, assign) BOOL aspectFill;
- (void)displayImage:(UIImage *)image;
@end
... ...
//
// RSKInternalUtility.h
// RSKImageCropperExample
//
// Created by Ruslan Skorb on 9/5/15.
// Copyright (c) 2015 Ruslan Skorb. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
Returns a localized version of the string designated by the specified key and residing in the RSKImageCropper table.
@param key The key for a string in the RSKImageCropper table.
@param comment The comment to place above the key-value pair in the strings file.
@return A localized version of the string designated by key in the RSKImageCropper table.
*/
FOUNDATION_EXPORT NSString * RSKLocalizedString(NSString *key, NSString *comment);
@interface RSKInternalUtility : NSObject
/**
Returns the NSBundle object for returning localized strings.
@return The NSBundle object for returning localized strings.
@discussion We assume a convention of a bundle named RSKImageCropperStrings.bundle, otherwise we
return the bundle associated with the RSKInternalUtility class.
*/
+ (NSBundle *)bundleForStrings;
@end
... ...
//
// RSKTouchView.h
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <UIKit/UIKit.h>
@interface RSKTouchView : UIView
@property (weak, nonatomic) UIView *receiver;
@end
... ...
//
// UIApplication+RSKImageCropper.h
//
// Copyright (c) 2015 Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <UIKit/UIKit.h>
/**
The category `RSKImageCropper` of the class `UIApplication` provides the method `rsk_sharedApplication` which returns `nil` in an application extension, otherwise it returns the singleton app instance.
*/
@interface UIApplication (RSKImageCropper)
/**
Returns `nil` in an application extension, otherwise returns the singleton app instance.
@return `nil` in an application extension, otherwise the app instance is created in the `UIApplicationMain` function.
*/
+ (UIApplication *)rsk_sharedApplication;
@end
... ...
//
// UIImage+RSKImageCropper.h
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <UIKit/UIKit.h>
@interface UIImage (RSKImageCropper)
// Fix the orientation of the image.
- (UIImage *)fixOrientation;
// Rotate the image clockwise around the center by the angle, in radians.
- (UIImage *)rotateByAngle:(CGFloat)angleInRadians;
@end
... ...
framework module RSKImageCropper {
umbrella header "RSKImageCropper.h"
export *
module * { export * }
}
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "نقل وتحجيم";
/* Cancel button */
"Cancel" = "إلغاء";
/* Choose button */
"Choose" = "تحديد";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Moure i escalar";
/* Cancel button */
"Cancel" = "Cancel·lar";
/* Choose button */
"Choose" = "Seleccionar";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Vyberte výřez";
/* Cancel button */
"Cancel" = "Zrušit";
/* Choose button */
"Choose" = "Vybrat";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Flyt og skaler";
/* Cancel button */
"Cancel" = "Annuller";
/* Choose button */
"Choose" = "Vælg";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Bewegen und Skalieren";
/* Cancel button */
"Cancel" = "Abbrechen";
/* Choose button */
"Choose" = "Auswählen";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Aλλaγή θεσης και μεγεθoυς";
/* Cancel button */
"Cancel" = "Ακύρωση";
/* Choose button */
"Choose" = "Eπιλoγή";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Move and Scale";
/* Cancel button */
"Cancel" = "Cancel";
/* Choose button */
"Choose" = "Choose";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Mover y escalar";
/* Cancel button */
"Cancel" = "Cancelar";
/* Choose button */
"Choose" = "Usar";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Siirrä ja skaalaa";
/* Cancel button */
"Cancel" = "Kumoa";
/* Choose button */
"Choose" = "Valitse";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Recadrer";
/* Cancel button */
"Cancel" = "Annuler";
/* Choose button */
"Choose" = "Choisir";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "הזז/י והתאם/י גודל";
/* Cancel button */
"Cancel" = "ביטול";
/* Choose button */
"Choose" = "בחר/י";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "हिलाकर आकार बदलें";
/* Cancel button */
"Cancel" = "रद्द करें";
/* Choose button */
"Choose" = "चुनें";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Pomaknite i podesite";
/* Cancel button */
"Cancel" = "Poništi";
/* Choose button */
"Choose" = "Odaberi";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Mozgatás és méretezés";
/* Cancel button */
"Cancel" = "Mégsem";
/* Choose button */
"Choose" = "Kiválasztás";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Geser dan Ubah Skala";
/* Cancel button */
"Cancel" = "Batalkan";
/* Choose button */
"Choose" = "Pilih";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Sposta e ridimensiona";
/* Cancel button */
"Cancel" = "Annulla";
/* Choose button */
"Choose" = "Scegli";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "移動と拡大縮小";
/* Cancel button */
"Cancel" = "キャンセル";
/* Choose button */
"Choose" = "選択";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Alih dan Skala";
/* Cancel button */
"Cancel" = "Batal";
/* Choose button */
"Choose" = "Pilih";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Flytt og skaler";
/* Cancel button */
"Cancel" = "Avbryt";
/* Choose button */
"Choose" = "Velg";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Verplaats en wijzig grootte";
/* Cancel button */
"Cancel" = "Annuleer";
/* Choose button */
"Choose" = "Kies";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Wybierz kadr";
/* Cancel button */
"Cancel" = "Anuluj";
/* Choose button */
"Choose" = "Wybierz";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Mover e Redimensionar";
/* Cancel button */
"Cancel" = "Cancelar";
/* Choose button */
"Choose" = "Escolher";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Mover e dimensionar";
/* Cancel button */
"Cancel" = "Cancelar";
/* Choose button */
"Choose" = "Escolher";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Mutaţi și redimensionaţi";
/* Cancel button */
"Cancel" = "Anulaţi";
/* Choose button */
"Choose" = "Selectaţi";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Cдвиг и масштаб";
/* Cancel button */
"Cancel" = "Отменить";
/* Choose button */
"Choose" = "Выбрать";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Posunutie a veľkosť";
/* Cancel button */
"Cancel" = "Zrušiť";
/* Choose button */
"Choose" = "Vybrať";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Flytta och skalanpassa";
/* Cancel button */
"Cancel" = "Avbryt";
/* Choose button */
"Choose" = "Välj";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Taşı ve Ölçekle";
/* Cancel button */
"Cancel" = "Vazgeç";
/* Choose button */
"Choose" = "Seç";
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Pyxaйтe i вiдмipяйтe";
/* Cancel button */
"Cancel" = "Скасувати";
/* Choose button */
"Choose" = "Вибрати";
\ No newline at end of file
... ...
//
// RSKImageCropper.strings
//
// Copyright (c) 2014-present Ruslan Skorb, http://ruslanskorb.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/* Move and Scale label */
"Move and Scale" = "Di chuyển và Chia tý lệ";
/* Cancel button */
"Cancel" = "Hủy";
/* Choose button */
"Choose" = "Chọn";
... ...