@capawesome/capacitor-clipboard 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapawesomeCapacitorClipboard.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +220 -0
- package/android/build.gradle +58 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/Clipboard.java +141 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/ClipboardPlugin.java +96 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/classes/ClipboardContentType.java +22 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/classes/CustomExceptions.java +12 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/classes/options/WriteOptions.java +60 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/classes/results/ReadResult.java +29 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/interfaces/Callback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/interfaces/EmptyCallback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/interfaces/NonEmptyResultCallback.java +7 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/interfaces/Result.java +7 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +249 -0
- package/dist/esm/definitions.d.ts +144 -0
- package/dist/esm/definitions.js +57 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +16 -0
- package/dist/esm/web.js +109 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +179 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +182 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Classes/Options/WriteOptions.swift +19 -0
- package/ios/Plugin/Classes/Results/ReadResult.swift +19 -0
- package/ios/Plugin/Clipboard.swift +74 -0
- package/ios/Plugin/ClipboardPlugin.swift +62 -0
- package/ios/Plugin/Enums/ClipboardContentType.swift +8 -0
- package/ios/Plugin/Enums/CustomError.swift +36 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Protocols/Result.swift +5 -0
- package/package.json +94 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapawesomeCapacitorClipboard'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '15.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robin Genz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapawesomeCapacitorClipboard",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapawesomeCapacitorClipboard",
|
|
10
|
+
targets: ["ClipboardPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "ClipboardPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Plugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "ClipboardPluginTests",
|
|
25
|
+
dependencies: ["ClipboardPlugin"],
|
|
26
|
+
path: "ios/PluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Capacitor Clipboard Plugin
|
|
2
|
+
|
|
3
|
+
Capacitor plugin to read from and write to the system clipboard.
|
|
4
|
+
|
|
5
|
+
<div class="capawesome-z29o10a">
|
|
6
|
+
<a href="https://cloud.capawesome.io/" target="_blank">
|
|
7
|
+
<img alt="Deliver Live Updates to your Capacitor app with Capawesome Cloud" src="https://cloud.capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
|
|
8
|
+
</a>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 📋 **Read & Write**: Read from and write to the system clipboard.
|
|
14
|
+
- 🖼️ **Images**: Copy and paste real images, not just data URLs as plain text.
|
|
15
|
+
- 📝 **Rich Text**: Write HTML content with a plain-text fallback.
|
|
16
|
+
- 🔗 **URLs**: Copy URLs as native URL clipboard items.
|
|
17
|
+
- 🌐 **Cross-platform**: Works on Android, iOS, and the web.
|
|
18
|
+
- 🔒 **App Store safe**: Uses only official platform APIs.
|
|
19
|
+
- 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS.
|
|
20
|
+
- 🔁 **Up-to-date**: Always supports the latest Capacitor version.
|
|
21
|
+
|
|
22
|
+
Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look!
|
|
23
|
+
|
|
24
|
+
## Newsletter
|
|
25
|
+
|
|
26
|
+
Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/).
|
|
27
|
+
|
|
28
|
+
## Compatibility
|
|
29
|
+
|
|
30
|
+
| Plugin Version | Capacitor Version | Status |
|
|
31
|
+
| -------------- | ----------------- | -------------- |
|
|
32
|
+
| 0.x.x | >=8.x.x | Active support |
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
You can use our **AI-Assisted Setup** to install the plugin.
|
|
37
|
+
Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx skills add capawesome-team/skills --skill capacitor-plugins
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then use the following prompt:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-clipboard` plugin in my project.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @capawesome/capacitor-clipboard
|
|
53
|
+
npx cap sync
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Android
|
|
57
|
+
|
|
58
|
+
No additional configuration is required for this plugin.
|
|
59
|
+
|
|
60
|
+
### iOS
|
|
61
|
+
|
|
62
|
+
No additional configuration is required for this plugin.
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
No configuration required for this plugin.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Clipboard } from '@capawesome/capacitor-clipboard';
|
|
72
|
+
|
|
73
|
+
const writeText = async () => {
|
|
74
|
+
await Clipboard.write({ text: 'Hello World' });
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const writeHtml = async () => {
|
|
78
|
+
await Clipboard.write({
|
|
79
|
+
html: '<b>Hello World</b>',
|
|
80
|
+
text: 'Hello World',
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const writeImage = async () => {
|
|
85
|
+
await Clipboard.write({
|
|
86
|
+
image: 'data:image/png;base64,iVBORw0KGgo...',
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const writeUrl = async () => {
|
|
91
|
+
await Clipboard.write({ url: 'https://capawesome.io' });
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const read = async () => {
|
|
95
|
+
const { type, value } = await Clipboard.read();
|
|
96
|
+
console.log('Type:', type, 'Value:', value);
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API
|
|
101
|
+
|
|
102
|
+
<docgen-index>
|
|
103
|
+
|
|
104
|
+
* [`read()`](#read)
|
|
105
|
+
* [`write(...)`](#write)
|
|
106
|
+
* [Interfaces](#interfaces)
|
|
107
|
+
* [Enums](#enums)
|
|
108
|
+
|
|
109
|
+
</docgen-index>
|
|
110
|
+
|
|
111
|
+
<docgen-api>
|
|
112
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
113
|
+
|
|
114
|
+
### read()
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
read() => Promise<ReadResult>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Read the current content of the system clipboard.
|
|
121
|
+
|
|
122
|
+
On Android, reading the clipboard is only possible while the app is in the
|
|
123
|
+
foreground.
|
|
124
|
+
|
|
125
|
+
On iOS, reading the clipboard displays a system paste notification. This is
|
|
126
|
+
expected behavior and cannot be suppressed.
|
|
127
|
+
|
|
128
|
+
**Returns:** <code>Promise<<a href="#readresult">ReadResult</a>></code>
|
|
129
|
+
|
|
130
|
+
**Since:** 0.1.0
|
|
131
|
+
|
|
132
|
+
--------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
### write(...)
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
write(options: WriteOptions) => Promise<void>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Write content to the system clipboard.
|
|
142
|
+
|
|
143
|
+
Exactly one of `text`, `html`, `image` or `url` must be provided. The
|
|
144
|
+
`html` property may additionally be combined with `text` to provide a
|
|
145
|
+
plain-text fallback.
|
|
146
|
+
|
|
147
|
+
| Param | Type |
|
|
148
|
+
| ------------- | ----------------------------------------------------- |
|
|
149
|
+
| **`options`** | <code><a href="#writeoptions">WriteOptions</a></code> |
|
|
150
|
+
|
|
151
|
+
**Since:** 0.1.0
|
|
152
|
+
|
|
153
|
+
--------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
### Interfaces
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
#### ReadResult
|
|
160
|
+
|
|
161
|
+
| Prop | Type | Description | Since |
|
|
162
|
+
| ----------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----- |
|
|
163
|
+
| **`type`** | <code><a href="#clipboardcontenttype">ClipboardContentType</a></code> | The type of the content that was read from the clipboard. | 0.1.0 |
|
|
164
|
+
| **`value`** | <code>string</code> | The content that was read from the clipboard. Images are returned as a Base64-encoded data URL. | 0.1.0 |
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
#### WriteOptions
|
|
168
|
+
|
|
169
|
+
| Prop | Type | Description | Since |
|
|
170
|
+
| ----------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
171
|
+
| **`html`** | <code>string</code> | The HTML content to write to the clipboard. Combine this with `text` to provide a plain-text fallback for apps that cannot handle HTML content. | 0.1.0 |
|
|
172
|
+
| **`image`** | <code>string</code> | The image to write to the clipboard as a Base64-encoded data URL. | 0.1.0 |
|
|
173
|
+
| **`label`** | <code>string</code> | The label used to describe the clipboard content. Only available on Android. | 0.1.0 |
|
|
174
|
+
| **`text`** | <code>string</code> | The plain text to write to the clipboard. | 0.1.0 |
|
|
175
|
+
| **`url`** | <code>string</code> | The URL to write to the clipboard. | 0.1.0 |
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
### Enums
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
#### ClipboardContentType
|
|
182
|
+
|
|
183
|
+
| Members | Value | Description | Since |
|
|
184
|
+
| ----------- | -------------------- | --------------------------------------------------------------- | ----- |
|
|
185
|
+
| **`Html`** | <code>'HTML'</code> | The content is HTML. | 0.1.0 |
|
|
186
|
+
| **`Image`** | <code>'IMAGE'</code> | The content is an image, returned as a Base64-encoded data URL. | 0.1.0 |
|
|
187
|
+
| **`Text`** | <code>'TEXT'</code> | The content is plain text. | 0.1.0 |
|
|
188
|
+
| **`Url`** | <code>'URL'</code> | The content is a URL. | 0.1.0 |
|
|
189
|
+
|
|
190
|
+
</docgen-api>
|
|
191
|
+
|
|
192
|
+
## Platform Behavior
|
|
193
|
+
|
|
194
|
+
### Android
|
|
195
|
+
|
|
196
|
+
- On Android 10 (API level 29) and later, apps can only read the clipboard while they are in the foreground and have input focus. The `read(...)` method rejects otherwise.
|
|
197
|
+
- On Android 12 (API level 31) and later, the system shows a toast message when an app reads the clipboard content that was written by another app. This is expected behavior and cannot be suppressed.
|
|
198
|
+
|
|
199
|
+
### iOS
|
|
200
|
+
|
|
201
|
+
- On iOS 14 and later, the system shows a paste notification banner every time the clipboard is read. This is expected behavior and cannot be suppressed.
|
|
202
|
+
|
|
203
|
+
## Migrating from `@capacitor/clipboard`
|
|
204
|
+
|
|
205
|
+
This plugin is a drop-in alternative to the official [`@capacitor/clipboard`](https://github.com/ionic-team/capacitor-plugins/tree/main/clipboard) plugin with real image support on Android and HTML support on all platforms. The following differences apply:
|
|
206
|
+
|
|
207
|
+
| `@capacitor/clipboard` | `@capawesome/capacitor-clipboard` |
|
|
208
|
+
| ------------------------------------------ | ------------------------------------------------------ |
|
|
209
|
+
| `write({ string: 'Hello' })` | `write({ text: 'Hello' })` |
|
|
210
|
+
| `write({ image: dataUrl })` (text-only on Android) | `write({ image: dataUrl })` (real image on all platforms) |
|
|
211
|
+
| — | `write({ html: '<b>Hello</b>', text: 'Hello' })` |
|
|
212
|
+
| `read() → { value, type: 'text/plain' }` | `read() → { value, type: 'TEXT' }` |
|
|
213
|
+
|
|
214
|
+
## Changelog
|
|
215
|
+
|
|
216
|
+
See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/clipboard/CHANGELOG.md).
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/clipboard/LICENSE).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace = "io.capawesome.capacitorjs.plugins.clipboard"
|
|
22
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
26
|
+
versionCode 1
|
|
27
|
+
versionName "1.0"
|
|
28
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
29
|
+
}
|
|
30
|
+
buildTypes {
|
|
31
|
+
release {
|
|
32
|
+
minifyEnabled false
|
|
33
|
+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError = false
|
|
38
|
+
}
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
repositories {
|
|
46
|
+
google()
|
|
47
|
+
mavenCentral()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
dependencies {
|
|
52
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
53
|
+
implementation project(':capacitor-android')
|
|
54
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
55
|
+
testImplementation "junit:junit:$junitVersion"
|
|
56
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
57
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
58
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.clipboard;
|
|
2
|
+
|
|
3
|
+
import android.content.ClipData;
|
|
4
|
+
import android.content.ClipboardManager;
|
|
5
|
+
import android.content.Context;
|
|
6
|
+
import android.graphics.Bitmap;
|
|
7
|
+
import android.graphics.BitmapFactory;
|
|
8
|
+
import android.net.Uri;
|
|
9
|
+
import android.util.Base64;
|
|
10
|
+
import androidx.annotation.NonNull;
|
|
11
|
+
import androidx.annotation.Nullable;
|
|
12
|
+
import androidx.core.content.FileProvider;
|
|
13
|
+
import io.capawesome.capacitorjs.plugins.clipboard.classes.ClipboardContentType;
|
|
14
|
+
import io.capawesome.capacitorjs.plugins.clipboard.classes.CustomExceptions;
|
|
15
|
+
import io.capawesome.capacitorjs.plugins.clipboard.classes.options.WriteOptions;
|
|
16
|
+
import io.capawesome.capacitorjs.plugins.clipboard.classes.results.ReadResult;
|
|
17
|
+
import io.capawesome.capacitorjs.plugins.clipboard.interfaces.EmptyCallback;
|
|
18
|
+
import io.capawesome.capacitorjs.plugins.clipboard.interfaces.NonEmptyResultCallback;
|
|
19
|
+
import java.io.ByteArrayOutputStream;
|
|
20
|
+
import java.io.File;
|
|
21
|
+
import java.io.FileOutputStream;
|
|
22
|
+
import java.io.InputStream;
|
|
23
|
+
|
|
24
|
+
public class Clipboard {
|
|
25
|
+
|
|
26
|
+
private static final String DEFAULT_LABEL = "";
|
|
27
|
+
private static final String IMAGE_FILE_NAME = "capawesome_capacitor_clipboard_image.png";
|
|
28
|
+
|
|
29
|
+
@NonNull
|
|
30
|
+
private final ClipboardPlugin plugin;
|
|
31
|
+
|
|
32
|
+
public Clipboard(@NonNull ClipboardPlugin plugin) {
|
|
33
|
+
this.plugin = plugin;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public void read(@NonNull NonEmptyResultCallback<ReadResult> callback) {
|
|
37
|
+
try {
|
|
38
|
+
ClipboardManager clipboardManager = getClipboardManager();
|
|
39
|
+
ClipData clipData = clipboardManager.getPrimaryClip();
|
|
40
|
+
if (clipData == null || clipData.getItemCount() == 0) {
|
|
41
|
+
callback.error(CustomExceptions.EMPTY_CLIPBOARD);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
ClipData.Item item = clipData.getItemAt(0);
|
|
45
|
+
ReadResult result = createReadResult(item);
|
|
46
|
+
if (result == null) {
|
|
47
|
+
callback.error(CustomExceptions.EMPTY_CLIPBOARD);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
callback.success(result);
|
|
51
|
+
} catch (Exception exception) {
|
|
52
|
+
callback.error(CustomExceptions.READ_FAILED);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public void write(@NonNull WriteOptions options, @NonNull EmptyCallback callback) {
|
|
57
|
+
try {
|
|
58
|
+
String label = options.getLabel() == null ? DEFAULT_LABEL : options.getLabel();
|
|
59
|
+
ClipData clipData;
|
|
60
|
+
if (options.getImage() != null) {
|
|
61
|
+
clipData = createImageClipData(label, options.getImage());
|
|
62
|
+
} else if (options.getHtml() != null) {
|
|
63
|
+
String text = options.getText() == null ? options.getHtml() : options.getText();
|
|
64
|
+
clipData = ClipData.newHtmlText(label, text, options.getHtml());
|
|
65
|
+
} else if (options.getUrl() != null) {
|
|
66
|
+
clipData = ClipData.newRawUri(label, Uri.parse(options.getUrl()));
|
|
67
|
+
} else {
|
|
68
|
+
clipData = ClipData.newPlainText(label, options.getText());
|
|
69
|
+
}
|
|
70
|
+
getClipboardManager().setPrimaryClip(clipData);
|
|
71
|
+
callback.success();
|
|
72
|
+
} catch (Exception exception) {
|
|
73
|
+
callback.error(CustomExceptions.WRITE_FAILED);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@NonNull
|
|
78
|
+
private ClipData createImageClipData(@NonNull String label, @NonNull String image) throws Exception {
|
|
79
|
+
byte[] bytes = decodeDataUrl(image);
|
|
80
|
+
File file = new File(getContext().getCacheDir(), IMAGE_FILE_NAME);
|
|
81
|
+
try (FileOutputStream outputStream = new FileOutputStream(file)) {
|
|
82
|
+
outputStream.write(bytes);
|
|
83
|
+
}
|
|
84
|
+
String authority = getContext().getPackageName() + ".fileprovider";
|
|
85
|
+
Uri uri = FileProvider.getUriForFile(getContext(), authority, file);
|
|
86
|
+
return ClipData.newUri(getContext().getContentResolver(), label, uri);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Nullable
|
|
90
|
+
private ReadResult createReadResult(@NonNull ClipData.Item item) throws Exception {
|
|
91
|
+
Uri uri = item.getUri();
|
|
92
|
+
if (uri != null) {
|
|
93
|
+
String mimeType = getContext().getContentResolver().getType(uri);
|
|
94
|
+
if (mimeType != null && mimeType.startsWith("image/")) {
|
|
95
|
+
return new ReadResult(ClipboardContentType.IMAGE, encodeImageAsDataUrl(uri));
|
|
96
|
+
}
|
|
97
|
+
return new ReadResult(ClipboardContentType.URL, uri.toString());
|
|
98
|
+
}
|
|
99
|
+
String html = item.getHtmlText();
|
|
100
|
+
if (html != null) {
|
|
101
|
+
return new ReadResult(ClipboardContentType.HTML, html);
|
|
102
|
+
}
|
|
103
|
+
CharSequence text = item.getText();
|
|
104
|
+
if (text != null) {
|
|
105
|
+
String value = text.toString();
|
|
106
|
+
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
107
|
+
return new ReadResult(ClipboardContentType.URL, value);
|
|
108
|
+
}
|
|
109
|
+
return new ReadResult(ClipboardContentType.TEXT, value);
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@NonNull
|
|
115
|
+
private byte[] decodeDataUrl(@NonNull String dataUrl) throws Exception {
|
|
116
|
+
int index = dataUrl.indexOf(',');
|
|
117
|
+
String base64 = index == -1 ? dataUrl : dataUrl.substring(index + 1);
|
|
118
|
+
return Base64.decode(base64, Base64.DEFAULT);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@NonNull
|
|
122
|
+
private String encodeImageAsDataUrl(@NonNull Uri uri) throws Exception {
|
|
123
|
+
try (InputStream inputStream = getContext().getContentResolver().openInputStream(uri)) {
|
|
124
|
+
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
|
125
|
+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
126
|
+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
|
|
127
|
+
String base64 = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP);
|
|
128
|
+
return "data:image/png;base64," + base64;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@NonNull
|
|
133
|
+
private ClipboardManager getClipboardManager() {
|
|
134
|
+
return (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@NonNull
|
|
138
|
+
private Context getContext() {
|
|
139
|
+
return plugin.getContext();
|
|
140
|
+
}
|
|
141
|
+
}
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/clipboard/ClipboardPlugin.java
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.clipboard;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
import com.getcapacitor.Logger;
|
|
6
|
+
import com.getcapacitor.Plugin;
|
|
7
|
+
import com.getcapacitor.PluginCall;
|
|
8
|
+
import com.getcapacitor.PluginMethod;
|
|
9
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
10
|
+
import io.capawesome.capacitorjs.plugins.clipboard.classes.CustomException;
|
|
11
|
+
import io.capawesome.capacitorjs.plugins.clipboard.classes.options.WriteOptions;
|
|
12
|
+
import io.capawesome.capacitorjs.plugins.clipboard.classes.results.ReadResult;
|
|
13
|
+
import io.capawesome.capacitorjs.plugins.clipboard.interfaces.EmptyCallback;
|
|
14
|
+
import io.capawesome.capacitorjs.plugins.clipboard.interfaces.NonEmptyResultCallback;
|
|
15
|
+
import io.capawesome.capacitorjs.plugins.clipboard.interfaces.Result;
|
|
16
|
+
|
|
17
|
+
@CapacitorPlugin(name = "Clipboard")
|
|
18
|
+
public class ClipboardPlugin extends Plugin {
|
|
19
|
+
|
|
20
|
+
public static final String ERROR_UNKNOWN_ERROR = "An unknown error has occurred.";
|
|
21
|
+
public static final String TAG = "ClipboardPlugin";
|
|
22
|
+
|
|
23
|
+
private Clipboard implementation;
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public void load() {
|
|
27
|
+
super.load();
|
|
28
|
+
this.implementation = new Clipboard(this);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@PluginMethod
|
|
32
|
+
public void read(PluginCall call) {
|
|
33
|
+
try {
|
|
34
|
+
NonEmptyResultCallback<ReadResult> callback = new NonEmptyResultCallback<>() {
|
|
35
|
+
@Override
|
|
36
|
+
public void success(@NonNull ReadResult result) {
|
|
37
|
+
resolveCall(call, result);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Override
|
|
41
|
+
public void error(Exception exception) {
|
|
42
|
+
rejectCall(call, exception);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
implementation.read(callback);
|
|
46
|
+
} catch (Exception exception) {
|
|
47
|
+
rejectCall(call, exception);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@PluginMethod
|
|
52
|
+
public void write(PluginCall call) {
|
|
53
|
+
try {
|
|
54
|
+
WriteOptions options = new WriteOptions(call);
|
|
55
|
+
EmptyCallback callback = new EmptyCallback() {
|
|
56
|
+
@Override
|
|
57
|
+
public void success() {
|
|
58
|
+
resolveCall(call);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Override
|
|
62
|
+
public void error(Exception exception) {
|
|
63
|
+
rejectCall(call, exception);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
implementation.write(options, callback);
|
|
67
|
+
} catch (Exception exception) {
|
|
68
|
+
rejectCall(call, exception);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private void rejectCall(@NonNull PluginCall call, @NonNull Exception exception) {
|
|
73
|
+
String message = exception.getMessage();
|
|
74
|
+
if (message == null) {
|
|
75
|
+
message = ERROR_UNKNOWN_ERROR;
|
|
76
|
+
}
|
|
77
|
+
String code = null;
|
|
78
|
+
if (exception instanceof CustomException) {
|
|
79
|
+
code = ((CustomException) exception).getCode();
|
|
80
|
+
}
|
|
81
|
+
Logger.error(TAG, message, exception);
|
|
82
|
+
call.reject(message, code);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private void resolveCall(@NonNull PluginCall call) {
|
|
86
|
+
call.resolve();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private void resolveCall(@NonNull PluginCall call, @Nullable Result result) {
|
|
90
|
+
if (result == null) {
|
|
91
|
+
call.resolve();
|
|
92
|
+
} else {
|
|
93
|
+
call.resolve(result.toJSObject());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.clipboard.classes;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
public enum ClipboardContentType {
|
|
6
|
+
HTML("HTML"),
|
|
7
|
+
IMAGE("IMAGE"),
|
|
8
|
+
TEXT("TEXT"),
|
|
9
|
+
URL("URL");
|
|
10
|
+
|
|
11
|
+
@NonNull
|
|
12
|
+
private final String value;
|
|
13
|
+
|
|
14
|
+
ClipboardContentType(@NonNull String value) {
|
|
15
|
+
this.value = value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@NonNull
|
|
19
|
+
public String getValue() {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.clipboard.classes;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
public class CustomException extends Exception {
|
|
7
|
+
|
|
8
|
+
@Nullable
|
|
9
|
+
private final String code;
|
|
10
|
+
|
|
11
|
+
public CustomException(@Nullable String code, @NonNull String message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Nullable
|
|
17
|
+
public String getCode() {
|
|
18
|
+
return code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.clipboard.classes;
|
|
2
|
+
|
|
3
|
+
public class CustomExceptions {
|
|
4
|
+
|
|
5
|
+
public static final CustomException CONTENT_MISSING = new CustomException(null, "One of text, html, image or url must be provided.");
|
|
6
|
+
public static final CustomException EMPTY_CLIPBOARD = new CustomException("EMPTY_CLIPBOARD", "The clipboard is empty.");
|
|
7
|
+
public static final CustomException READ_FAILED = new CustomException("READ_FAILED", "The clipboard content could not be read.");
|
|
8
|
+
public static final CustomException WRITE_FAILED = new CustomException(
|
|
9
|
+
"WRITE_FAILED",
|
|
10
|
+
"The content could not be written to the clipboard."
|
|
11
|
+
);
|
|
12
|
+
}
|