├── .circleci
└── config.yml
├── .editorconfig
├── .eslintignore
├── .gitattributes
├── .gitignore
├── Bubble-select-ios.gif
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── advanced-example.gif
├── android-example.gif
├── android
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── reactnativebubbleselect.iml
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── reactnativebubbleselect
│ │ ├── BubbleDeselectNodeEvent.kt
│ │ ├── BubbleRemoveNodeEvent.kt
│ │ ├── BubbleSelectNodeEvent.kt
│ │ ├── BubbleSelectNodeView.kt
│ │ ├── BubbleSelectNodeViewManager.kt
│ │ ├── BubbleSelectPackage.kt
│ │ ├── BubbleSelectView.kt
│ │ └── BubbleSelectViewManager.kt
│ └── res
│ └── layout
│ ├── bubble_node.xml
│ └── bubble_view.xml
├── babel.config.js
├── bubble-min.mp4
├── commitlint.config.js
├── example
├── android
│ ├── .project
│ ├── .settings
│ │ └── org.eclipse.buildship.core.prefs
│ ├── BubbleSelectExample.iml
│ ├── app
│ │ ├── app.iml
│ │ ├── build.gradle
│ │ ├── debug.keystore
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── BubbleSelectExample
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MainApplication.java
│ │ │ └── res
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── app.json
├── index.tsx
├── ios
│ ├── BubbleSelectExample.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── BubbleSelectExample.xcscheme
│ ├── BubbleSelectExample.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── BubbleSelectExample
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.xib
│ │ ├── Images.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── main.m
│ ├── Podfile
│ └── Podfile.lock
├── metro.config.js
├── package.json
├── src
│ ├── App.tsx
│ ├── cities.json
│ ├── randomCity.ts
│ └── randomColor.ts
└── yarn.lock
├── ios
├── BubbleSelect-Bridging-Header.h
├── BubbleSelect.xcodeproj
│ └── project.pbxproj
├── BubbleSelect.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Magnetic
│ ├── Extensions.swift
│ ├── Magnetic.swift
│ ├── MagneticView.swift
│ ├── Node.swift
│ ├── SKAction+Color.swift
│ └── SKMultilineLabelNode.swift
├── MagneticViewExtension.swift
├── RNBubbleMagneticView.swift
├── RNBubbleSelectNode.swift
├── RNBubbleSelectNodeViewManager.m
├── RNBubbleSelectNodeViewManager.swift
├── RNBubbleSelectViewManager.m
└── RNBubbleSelectViewManager.swift
├── package.json
├── react-native-bubble-select.podspec
├── screenshot.png
├── src
├── Bubble.tsx
├── BubbleSelect.tsx
├── __tests__
│ └── index.test.tsx
└── index.ts
├── swift-error.png
├── tsconfig.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | defaults: &defaults
4 | docker:
5 | - image: circleci/node:10
6 | working_directory: ~/project
7 |
8 | jobs:
9 | install-dependencies:
10 | <<: *defaults
11 | steps:
12 | - checkout
13 | - attach_workspace:
14 | at: ~/project
15 | - restore_cache:
16 | keys:
17 | - dependencies-{{ checksum "package.json" }}
18 | - dependencies-
19 | - restore_cache:
20 | keys:
21 | - dependencies-example-{{ checksum "example/package.json" }}
22 | - dependencies-example-
23 | - run: |
24 | yarn install --cwd example --frozen-lockfile
25 | yarn install --frozen-lockfile
26 | - save_cache:
27 | key: dependencies-{{ checksum "package.json" }}
28 | paths: node_modules
29 | - save_cache:
30 | key: dependencies-example-{{ checksum "example/package.json" }}
31 | paths: example/node_modules
32 | - persist_to_workspace:
33 | root: .
34 | paths: .
35 | lint:
36 | <<: *defaults
37 | steps:
38 | - attach_workspace:
39 | at: ~/project
40 | - run: |
41 | yarn lint
42 | typescript:
43 | <<: *defaults
44 | steps:
45 | - attach_workspace:
46 | at: ~/project
47 | - run: yarn typescript
48 | unit-tests:
49 | <<: *defaults
50 | steps:
51 | - attach_workspace:
52 | at: ~/project
53 | - run: yarn test --coverage
54 | - store_artifacts:
55 | path: coverage
56 | destination: coverage
57 | build-package:
58 | <<: *defaults
59 | steps:
60 | - attach_workspace:
61 | at: ~/project
62 | - run: yarn prepare
63 |
64 |
65 | workflows:
66 | version: 2
67 | build-and-test:
68 | jobs:
69 | - install-dependencies
70 | - lint:
71 | requires:
72 | - install-dependencies
73 | - typescript:
74 | requires:
75 | - install-dependencies
76 | - unit-tests:
77 | requires:
78 | - install-dependencies
79 | - build-package:
80 | requires:
81 | - install-dependencies
82 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 2
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | example/ios/Pods
2 | example/node_modules
3 | lib
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | jsconfig.json
11 |
12 | # Xcode
13 | #
14 | build/
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 | *.xccheckout
25 | *.moved-aside
26 | DerivedData
27 | *.hmap
28 | *.ipa
29 | *.xcuserstate
30 | project.xcworkspace
31 |
32 | # Android/IJ
33 | #
34 | .idea
35 | .gradle
36 | local.properties
37 | android.iml
38 |
39 | # Cocoapods
40 | #
41 | example/ios/Pods
42 |
43 | # node.js
44 | #
45 | node_modules/
46 | npm-debug.log
47 | yarn-debug.log
48 | yarn-error.log
49 |
50 | # BUCK
51 | buck-out/
52 | \.buckd/
53 | android/app/libs
54 | android/keystores/debug.keystore
55 |
56 | # generated by bob
57 | lib/
58 |
--------------------------------------------------------------------------------
/Bubble-select-ios.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/Bubble-select-ios.gif
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project.
4 |
5 | ## Development workflow
6 |
7 | To get started with the project, run `yarn bootstrap` in the root directory to install the required dependencies for each package:
8 |
9 | ```sh
10 | yarn bootstrap
11 | ```
12 |
13 | While developing, you can run the [example app](/example/) to test your changes.
14 |
15 | To start the packager:
16 |
17 | ```sh
18 | yarn example start
19 | ```
20 |
21 | To run the example app on Android:
22 |
23 | ```sh
24 | yarn example android
25 | ```
26 |
27 | To run the example app on iOS:
28 |
29 | ```sh
30 | yarn example android
31 | ```
32 |
33 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
34 |
35 | ```sh
36 | yarn typescript
37 | yarn lint
38 | ```
39 |
40 | To fix formatting errors, run the following:
41 |
42 | ```sh
43 | yarn lint --fix
44 | ```
45 |
46 | Remember to add tests for your change if possible. Run the unit tests by:
47 |
48 | ```sh
49 | yarn test
50 | ```
51 |
52 | ### Commit message convention
53 |
54 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
55 |
56 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
57 | - `feat`: new features, e.g. add new method to the module.
58 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
59 | - `docs`: changes into documentation, e.g. add usage example for the module..
60 | - `test`: adding or updating tests, eg add integration tests using detox.
61 | - `chore`: tooling changes, e.g. change CI config.
62 |
63 | Our pre-commit hooks verify that your commit message matches this format when committing.
64 |
65 | ### Linting and tests
66 |
67 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
68 |
69 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
70 |
71 | Our pre-commit hooks verify that the linter and tests pass when committing.
72 |
73 | ### Scripts
74 |
75 | The `package.json` file contains various scripts for common tasks:
76 |
77 | - `yarn bootstrap`: setup project by installing all dependencies and pods.
78 | - `yarn typescript`: type-check files with TypeScript.
79 | - `yarn lint`: lint files with ESLint.
80 | - `yarn test`: run unit tests with Jest.
81 | - `yarn example start`: start the Metro server for the example app.
82 | - `yarn example android`: run the example app on Android.
83 | - `yarn example ios`: run the example app on iOS.
84 |
85 | ### Sending a pull request
86 |
87 | > **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).
88 |
89 | When you're sending a pull request:
90 |
91 | - Prefer small pull requests focused on one change.
92 | - Verify that linters and tests are passing.
93 | - Review the documentation to make sure it looks good.
94 | - Follow the pull request template when opening a pull request.
95 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
96 |
97 | ## Code of Conduct
98 |
99 | ### Our Pledge
100 |
101 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
102 |
103 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
104 |
105 | ### Our Standards
106 |
107 | Examples of behavior that contributes to a positive environment for our community include:
108 |
109 | - Demonstrating empathy and kindness toward other people
110 | - Being respectful of differing opinions, viewpoints, and experiences
111 | - Giving and gracefully accepting constructive feedback
112 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
113 | - Focusing on what is best not just for us as individuals, but for the overall community
114 |
115 | Examples of unacceptable behavior include:
116 |
117 | - The use of sexualized language or imagery, and sexual attention or
118 | advances of any kind
119 | - Trolling, insulting or derogatory comments, and personal or political attacks
120 | - Public or private harassment
121 | - Publishing others' private information, such as a physical or email
122 | address, without their explicit permission
123 | - Other conduct which could reasonably be considered inappropriate in a
124 | professional setting
125 |
126 | ### Enforcement Responsibilities
127 |
128 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
129 |
130 | Community leaders 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, and will communicate reasons for moderation decisions when appropriate.
131 |
132 | ### Scope
133 |
134 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
135 |
136 | ### Enforcement
137 |
138 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
139 |
140 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
141 |
142 | ### Enforcement Guidelines
143 |
144 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
145 |
146 | #### 1. Correction
147 |
148 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
149 |
150 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
151 |
152 | #### 2. Warning
153 |
154 | **Community Impact**: A violation through a single incident or series of actions.
155 |
156 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
157 |
158 | #### 3. Temporary Ban
159 |
160 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
161 |
162 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
163 |
164 | #### 4. Permanent Ban
165 |
166 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
167 |
168 | **Consequence**: A permanent ban from any sort of public interaction within the community.
169 |
170 | ### Attribution
171 |
172 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
173 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
174 |
175 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
176 |
177 | [homepage]: https://www.contributor-covenant.org
178 |
179 | For answers to common questions about this code of conduct, see the FAQ at
180 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
181 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jesse Onolememen
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Bubble Select
2 |
3 | > This project is mostly `stale` at the moment. I haven't had time to maintain it, so the code is outdated. Feel free to open a pull request though and I will try review it once I get the chance to
4 |
5 | An easy-to-use customizable bubble animation picker, similar to the Apple Music genre selection
6 |
7 | 
8 |
9 | ## Features
10 |
11 | - iOS & Android Support (In beta)
12 | - Typescript Support
13 | - Customizable
14 |
15 | ## iOS Example
16 |
17 | 
18 |
19 | Advanced Example
20 |
21 | 
22 |
23 | ## Android Example
24 |
25 | 
26 |
27 | ## Installation
28 |
29 | Install the library using either yarn or npm like so:
30 |
31 | ```sh
32 | yarn add react-native-bubble-select
33 | ```
34 |
35 | ```sh
36 | npm install --save react-native-bubble-select
37 | ```
38 |
39 | ### iOS Installation
40 |
41 | If you're using React Native versions > 60.0, it's relatively straightforward.
42 |
43 | ```sh
44 | cd ios && pod install
45 | ```
46 |
47 | For versions below 0.60.0, use rnpm links
48 |
49 | - Run `react-native link react-native-bubble-select`
50 | - If linking fails, follow the
51 | [manual linking steps](https://facebook.github.io/react-native/docs/linking-libraries-ios.html#manual-linking)
52 |
53 | #### Additional Steps
54 |
55 | This library was written in Swift, so in-order for you app to compile, you need to have at least on .swift file in your source code a bridging header to avoid a runtime error like so:
56 |
57 | 
58 |
59 | All you have to do is:
60 |
61 | - File > New > File
62 | - Swift File
63 | - Name the file whatever you wish
64 | - When prompted to create a bridging header, do so
65 |
66 | You must also include `use_frameworks!` at the top of your `Podfile`
67 |
68 | ### Android Installation
69 |
70 | > **Note** as of version 0.5.0, android support is experimental.
71 |
72 | For versions below 0.60.0, follow the linking instructions above.
73 |
74 | ## Usage
75 |
76 | You can view the [example project](./example/src/App.tsx) for more usage.
77 |
78 | ### Simple Usage
79 |
80 | ```js
81 | import React from 'react';
82 | import BubbleSelect, { Bubble } from 'react-native-bubble-select';
83 | import { Dimensions } from 'react-native';
84 |
85 | const { width, height } = Dimensions.get('window');
86 |
87 | const App = () => {
88 | return (
89 | console.log('Selected: ', bubble.id)}
91 | onDeselect={bubble => console.log('Deselected: ', bubble.id)}
92 | width={width}
93 | height={height}
94 | >
95 |
96 |
97 |
98 |
99 |
100 | );
101 | };
102 | ```
103 |
104 | ### Advanced Usage
105 |
106 | ```tsx
107 | import React from 'react';
108 | import { Platform, Dimensions } from 'react-native';
109 | import BubbleSelect, { Bubble, BubbleNode } from 'react-native-bubble-select';
110 | import randomCities from './randomCities';
111 |
112 | const { width, height } = Dimensions.get('window');
113 |
114 | const App = () => {
115 | const [cities, setCities] = React.useState(randomCities());
116 | const [selectedCites, setSelectedCities] = React.useState([]);
117 | const [removedCities, setRemovedCities] = React.useState([]);
118 |
119 | const addCity = () => {
120 | setCities([...cities, randomCity()]);
121 | };
122 |
123 | const handleSelect = (bubble: BubbleNode) => {
124 | setSelectedCities([...selectedCites, bubble]);
125 | };
126 |
127 | const handleDeselect = (bubble: BubbleNode) => {
128 | setSelectedCities(selectedCites.filter(({ id }) => id !== bubble.id));
129 | };
130 |
131 | const handleRemove = (bubble: BubbleNode) => {
132 | setRemovedCities([...removedCities, bubble]);
133 | };
134 |
135 | return (
136 |
147 | {cities.map(city => (
148 |
156 | ))}
157 |
158 | );
159 | };
160 | ```
161 |
162 | ## Props
163 |
164 | ### Common Props
165 |
166 | | property | type | required | description | default |
167 | | --------------- | ------ | -------- | ---------------------------------------------------------------------- | ------------ |
168 | | id | string | TRUE | A custom identifier used for identifying the node | - |
169 | | text | string | TRUE | The text of the bubble. **Note: on android the text must be unique** | - |
170 | | color | string | FALSE | The background color of the bubble | black |
171 | | radius | number | FALSE | The radius of the bubble. This value is ignored if autoSize is enabled | 30 |
172 | | fontName | string | FALSE | The name of the custom font applied to the bubble | Avenir-Black |
173 | | fontSize | number | FALSE | The size of the custom font applied to the bubble | 13 |
174 | | fontColor | string | FALSE | The color of the bubble text | white |
175 | | backgroundColor | string | FALSE | The background color of the picker | white |
176 |
177 | ### iOS Only Props
178 |
179 | | property | type | required | description | default |
180 | | ----------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
181 | | id | string | TRUE | A custom identifier used for identifying the node | - |
182 | | text | string | TRUE | The text of the bubble. **Note: on android the text must be unique** | - |
183 | | color | string | FALSE | The background color of the bubble | black |
184 | | radius | number | FALSE | The radius of the bubble. This value is ignored if autoSize is enabled | 30 |
185 | | marginScale | number | FALSE | The margin scale applied to the physics body of the bubble. **recommend that you do not change this value unless you know what you are doing** | 1.01 |
186 | | fontName | string | FALSE | The name of the custom font applied to the bubble | Avenir-Black |
187 | | fontSize | number | FALSE | The size of the custom font applied to the bubble | 13 |
188 | | fontColor | string | FALSE | The color of the bubble text | white |
189 | | lineHeight | number | FALSE | The line height of the bubble. This value is ignored if autoSize is enabled | 1.5 |
190 | | borderColor | string | FALSE | The border color of the buble | - |
191 | | borderWidth | number | FALSE | The border width of the bubble | - |
192 | | padding | number | FALSE | Extra padding applied to the bubble contents, if autoSize is enabled | 20 |
193 | | selectedScale | number | FALSE | The scale of the selected bubble | 1.33 |
194 | | deselectedScale | number | FALSE | The scale of the deselected bubble | 1 |
195 | | animationDuration | number | FALSE | The duration of the scale animation | 0.2 |
196 | | selectedColor | string | FALSE | The background color of the selected bubble | - |
197 | | selectedFontColor | string | FALSE | The color of the selected bubble text | - |
198 | | autoSize | boolean | FALSE | Whether or not the bubble should resize to fit its content | TRUE |
199 | | initialSelection | string[] | FALSE | An id array of the initially selected nodes | - |
200 |
201 | ### Android Only Props
202 |
203 | | property | type | required | description | default |
204 | | ----------------- | -------- | -------- | ---------------------------------------------- | ------- |
205 | | bubbleSize | number | FALSE | The size of all the bubbles | - |
206 | | gradient | Gradient | FALSE | A custom gradient to be applied to the bubbles | - |
207 | | maxSelectionCount | number | FALSE | The max number of selected bubbles | - |
208 |
209 | #### Gradient
210 |
211 | | property | type | required | description | default |
212 | | ---------- | ------------------------------ | -------- | ---------------------------------------------- | ------- |
213 | | startColor | string | TRUE | The size of all the bubbles | - |
214 | | endColor | string | TRUE | A custom gradient to be applied to the bubbles | - |
215 | | direction | enum('vertical', 'horizontal') | TRUE | The direction of the gradient | - |
216 |
217 | > **Note** all required fields must be provided else the application will crash.
218 |
219 | ## Acknowledgments
220 |
221 | - The iOS version is based off of [Magnetic](https://github.com/efremidze/Magnetic)
222 | - The Android version is based off of [Bubble-Picker](https://github.com/igalata/Bubble-Picker)
223 |
224 | ## Known Issues
225 |
226 | ### iOS
227 |
228 | - on certain occasions only half of the bubbles are shown on the screen #2
229 |
230 | ### Android
231 |
232 | - the title of each bubble must be unique else the wrong element may be returned
233 | - hot reloading does not work #3
234 | - selection handlers are not triggered
235 | - after 5 items are selected, the picker rests, likewise with removed.
236 |
237 | ## Roadmap
238 |
239 | ### iOS
240 |
241 | - [ ] enable support for images
242 |
243 | ### Android
244 |
245 | - [ ] enable long press to remove
246 | - [ ] auto size bubble based on content
247 | - [ ] enable support for images
248 |
249 | ### General
250 |
251 | - [ ] Improve documentation
252 | - [ ] Unit tests
253 | - [ ] Flow support
254 |
255 | ## License
256 |
257 | MIT
258 |
--------------------------------------------------------------------------------
/advanced-example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/advanced-example.gif
--------------------------------------------------------------------------------
/android-example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/android-example.gif
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android_
4 | Project android_ created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0))
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3 | def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['BubbleSelect_kotlinVersion']
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 |
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.2.1'
12 | // noinspection DifferentKotlinGradleVersion
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | apply plugin: 'com.android.library'
18 | apply plugin: 'kotlin-android'
19 |
20 | def getExtOrDefault(name) {
21 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['BubbleSelect_' + name]
22 | }
23 |
24 | def getExtOrIntegerDefault(name) {
25 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['BubbleSelect_' + name]).toInteger()
26 | }
27 |
28 | android {
29 | compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
30 | buildToolsVersion getExtOrDefault('buildToolsVersion')
31 | defaultConfig {
32 | minSdkVersion 23
33 | targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
34 | versionCode 1
35 | versionName "1.0"
36 | }
37 | buildTypes {
38 | release {
39 | minifyEnabled false
40 | }
41 | }
42 | lintOptions {
43 | disable 'GradleCompatible'
44 | }
45 | compileOptions {
46 | sourceCompatibility JavaVersion.VERSION_1_8
47 | targetCompatibility JavaVersion.VERSION_1_8
48 | }
49 | }
50 |
51 | repositories {
52 | mavenCentral()
53 | jcenter()
54 | google()
55 | maven { url "https://jitpack.io" }
56 |
57 | def found = false
58 | def defaultDir = null
59 | def androidSourcesName = 'React Native sources'
60 |
61 | if (rootProject.ext.has('reactNativeAndroidRoot')) {
62 | defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
63 | } else {
64 | defaultDir = new File(
65 | projectDir,
66 | '/../../../node_modules/react-native/android'
67 | )
68 | }
69 |
70 | if (defaultDir.exists()) {
71 | maven {
72 | url defaultDir.toString()
73 | name androidSourcesName
74 | }
75 |
76 | logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
77 | found = true
78 | } else {
79 | def parentDir = rootProject.projectDir
80 |
81 | 1.upto(5, {
82 | if (found) return true
83 | parentDir = parentDir.parentFile
84 |
85 | def androidSourcesDir = new File(
86 | parentDir,
87 | 'node_modules/react-native'
88 | )
89 |
90 | def androidPrebuiltBinaryDir = new File(
91 | parentDir,
92 | 'node_modules/react-native/android'
93 | )
94 |
95 | if (androidPrebuiltBinaryDir.exists()) {
96 | maven {
97 | url androidPrebuiltBinaryDir.toString()
98 | name androidSourcesName
99 | }
100 |
101 | logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
102 | found = true
103 | } else if (androidSourcesDir.exists()) {
104 | maven {
105 | url androidSourcesDir.toString()
106 | name androidSourcesName
107 | }
108 |
109 | logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
110 | found = true
111 | }
112 | })
113 | }
114 |
115 | if (!found) {
116 | throw new GradleException(
117 | "${project.name}: unable to locate React Native android sources. " +
118 | "Ensure you have you installed React Native as a dependency in your project and try again."
119 | )
120 | }
121 | }
122 |
123 | def kotlin_version = getExtOrDefault('kotlinVersion')
124 |
125 | dependencies {
126 | // noinspection GradleDynamicVersion
127 | api 'com.facebook.react:react-native:+'
128 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
129 | implementation 'com.github.jesster2k10:Bubble-Picker:v0.3-beta'
130 | }
131 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | BubbleSelect_kotlinVersion=1.3.50
2 | BubbleSelect_compileSdkVersion=28
3 | BubbleSelect_buildToolsVersion=28.0.3
4 | BubbleSelect_targetSdkVersion=28
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 29 17:44:13 IST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleDeselectNodeEvent.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import com.facebook.react.bridge.Arguments
4 | import com.facebook.react.uimanager.events.Event
5 | import com.facebook.react.uimanager.events.RCTEventEmitter
6 |
7 | class BubbleDeselectNodeEvent(viewId: Int): Event(viewId) {
8 | companion object {
9 | const val EVENT_NAME = "onDeselectNode"
10 | }
11 |
12 | lateinit var node: BubbleSelectNodeView
13 |
14 | override fun getEventName(): String {
15 | return EVENT_NAME
16 | }
17 |
18 | override fun getCoalescingKey(): Short {
19 | return 0
20 | }
21 |
22 | override fun canCoalesce(): Boolean {
23 | return false
24 | }
25 |
26 | override fun dispatch(rctEventEmitter: RCTEventEmitter?) {
27 | val eventData = Arguments.createMap()
28 | eventData.putString("text", node.text)
29 | eventData.putString("id", node.id)
30 | eventData.putInt("target", viewTag)
31 |
32 | rctEventEmitter?.receiveEvent(viewTag, eventName, eventData)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleRemoveNodeEvent.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import com.facebook.react.bridge.Arguments
4 | import com.facebook.react.uimanager.events.Event
5 | import com.facebook.react.uimanager.events.RCTEventEmitter
6 |
7 | class BubbleRemoveNodeEvent(viewId: Int): Event(viewId) {
8 | companion object {
9 | var EVENT_NAME = "onRemoveNode"
10 | }
11 |
12 | lateinit var item: BubbleSelectNodeView
13 |
14 | override fun getEventName(): String {
15 | return EVENT_NAME;
16 | }
17 |
18 | override fun getCoalescingKey(): Short {
19 | return 0
20 | }
21 |
22 | override fun canCoalesce(): Boolean {
23 | return false
24 | }
25 |
26 | override fun dispatch(rctEventEmitter: RCTEventEmitter?) {
27 | val eventData = Arguments.createMap()
28 | eventData.putString("id", item.id)
29 | eventData.putString("text", item.text)
30 | eventData.putInt("target", viewTag)
31 |
32 | rctEventEmitter?.receiveEvent(viewTag, eventName, eventData)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleSelectNodeEvent.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import com.facebook.react.bridge.Arguments
4 | import com.facebook.react.uimanager.events.Event
5 | import com.facebook.react.uimanager.events.RCTEventEmitter
6 |
7 | class BubbleSelectNodeEvent(viewId: Int): Event(viewId) {
8 | companion object {
9 | const val EVENT_NAME = "onSelectNode"
10 | }
11 |
12 | lateinit var node: BubbleSelectNodeView
13 |
14 | override fun getEventName(): String {
15 | return EVENT_NAME
16 | }
17 |
18 | override fun getCoalescingKey(): Short {
19 | return 0
20 | }
21 |
22 | override fun canCoalesce(): Boolean {
23 | return false
24 | }
25 |
26 | override fun dispatch(rctEventEmitter: RCTEventEmitter?) {
27 | val eventData = Arguments.createMap()
28 | eventData.putString("text", node.text)
29 | eventData.putString("id", node.id)
30 | eventData.putInt("target", viewTag)
31 |
32 | rctEventEmitter?.receiveEvent(viewTag, eventName, eventData)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleSelectNodeView.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import android.graphics.Color
4 | import android.graphics.Typeface
5 | import android.widget.LinearLayout
6 | import com.facebook.react.bridge.ReactContext
7 | import com.facebook.react.bridge.ReadableMap
8 | import com.igalata.bubblepicker.model.BubbleGradient
9 |
10 | class BubbleSelectNodeView(context: ReactContext): LinearLayout(context) {
11 | lateinit var id: String
12 | lateinit var text: String
13 | var fontFamily: String? = null
14 | var fontStyle: Int = Typeface.NORMAL
15 | var fontSize: Float = 14f
16 | var fontColor: String = "#ffffff"
17 | var color: String? = null
18 | var gradient: ReadableMap? = null
19 |
20 | init {
21 | inflate(context, R.layout.bubble_node, this)
22 | }
23 |
24 | fun setFontStyle(style: String?) {
25 | fontStyle = when (style) {
26 | "bold-italic" -> Typeface.BOLD_ITALIC
27 | "italic" -> Typeface.ITALIC
28 | "bold" -> Typeface.BOLD
29 | else -> Typeface.NORMAL
30 | }
31 | }
32 |
33 | fun getGradient(): BubbleGradient? {
34 | val mGradient = this.gradient;
35 | if (mGradient === null) {
36 | return null
37 | }
38 |
39 | val startColor = mGradient.getString("startColor");
40 | val endColor = mGradient.getString("endColor");
41 | val direction = when (mGradient.getString("direction")) {
42 | "horizontal" -> BubbleGradient.HORIZONTAL
43 | else -> BubbleGradient.VERTICAL
44 | }
45 |
46 | if (startColor === null || endColor === null) {
47 | return null
48 | }
49 |
50 | return BubbleGradient(
51 | Color.parseColor(startColor),
52 | Color.parseColor(endColor),
53 | direction
54 | )
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleSelectNodeViewManager.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import com.facebook.react.bridge.ReadableMap
4 | import com.facebook.react.uimanager.SimpleViewManager
5 | import com.facebook.react.uimanager.ThemedReactContext
6 | import com.facebook.react.uimanager.annotations.ReactProp
7 |
8 | class BubbleSelectNodeViewManager: SimpleViewManager() {
9 | override fun getName(): String {
10 | return "RNBubbleSelectNodeView"
11 | }
12 |
13 | override fun createViewInstance(reactContext: ThemedReactContext): BubbleSelectNodeView {
14 | return BubbleSelectNodeView(reactContext)
15 | }
16 |
17 | @ReactProp(name = "text")
18 | fun setText(view: BubbleSelectNodeView, text: String?) {
19 | if (text == null) return
20 | view.text = text
21 | }
22 |
23 | @ReactProp(name = "id")
24 | fun setId(view: BubbleSelectNodeView, id: String?) {
25 | if (id == null) return
26 | view.id = id
27 | }
28 |
29 | @ReactProp(name = "fontFamily")
30 | fun setFontFamily(view: BubbleSelectNodeView, fontFamily: String?) {
31 | view.fontFamily = fontFamily
32 | }
33 |
34 | @ReactProp(name = "fontStyle")
35 | fun setFontStyle(view: BubbleSelectNodeView, fontStyle: String?) {
36 | view.run { setFontStyle(fontStyle) }
37 | }
38 |
39 | @ReactProp(name = "fontColor")
40 | fun setFontColor(view: BubbleSelectNodeView, fontColor: String?) {
41 | if (fontColor == null) return;
42 | view.fontColor = fontColor
43 | }
44 |
45 | @ReactProp(name = "color")
46 | fun setColor(view: BubbleSelectNodeView, color: String?) {
47 | view.color = color
48 | }
49 |
50 | @ReactProp(name = "gradient")
51 | fun setGradient(view: BubbleSelectNodeView, gradient: ReadableMap?) {
52 | view.gradient = gradient
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleSelectPackage.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import java.util.Arrays
4 | import java.util.Collections
5 |
6 | import com.facebook.react.ReactPackage
7 | import com.facebook.react.bridge.NativeModule
8 | import com.facebook.react.bridge.ReactApplicationContext
9 | import com.facebook.react.uimanager.ViewManager
10 | import com.facebook.react.bridge.JavaScriptModule
11 |
12 | class BubbleSelectPackage : ReactPackage {
13 | override fun createNativeModules(reactContext: ReactApplicationContext): List {
14 | return emptyList()
15 | }
16 |
17 | override fun createViewManagers(reactContext: ReactApplicationContext): List> {
18 | return listOf(
19 | BubbleSelectViewManager(),
20 | BubbleSelectNodeViewManager()
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleSelectView.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import android.graphics.Color
4 | import android.graphics.Typeface
5 | import android.widget.FrameLayout
6 | import com.facebook.react.bridge.LifecycleEventListener
7 | import com.facebook.react.bridge.ReactContext
8 | import com.facebook.react.uimanager.UIManagerModule
9 | import com.facebook.react.uimanager.events.RCTEventEmitter
10 | import com.igalata.bubblepicker.adapter.BubblePickerAdapter
11 | import com.igalata.bubblepicker.model.BubbleGradient
12 | import com.igalata.bubblepicker.model.PickerItem
13 | import com.igalata.bubblepicker.rendering.BubblePicker
14 | import com.igalata.bubblepicker.BubblePickerListener
15 |
16 | class BubbleSelectView(context: ReactContext): FrameLayout(context), LifecycleEventListener, BubblePickerListener {
17 | val bubblePicker: BubblePicker
18 | val nodes: ArrayList = ArrayList()
19 |
20 | init {
21 | inflate(context, R.layout.bubble_view, this)
22 | bubblePicker = findViewById(R.id.bubble_picker);
23 | bubblePicker.listener = this;
24 | bubblePicker.maxSelectedCount = 10000
25 | context.addLifecycleEventListener(this);
26 | setupBubblePickerAdapter()
27 | }
28 |
29 | private fun setupBubblePickerAdapter() {
30 | bubblePicker.adapter = object : BubblePickerAdapter {
31 | override val totalCount: Int = nodes.size
32 |
33 | override fun getItem(position: Int): PickerItem {
34 | return PickerItem().apply {
35 | val node = nodes[position]
36 | title = node.text
37 | id = node.id
38 | if (node.fontFamily !== null) {
39 | typeface = Typeface.create(node.fontFamily, node.fontStyle)
40 | }
41 | textColor = Color.parseColor(node.fontColor)
42 |
43 | if (node.gradient !== null) {
44 | gradient = node.getGradient();
45 | } else if (node.color !== null) {
46 | gradient = BubbleGradient(
47 | Color.parseColor(node.color),
48 | Color.parseColor(node.color),
49 | BubbleGradient.VERTICAL
50 | )
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
57 | fun addNode(node: BubbleSelectNodeView) {
58 | nodes.add(node)
59 | bubblePicker.addedItem(nodes.size - 1)
60 | }
61 |
62 | // fun removeNode(node: BubbleSelectNodeView) {
63 | // nodes.remove(node)
64 | // setupBubblePickerAdapter()
65 | // }
66 |
67 | override fun onHostPause() {
68 | bubblePicker.onPause()
69 | }
70 |
71 | override fun onHostResume() {
72 | bubblePicker.onResume()
73 | }
74 |
75 | override fun onHostDestroy() {}
76 |
77 | private fun findNode(item: PickerItem): BubbleSelectNodeView? {
78 | return nodes.find {
79 | it.id == item.id
80 | }
81 | }
82 |
83 | override fun onBubbleDeselected(item: PickerItem) {
84 | val node = findNode(item) ?: return
85 | val event = BubbleDeselectNodeEvent(id)
86 | event.node = node
87 |
88 | val reactContext = context as ReactContext
89 | reactContext.getNativeModule(UIManagerModule::class.java)?.eventDispatcher?.dispatchEvent(event)
90 | }
91 |
92 | override fun onBubbleSelected(item: PickerItem) {
93 | val node = findNode(item) ?: return
94 | val event = BubbleSelectNodeEvent(id)
95 | event.node = node
96 |
97 | val reactContext = context as ReactContext
98 | reactContext.getNativeModule(UIManagerModule::class.java)?.eventDispatcher?.dispatchEvent(event)
99 | }
100 |
101 | override fun onBubbleRemoved(item: PickerItem) {
102 | val node = findNode(item) ?: return
103 | val event = BubbleRemoveNodeEvent(id)
104 | event.item = node
105 |
106 | val reactContext = context as ReactContext
107 | reactContext.getNativeModule(UIManagerModule::class.java)?.eventDispatcher?.dispatchEvent(event)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactnativebubbleselect/BubbleSelectViewManager.kt:
--------------------------------------------------------------------------------
1 | package com.reactnativebubbleselect
2 |
3 | import android.graphics.Color
4 | import android.graphics.drawable.ColorDrawable
5 | import android.view.View
6 | import com.facebook.react.common.MapBuilder
7 | import com.facebook.react.uimanager.ThemedReactContext
8 | import com.facebook.react.uimanager.ViewGroupManager
9 | import com.facebook.react.uimanager.annotations.ReactProp
10 |
11 | class BubbleSelectViewManager: ViewGroupManager() {
12 | override fun getName(): String {
13 | return "RNBubbleSelectView"
14 | }
15 |
16 | override fun createViewInstance(reactContext: ThemedReactContext): BubbleSelectView {
17 | return BubbleSelectView(reactContext)
18 | }
19 |
20 | @ReactProp(name = "maxSelectedItems")
21 | fun setMaxSelectedItems(view: BubbleSelectView, max: Int?) {
22 | if (max == null) return;
23 | view.bubblePicker.maxSelectedCount = max
24 | }
25 |
26 | @ReactProp(name = "bubbleSize")
27 | fun setBubbleSize(view: BubbleSelectView, size: Int?) {
28 | if (size == null) return
29 | view.bubblePicker.bubbleSize = size
30 | }
31 |
32 | @ReactProp(name = "backgroundColor")
33 | fun setBackgroundColor(view: BubbleSelectView, color: String?) {
34 | if (color == null) return
35 | view.background = ColorDrawable(Color.parseColor(color))
36 | }
37 |
38 | override fun addView(parent: BubbleSelectView?, child: View?, index: Int) {
39 | if (child is BubbleSelectNodeView && parent !== null) {
40 | parent.addNode(child)
41 | }
42 | }
43 |
44 | override fun removeView(parent: BubbleSelectView?, child: View?) {
45 | if (child is BubbleSelectNodeView && parent !== null) {
46 | // parent.removeNode(child)
47 | }
48 | }
49 |
50 | override fun getExportedCustomDirectEventTypeConstants(): MutableMap {
51 | return MapBuilder.builder()
52 | .put(BubbleSelectNodeEvent.EVENT_NAME, MapBuilder.of(
53 | "registrationName", "onSelectNode"
54 | ))
55 | .put(BubbleDeselectNodeEvent.EVENT_NAME, MapBuilder.of(
56 | "registrationName", "onDeselectNode"
57 | ))
58 | .put(BubbleRemoveNodeEvent.EVENT_NAME, MapBuilder.of(
59 | "registrationName", "onRemoveNode"
60 | ))
61 | .build()
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/bubble_node.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/bubble_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:metro-react-native-babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/bubble-min.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/bubble-min.mp4
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
4 |
--------------------------------------------------------------------------------
/example/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android
4 | Project android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/example/android/BubbleSelectExample.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | generateDebugSources
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 |
5 | /**
6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7 | * and bundleReleaseJsAndAssets).
8 | * These basically call `react-native bundle` with the correct arguments during the Android build
9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10 | * bundle directly from the development server. Below you can see all the possible configurations
11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
12 | * `apply from: "../../node_modules/react-native/react.gradle"` line.
13 | *
14 | * project.ext.react = [
15 | * // the name of the generated asset file containing your JS bundle
16 | * bundleAssetName: "index.android.bundle",
17 | *
18 | * // the entry file for bundle generation
19 | * entryFile: "index.android.js",
20 | *
21 | * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format
22 | * bundleCommand: "ram-bundle",
23 | *
24 | * // whether to bundle JS and assets in debug mode
25 | * bundleInDebug: false,
26 | *
27 | * // whether to bundle JS and assets in release mode
28 | * bundleInRelease: true,
29 | *
30 | * // whether to bundle JS and assets in another build variant (if configured).
31 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
32 | * // The configuration property can be in the following formats
33 | * // 'bundleIn${productFlavor}${buildType}'
34 | * // 'bundleIn${buildType}'
35 | * // bundleInFreeDebug: true,
36 | * // bundleInPaidRelease: true,
37 | * // bundleInBeta: true,
38 | *
39 | * // whether to disable dev mode in custom build variants (by default only disabled in release)
40 | * // for BubbleSelectExample: to disable dev mode in the staging build type (if configured)
41 | * devDisabledInStaging: true,
42 | * // The configuration property can be in the following formats
43 | * // 'devDisabledIn${productFlavor}${buildType}'
44 | * // 'devDisabledIn${buildType}'
45 | *
46 | * // the root of your project, i.e. where "package.json" lives
47 | * root: "../../",
48 | *
49 | * // where to put the JS bundle asset in debug mode
50 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
51 | *
52 | * // where to put the JS bundle asset in release mode
53 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
54 | *
55 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
56 | * // require('./image.png')), in debug mode
57 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
58 | *
59 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
60 | * // require('./image.png')), in release mode
61 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
62 | *
63 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
64 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
65 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
66 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
67 | * // for BubbleSelectExample, you might want to remove it from here.
68 | * inputExcludes: ["android/**", "ios/**"],
69 | *
70 | * // override which node gets called and with what additional arguments
71 | * nodeExecutableAndArgs: ["node"],
72 | *
73 | * // supply additional arguments to the packager
74 | * extraPackagerArgs: []
75 | * ]
76 | */
77 |
78 | project.ext.react = [
79 | entryFile: "index.js",
80 | enableHermes: false, // clean and rebuild if changing
81 | ]
82 |
83 | apply from: "../../node_modules/react-native/react.gradle"
84 |
85 | /**
86 | * Set this to true to create two separate APKs instead of one:
87 | * - An APK that only works on ARM devices
88 | * - An APK that only works on x86 devices
89 | * The advantage is the size of the APK is reduced by about 4MB.
90 | * Upload all the APKs to the Play Store and people will download
91 | * the correct one based on the CPU architecture of their device.
92 | */
93 | def enableSeparateBuildPerCPUArchitecture = false
94 |
95 | /**
96 | * Run Proguard to shrink the Java bytecode in release builds.
97 | */
98 | def enableProguardInReleaseBuilds = false
99 |
100 | /**
101 | * The preferred build flavor of JavaScriptCore.
102 | *
103 | * For BubbleSelectExample, to use the international variant, you can use:
104 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
105 | *
106 | * The international variant includes ICU i18n library and necessary data
107 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
108 | * give correct results when using with locales other than en-US. Note that
109 | * this variant is about 6MiB larger per architecture than default.
110 | */
111 | def jscFlavor = 'org.webkit:android-jsc:+'
112 |
113 | /**
114 | * Whether to enable the Hermes VM.
115 | *
116 | * This should be set on project.ext.react and mirrored here. If it is not set
117 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
118 | * and the benefits of using Hermes will therefore be sharply reduced.
119 | */
120 | def enableHermes = project.ext.react.get("enableHermes", false);
121 |
122 | android {
123 | compileSdkVersion rootProject.ext.compileSdkVersion
124 |
125 | compileOptions {
126 | sourceCompatibility JavaVersion.VERSION_1_8
127 | targetCompatibility JavaVersion.VERSION_1_8
128 | }
129 |
130 | defaultConfig {
131 | applicationId "com.BubbleSelectExample"
132 | minSdkVersion rootProject.ext.minSdkVersion
133 | targetSdkVersion rootProject.ext.targetSdkVersion
134 | versionCode 1
135 | versionName "1.0"
136 | }
137 | splits {
138 | abi {
139 | reset()
140 | enable enableSeparateBuildPerCPUArchitecture
141 | universalApk false // If true, also generate a universal APK
142 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
143 | }
144 | }
145 | signingConfigs {
146 | debug {
147 | storeFile file('debug.keystore')
148 | storePassword 'android'
149 | keyAlias 'androiddebugkey'
150 | keyPassword 'android'
151 | }
152 | }
153 | buildTypes {
154 | debug {
155 | signingConfig signingConfigs.debug
156 | }
157 | release {
158 | // Caution! In production, you need to generate your own keystore file.
159 | // see https://facebook.github.io/react-native/docs/signed-apk-android.
160 | signingConfig signingConfigs.debug
161 | minifyEnabled enableProguardInReleaseBuilds
162 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
163 | }
164 | }
165 | // applicationVariants are e.g. debug, release
166 | applicationVariants.all { variant ->
167 | variant.outputs.each { output ->
168 | // For each separate APK per architecture, set a unique version code as described here:
169 | // https://developer.android.com/studio/build/configure-apk-splits.html
170 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
171 | def abi = output.getFilter(OutputFile.ABI)
172 | if (abi != null) { // null for the universal-debug, universal-release variants
173 | output.versionCodeOverride =
174 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
175 | }
176 |
177 | }
178 | }
179 | }
180 |
181 | dependencies {
182 | implementation fileTree(dir: "libs", include: ["*.jar"])
183 | implementation "com.facebook.react:react-native:+" // From node_modules
184 |
185 | if (enableHermes) {
186 | def hermesPath = "../../node_modules/hermes-engine/android/";
187 | debugImplementation files(hermesPath + "hermes-debug.aar")
188 | releaseImplementation files(hermesPath + "hermes-release.aar")
189 | } else {
190 | implementation jscFlavor
191 | }
192 |
193 | compile project(':reactnativebubbleselect')
194 | }
195 |
196 | // Run this once to be able to run the application with BUCK
197 | // puts all compile dependencies into folder libs for BUCK to use
198 | task copyDownloadableDepsToLibs(type: Copy) {
199 | from configurations.compile
200 | into 'libs'
201 | }
202 |
203 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
204 |
--------------------------------------------------------------------------------
/example/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/debug.keystore
--------------------------------------------------------------------------------
/example/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/BubbleSelectExample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.BubbleSelectExample;
2 |
3 | import com.facebook.react.ReactActivity;
4 |
5 | public class MainActivity extends ReactActivity {
6 |
7 | /**
8 | * Returns the name of the main component registered from JavaScript. This is used to schedule
9 | * rendering of the component.
10 | */
11 | @Override
12 | protected String getMainComponentName() {
13 | return "BubbleSelectExample";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/BubbleSelectExample/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.BubbleSelectExample;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import com.facebook.react.PackageList;
6 | import com.facebook.react.ReactApplication;
7 | import com.facebook.react.ReactNativeHost;
8 | import com.facebook.react.ReactPackage;
9 | import com.facebook.soloader.SoLoader;
10 | import java.lang.reflect.InvocationTargetException;
11 | import java.util.List;
12 |
13 | import com.reactnativebubbleselect.BubbleSelectPackage;
14 |
15 | public class MainApplication extends Application implements ReactApplication {
16 |
17 | private final ReactNativeHost mReactNativeHost =
18 | new ReactNativeHost(this) {
19 | @Override
20 | public boolean getUseDeveloperSupport() {
21 | return BuildConfig.DEBUG;
22 | }
23 |
24 | @Override
25 | protected List getPackages() {
26 | @SuppressWarnings("UnnecessaryLocalVariable")
27 | List packages = new PackageList(this).getPackages();
28 | // Packages that cannot be autolinked yet can be added manually here, for BubbleSelectExample:
29 | // packages.add(new MyReactNativePackage());
30 | packages.add(new BubbleSelectPackage());
31 |
32 | return packages;
33 | }
34 |
35 | @Override
36 | protected String getJSMainModuleName() {
37 | return "index";
38 | }
39 | };
40 |
41 | @Override
42 | public ReactNativeHost getReactNativeHost() {
43 | return mReactNativeHost;
44 | }
45 |
46 | @Override
47 | public void onCreate() {
48 | super.onCreate();
49 | SoLoader.init(this, /* native exopackage */ false);
50 | initializeFlipper(this); // Remove this line if you don't want Flipper enabled
51 | }
52 |
53 | /**
54 | * Loads Flipper in React Native templates.
55 | *
56 | * @param context
57 | */
58 | private static void initializeFlipper(Context context) {
59 | if (BuildConfig.DEBUG) {
60 | try {
61 | /*
62 | We use reflection here to pick up the class that initializes Flipper,
63 | since Flipper library is not available in release mode
64 | */
65 | Class> aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper");
66 | aClass.getMethod("initializeFlipper", Context.class).invoke(null, context);
67 | } catch (ClassNotFoundException e) {
68 | e.printStackTrace();
69 | } catch (NoSuchMethodException e) {
70 | e.printStackTrace();
71 | } catch (IllegalAccessException e) {
72 | e.printStackTrace();
73 | } catch (InvocationTargetException e) {
74 | e.printStackTrace();
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BubbleSelect Example
3 |
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | buildToolsVersion = "28.0.3"
6 | minSdkVersion = 23
7 | compileSdkVersion = 28
8 | targetSdkVersion = 28
9 | }
10 | repositories {
11 | google()
12 | jcenter()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle:3.4.2")
16 |
17 | // NOTE: Do not place your application dependencies here; they belong
18 | // in the individual module build.gradle files
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | mavenLocal()
25 | maven {
26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
27 | url("$rootDir/../node_modules/react-native/android")
28 | }
29 | maven {
30 | // Android JSC is installed from npm
31 | url("$rootDir/../node_modules/jsc-android/dist")
32 | }
33 |
34 | google()
35 | jcenter()
36 | maven { url 'https://jitpack.io' }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.useAndroidX=true
21 | android.enableJetifier=true
22 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/example/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/example/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin, switch paths to Windows format before running java
129 | if $cygwin ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem http://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'BubbleSelectExample'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 |
5 | include ':reactnativebubbleselect'
6 | project(':reactnativebubbleselect').projectDir = new File(rootProject.projectDir, '../../android')
7 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BubbleSelectExample",
3 | "displayName": "BubbleSelect Example"
4 | }
5 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import { AppRegistry } from 'react-native';
2 | import App from './src/App';
3 | import { name as appName } from './app.json';
4 |
5 | AppRegistry.registerComponent(appName, () => App);
6 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample.xcodeproj/xcshareddata/xcschemes/BubbleSelectExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
81 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (nonatomic, strong) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import "AppDelegate.h"
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | @implementation AppDelegate
15 |
16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
17 | {
18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
20 | moduleName:@"BubbleSelectExample"
21 | initialProperties:nil];
22 |
23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
24 |
25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
26 | UIViewController *rootViewController = [UIViewController new];
27 | rootViewController.view = rootView;
28 | self.window.rootViewController = rootViewController;
29 | [self.window makeKeyAndVisible];
30 | return YES;
31 | }
32 |
33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
34 | {
35 | #if DEBUG
36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
37 | #else
38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
39 | #endif
40 | }
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | BubbleSelect Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 | NSExceptionDomains
32 |
33 | localhost
34 |
35 | NSExceptionAllowsInsecureHTTPLoads
36 |
37 |
38 |
39 |
40 | NSLocationWhenInUseUsageDescription
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UIViewControllerBasedStatusBarAppearance
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/example/ios/BubbleSelectExample/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3 | use_frameworks!
4 |
5 | target 'BubbleSelectExample' do
6 | # Pods for BubbleSelectExample
7 | pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
8 | pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
9 | pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
10 | pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
11 | pod 'React', :path => '../node_modules/react-native/'
12 | pod 'React-Core', :path => '../node_modules/react-native/'
13 | pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
14 | pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
15 | pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
16 | pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
17 | pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
18 | pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
19 | pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
20 | pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
21 | pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
22 | pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
23 | pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
24 | pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
25 |
26 | pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
27 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
28 | pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
29 | pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
30 | pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
31 | pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
32 | pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
33 |
34 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
35 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
36 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
37 |
38 | pod 'react-native-bubble-select', :path => '../..'
39 |
40 | use_native_modules!
41 | end
42 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - boost-for-react-native (1.63.0)
3 | - DoubleConversion (1.1.6)
4 | - FBLazyVector (0.61.5)
5 | - FBReactNativeSpec (0.61.5):
6 | - Folly (= 2018.10.22.00)
7 | - RCTRequired (= 0.61.5)
8 | - RCTTypeSafety (= 0.61.5)
9 | - React-Core (= 0.61.5)
10 | - React-jsi (= 0.61.5)
11 | - ReactCommon/turbomodule/core (= 0.61.5)
12 | - Folly (2018.10.22.00):
13 | - boost-for-react-native
14 | - DoubleConversion
15 | - Folly/Default (= 2018.10.22.00)
16 | - glog
17 | - Folly/Default (2018.10.22.00):
18 | - boost-for-react-native
19 | - DoubleConversion
20 | - glog
21 | - glog (0.3.5)
22 | - RCTRequired (0.61.5)
23 | - RCTTypeSafety (0.61.5):
24 | - FBLazyVector (= 0.61.5)
25 | - Folly (= 2018.10.22.00)
26 | - RCTRequired (= 0.61.5)
27 | - React-Core (= 0.61.5)
28 | - React (0.61.5):
29 | - React-Core (= 0.61.5)
30 | - React-Core/DevSupport (= 0.61.5)
31 | - React-Core/RCTWebSocket (= 0.61.5)
32 | - React-RCTActionSheet (= 0.61.5)
33 | - React-RCTAnimation (= 0.61.5)
34 | - React-RCTBlob (= 0.61.5)
35 | - React-RCTImage (= 0.61.5)
36 | - React-RCTLinking (= 0.61.5)
37 | - React-RCTNetwork (= 0.61.5)
38 | - React-RCTSettings (= 0.61.5)
39 | - React-RCTText (= 0.61.5)
40 | - React-RCTVibration (= 0.61.5)
41 | - React-Core (0.61.5):
42 | - Folly (= 2018.10.22.00)
43 | - glog
44 | - React-Core/Default (= 0.61.5)
45 | - React-cxxreact (= 0.61.5)
46 | - React-jsi (= 0.61.5)
47 | - React-jsiexecutor (= 0.61.5)
48 | - Yoga
49 | - React-Core/CoreModulesHeaders (0.61.5):
50 | - Folly (= 2018.10.22.00)
51 | - glog
52 | - React-Core/Default
53 | - React-cxxreact (= 0.61.5)
54 | - React-jsi (= 0.61.5)
55 | - React-jsiexecutor (= 0.61.5)
56 | - Yoga
57 | - React-Core/Default (0.61.5):
58 | - Folly (= 2018.10.22.00)
59 | - glog
60 | - React-cxxreact (= 0.61.5)
61 | - React-jsi (= 0.61.5)
62 | - React-jsiexecutor (= 0.61.5)
63 | - Yoga
64 | - React-Core/DevSupport (0.61.5):
65 | - Folly (= 2018.10.22.00)
66 | - glog
67 | - React-Core/Default (= 0.61.5)
68 | - React-Core/RCTWebSocket (= 0.61.5)
69 | - React-cxxreact (= 0.61.5)
70 | - React-jsi (= 0.61.5)
71 | - React-jsiexecutor (= 0.61.5)
72 | - React-jsinspector (= 0.61.5)
73 | - Yoga
74 | - React-Core/RCTActionSheetHeaders (0.61.5):
75 | - Folly (= 2018.10.22.00)
76 | - glog
77 | - React-Core/Default
78 | - React-cxxreact (= 0.61.5)
79 | - React-jsi (= 0.61.5)
80 | - React-jsiexecutor (= 0.61.5)
81 | - Yoga
82 | - React-Core/RCTAnimationHeaders (0.61.5):
83 | - Folly (= 2018.10.22.00)
84 | - glog
85 | - React-Core/Default
86 | - React-cxxreact (= 0.61.5)
87 | - React-jsi (= 0.61.5)
88 | - React-jsiexecutor (= 0.61.5)
89 | - Yoga
90 | - React-Core/RCTBlobHeaders (0.61.5):
91 | - Folly (= 2018.10.22.00)
92 | - glog
93 | - React-Core/Default
94 | - React-cxxreact (= 0.61.5)
95 | - React-jsi (= 0.61.5)
96 | - React-jsiexecutor (= 0.61.5)
97 | - Yoga
98 | - React-Core/RCTImageHeaders (0.61.5):
99 | - Folly (= 2018.10.22.00)
100 | - glog
101 | - React-Core/Default
102 | - React-cxxreact (= 0.61.5)
103 | - React-jsi (= 0.61.5)
104 | - React-jsiexecutor (= 0.61.5)
105 | - Yoga
106 | - React-Core/RCTLinkingHeaders (0.61.5):
107 | - Folly (= 2018.10.22.00)
108 | - glog
109 | - React-Core/Default
110 | - React-cxxreact (= 0.61.5)
111 | - React-jsi (= 0.61.5)
112 | - React-jsiexecutor (= 0.61.5)
113 | - Yoga
114 | - React-Core/RCTNetworkHeaders (0.61.5):
115 | - Folly (= 2018.10.22.00)
116 | - glog
117 | - React-Core/Default
118 | - React-cxxreact (= 0.61.5)
119 | - React-jsi (= 0.61.5)
120 | - React-jsiexecutor (= 0.61.5)
121 | - Yoga
122 | - React-Core/RCTSettingsHeaders (0.61.5):
123 | - Folly (= 2018.10.22.00)
124 | - glog
125 | - React-Core/Default
126 | - React-cxxreact (= 0.61.5)
127 | - React-jsi (= 0.61.5)
128 | - React-jsiexecutor (= 0.61.5)
129 | - Yoga
130 | - React-Core/RCTTextHeaders (0.61.5):
131 | - Folly (= 2018.10.22.00)
132 | - glog
133 | - React-Core/Default
134 | - React-cxxreact (= 0.61.5)
135 | - React-jsi (= 0.61.5)
136 | - React-jsiexecutor (= 0.61.5)
137 | - Yoga
138 | - React-Core/RCTVibrationHeaders (0.61.5):
139 | - Folly (= 2018.10.22.00)
140 | - glog
141 | - React-Core/Default
142 | - React-cxxreact (= 0.61.5)
143 | - React-jsi (= 0.61.5)
144 | - React-jsiexecutor (= 0.61.5)
145 | - Yoga
146 | - React-Core/RCTWebSocket (0.61.5):
147 | - Folly (= 2018.10.22.00)
148 | - glog
149 | - React-Core/Default (= 0.61.5)
150 | - React-cxxreact (= 0.61.5)
151 | - React-jsi (= 0.61.5)
152 | - React-jsiexecutor (= 0.61.5)
153 | - Yoga
154 | - React-CoreModules (0.61.5):
155 | - FBReactNativeSpec (= 0.61.5)
156 | - Folly (= 2018.10.22.00)
157 | - RCTTypeSafety (= 0.61.5)
158 | - React-Core/CoreModulesHeaders (= 0.61.5)
159 | - React-RCTImage (= 0.61.5)
160 | - ReactCommon/turbomodule/core (= 0.61.5)
161 | - React-cxxreact (0.61.5):
162 | - boost-for-react-native (= 1.63.0)
163 | - DoubleConversion
164 | - Folly (= 2018.10.22.00)
165 | - glog
166 | - React-jsinspector (= 0.61.5)
167 | - React-jsi (0.61.5):
168 | - boost-for-react-native (= 1.63.0)
169 | - DoubleConversion
170 | - Folly (= 2018.10.22.00)
171 | - glog
172 | - React-jsi/Default (= 0.61.5)
173 | - React-jsi/Default (0.61.5):
174 | - boost-for-react-native (= 1.63.0)
175 | - DoubleConversion
176 | - Folly (= 2018.10.22.00)
177 | - glog
178 | - React-jsiexecutor (0.61.5):
179 | - DoubleConversion
180 | - Folly (= 2018.10.22.00)
181 | - glog
182 | - React-cxxreact (= 0.61.5)
183 | - React-jsi (= 0.61.5)
184 | - React-jsinspector (0.61.5)
185 | - react-native-bubble-select (0.4.1):
186 | - React
187 | - React-RCTActionSheet (0.61.5):
188 | - React-Core/RCTActionSheetHeaders (= 0.61.5)
189 | - React-RCTAnimation (0.61.5):
190 | - React-Core/RCTAnimationHeaders (= 0.61.5)
191 | - React-RCTBlob (0.61.5):
192 | - React-Core/RCTBlobHeaders (= 0.61.5)
193 | - React-Core/RCTWebSocket (= 0.61.5)
194 | - React-jsi (= 0.61.5)
195 | - React-RCTNetwork (= 0.61.5)
196 | - React-RCTImage (0.61.5):
197 | - React-Core/RCTImageHeaders (= 0.61.5)
198 | - React-RCTNetwork (= 0.61.5)
199 | - React-RCTLinking (0.61.5):
200 | - React-Core/RCTLinkingHeaders (= 0.61.5)
201 | - React-RCTNetwork (0.61.5):
202 | - React-Core/RCTNetworkHeaders (= 0.61.5)
203 | - React-RCTSettings (0.61.5):
204 | - React-Core/RCTSettingsHeaders (= 0.61.5)
205 | - React-RCTText (0.61.5):
206 | - React-Core/RCTTextHeaders (= 0.61.5)
207 | - React-RCTVibration (0.61.5):
208 | - React-Core/RCTVibrationHeaders (= 0.61.5)
209 | - ReactCommon/jscallinvoker (0.61.5):
210 | - DoubleConversion
211 | - Folly (= 2018.10.22.00)
212 | - glog
213 | - React-cxxreact (= 0.61.5)
214 | - ReactCommon/turbomodule/core (0.61.5):
215 | - DoubleConversion
216 | - Folly (= 2018.10.22.00)
217 | - glog
218 | - React-Core (= 0.61.5)
219 | - React-cxxreact (= 0.61.5)
220 | - React-jsi (= 0.61.5)
221 | - ReactCommon/jscallinvoker (= 0.61.5)
222 | - Yoga (1.14.0)
223 |
224 | DEPENDENCIES:
225 | - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
226 | - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
227 | - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
228 | - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
229 | - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
230 | - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
231 | - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
232 | - React (from `../node_modules/react-native/`)
233 | - React-Core (from `../node_modules/react-native/`)
234 | - React-Core/DevSupport (from `../node_modules/react-native/`)
235 | - React-Core/RCTWebSocket (from `../node_modules/react-native/`)
236 | - React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
237 | - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
238 | - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
239 | - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
240 | - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
241 | - react-native-bubble-select (from `../..`)
242 | - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
243 | - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
244 | - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`)
245 | - React-RCTImage (from `../node_modules/react-native/Libraries/Image`)
246 | - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`)
247 | - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`)
248 | - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
249 | - React-RCTText (from `../node_modules/react-native/Libraries/Text`)
250 | - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
251 | - ReactCommon/jscallinvoker (from `../node_modules/react-native/ReactCommon`)
252 | - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
253 | - Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
254 |
255 | SPEC REPOS:
256 | trunk:
257 | - boost-for-react-native
258 |
259 | EXTERNAL SOURCES:
260 | DoubleConversion:
261 | :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
262 | FBLazyVector:
263 | :path: "../node_modules/react-native/Libraries/FBLazyVector"
264 | FBReactNativeSpec:
265 | :path: "../node_modules/react-native/Libraries/FBReactNativeSpec"
266 | Folly:
267 | :podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec"
268 | glog:
269 | :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
270 | RCTRequired:
271 | :path: "../node_modules/react-native/Libraries/RCTRequired"
272 | RCTTypeSafety:
273 | :path: "../node_modules/react-native/Libraries/TypeSafety"
274 | React:
275 | :path: "../node_modules/react-native/"
276 | React-Core:
277 | :path: "../node_modules/react-native/"
278 | React-CoreModules:
279 | :path: "../node_modules/react-native/React/CoreModules"
280 | React-cxxreact:
281 | :path: "../node_modules/react-native/ReactCommon/cxxreact"
282 | React-jsi:
283 | :path: "../node_modules/react-native/ReactCommon/jsi"
284 | React-jsiexecutor:
285 | :path: "../node_modules/react-native/ReactCommon/jsiexecutor"
286 | React-jsinspector:
287 | :path: "../node_modules/react-native/ReactCommon/jsinspector"
288 | react-native-bubble-select:
289 | :path: "../.."
290 | React-RCTActionSheet:
291 | :path: "../node_modules/react-native/Libraries/ActionSheetIOS"
292 | React-RCTAnimation:
293 | :path: "../node_modules/react-native/Libraries/NativeAnimation"
294 | React-RCTBlob:
295 | :path: "../node_modules/react-native/Libraries/Blob"
296 | React-RCTImage:
297 | :path: "../node_modules/react-native/Libraries/Image"
298 | React-RCTLinking:
299 | :path: "../node_modules/react-native/Libraries/LinkingIOS"
300 | React-RCTNetwork:
301 | :path: "../node_modules/react-native/Libraries/Network"
302 | React-RCTSettings:
303 | :path: "../node_modules/react-native/Libraries/Settings"
304 | React-RCTText:
305 | :path: "../node_modules/react-native/Libraries/Text"
306 | React-RCTVibration:
307 | :path: "../node_modules/react-native/Libraries/Vibration"
308 | ReactCommon:
309 | :path: "../node_modules/react-native/ReactCommon"
310 | Yoga:
311 | :path: "../node_modules/react-native/ReactCommon/yoga"
312 |
313 | SPEC CHECKSUMS:
314 | boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
315 | DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
316 | FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
317 | FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
318 | Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
319 | glog: 1f3da668190260b06b429bb211bfbee5cd790c28
320 | RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
321 | RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
322 | React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
323 | React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
324 | React-CoreModules: d04f8494c1a328b69ec11db9d1137d667f916dcb
325 | React-cxxreact: d0f7bcafa196ae410e5300736b424455e7fb7ba7
326 | React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
327 | React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
328 | React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
329 | react-native-bubble-select: a442625a79e6bfc7b1d59915bf97497045e7fa33
330 | React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
331 | React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
332 | React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
333 | React-RCTImage: 6b8e8df449eb7c814c99a92d6b52de6fe39dea4e
334 | React-RCTLinking: 121bb231c7503cf9094f4d8461b96a130fabf4a5
335 | React-RCTNetwork: fb353640aafcee84ca8b78957297bd395f065c9a
336 | React-RCTSettings: 8db258ea2a5efee381fcf7a6d5044e2f8b68b640
337 | React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe
338 | React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
339 | ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
340 | Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
341 |
342 | PODFILE CHECKSUM: 03bd40bb176112a5301efe775fc68a96aaa45413
343 |
344 | COCOAPODS: 1.8.4
345 |
--------------------------------------------------------------------------------
/example/metro.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const blacklist = require('metro-config/src/defaults/blacklist');
4 | const escape = require('escape-string-regexp');
5 |
6 | const root = path.resolve(__dirname, '..');
7 | const pak = JSON.parse(
8 | fs.readFileSync(path.join(root, 'package.json'), 'utf8')
9 | );
10 |
11 | const modules = [
12 | '@babel/runtime',
13 | ...Object.keys({
14 | ...pak.dependencies,
15 | ...pak.peerDependencies,
16 | }),
17 | ];
18 |
19 | module.exports = {
20 | projectRoot: __dirname,
21 | watchFolders: [root],
22 |
23 | resolver: {
24 | blacklistRE: blacklist([
25 | new RegExp(`^${escape(path.join(root, 'node_modules'))}\\/.*$`),
26 | ]),
27 |
28 | extraNodeModules: modules.reduce((acc, name) => {
29 | acc[name] = path.join(__dirname, 'node_modules', name);
30 | return acc;
31 | }, {}),
32 | },
33 |
34 | transformer: {
35 | getTransformOptions: async () => ({
36 | transform: {
37 | experimentalImportSupport: false,
38 | inlineRequires: true,
39 | },
40 | }),
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-bubble-select-example",
3 | "description": "Example app for react-native-bubble-select",
4 | "version": "0.0.1",
5 | "private": true,
6 | "scripts": {
7 | "android": "react-native run-android",
8 | "ios": "react-native run-ios",
9 | "start": "react-native start"
10 | },
11 | "dependencies": {
12 | "react": "16.9.0",
13 | "react-native": "0.61.5"
14 | },
15 | "devDependencies": {
16 | "@babel/core": "^7.8.4",
17 | "@babel/runtime": "^7.8.4",
18 | "metro-react-native-babel-preset": "^0.58.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {
3 | StyleSheet,
4 | View,
5 | Text,
6 | SafeAreaView,
7 | Button,
8 | Platform,
9 | Dimensions,
10 | } from 'react-native';
11 | import BubbleSelect, { Bubble, BubbleNode } from 'react-native-bubble-select';
12 | import randomCity, { randomCities } from './randomCity';
13 |
14 | const { width, height } = Dimensions.get('window');
15 |
16 | export default function App() {
17 | const [cities, setCities] = React.useState([]);
18 | const [force, setForce] = React.useState(false);
19 | const [selectedCites, setSelectedCities] = React.useState([]);
20 | const [removedCities, setRemovedCities] = React.useState([]);
21 |
22 | React.useEffect(() => {
23 | if (force) {
24 | setCities(randomCities());
25 | }
26 | }, [force]);
27 |
28 | React.useEffect(() => {
29 | if (Platform.OS === 'ios') {
30 | setForce(true);
31 | } else {
32 | setCities(randomCities());
33 | }
34 | }, []);
35 |
36 | const addCity = () => {
37 | setCities([...cities, randomCity()]);
38 | };
39 |
40 | const handleSelect = (bubble: BubbleNode) => {
41 | setSelectedCities([...selectedCites, bubble]);
42 | };
43 |
44 | const handleDeselect = (bubble: BubbleNode) => {
45 | setSelectedCities(selectedCites.filter(({ id }) => id !== bubble.id));
46 | };
47 |
48 | const handleRemove = (bubble: BubbleNode) => {
49 | console.log(bubble);
50 | setRemovedCities([...removedCities, bubble]);
51 | };
52 |
53 | return (
54 |
55 |
56 |
57 | Discover New Cities
58 |
59 | Tap on the places you love, hold on the places you don't.
60 |
61 | {selectedCites.length > 0 ? (
62 |
63 | Selected: {selectedCites.map(city => city.text).join(', ')}
64 |
65 | ) : null}
66 | {removedCities.length > 0 ? (
67 |
68 | Removed: {removedCities.map(city => city.text).join(', ')}
69 |
70 | ) : null}
71 |
72 |
85 | {cities.map(city => (
86 |
95 | ))}
96 |
97 |
98 |
99 |
100 |
101 |
102 | );
103 | }
104 |
105 | const styles = StyleSheet.create({
106 | safeArea: {
107 | flex: 1,
108 | },
109 | container: {
110 | flex: 1,
111 | backgroundColor: 'white',
112 | },
113 | header: {
114 | flexDirection: 'column',
115 | alignItems: 'center',
116 | justifyContent: 'space-between',
117 | paddingTop: 45,
118 | },
119 | title: {
120 | fontSize: 30,
121 | fontWeight: 'bold',
122 | textAlign: 'center',
123 | marginBottom: 10,
124 | },
125 | footer: {
126 | flexDirection: 'row',
127 | width: '100%',
128 | paddingLeft: 50,
129 | paddingRight: 50,
130 | marginBottom: Platform.select({
131 | android: 50,
132 | }),
133 | alignItems: 'center',
134 | },
135 | message: {},
136 | selectedCity: {
137 | marginTop: 15,
138 | fontSize: 12,
139 | maxWidth: '80%',
140 | textAlign: 'center',
141 | },
142 | });
143 |
--------------------------------------------------------------------------------
/example/src/randomCity.ts:
--------------------------------------------------------------------------------
1 | import cities from './cities.json';
2 | import randomColor from './randomColor';
3 | import { Platform } from 'react-native';
4 |
5 | let i = 0;
6 |
7 | export default function randomCity() {
8 | const { city } = cities[Math.floor(Math.random() * cities.length)];
9 | i += 1;
10 | let color = {};
11 |
12 | // assign a gradient to odd items
13 | if (i % 2 === 0) {
14 | color = {
15 | gradient: {
16 | startColor: randomColor(),
17 | endColor: randomColor(),
18 | direction: 'vertical',
19 | },
20 | };
21 | } else {
22 | color = {
23 | color: randomColor(),
24 | };
25 | }
26 |
27 | return Platform.select({
28 | ios: {
29 | id: `${city}--${i}`,
30 | text: city,
31 | color: randomColor(),
32 | selectedColor: randomColor(),
33 | selectedScale: Math.floor(Math.random() * 1.5) + 1.2,
34 | },
35 | android: {
36 | id: `${city}--${i}`,
37 | text: city,
38 | ...color,
39 | },
40 | });
41 | }
42 |
43 | export function randomCities() {
44 | return [...Array(15).keys()].map(randomCity);
45 | }
46 |
--------------------------------------------------------------------------------
/example/src/randomColor.ts:
--------------------------------------------------------------------------------
1 | export default function randomColor() {
2 | var letters = '0123456789ABCDEF';
3 | var color = '#';
4 | for (var i = 0; i < 6; i++) {
5 | color += letters[Math.floor(Math.random() * 16)];
6 | }
7 | return color;
8 | }
9 |
--------------------------------------------------------------------------------
/ios/BubbleSelect-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import
6 | #import
7 | #import
8 |
--------------------------------------------------------------------------------
/ios/BubbleSelect.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 95899B62243399A600F2490D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95899B5C243399A600F2490D /* Extensions.swift */; };
11 | 95899B63243399A600F2490D /* SKMultilineLabelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95899B5D243399A600F2490D /* SKMultilineLabelNode.swift */; };
12 | 95899B64243399A600F2490D /* MagneticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95899B5E243399A600F2490D /* MagneticView.swift */; };
13 | 95899B65243399A600F2490D /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95899B5F243399A600F2490D /* Node.swift */; };
14 | 95899B66243399A600F2490D /* SKAction+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95899B60243399A600F2490D /* SKAction+Color.swift */; };
15 | 95899B67243399A600F2490D /* Magnetic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95899B61243399A600F2490D /* Magnetic.swift */; };
16 | 9594C7E6243103DA00EAE357 /* MagneticViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9594C7E2243103D900EAE357 /* MagneticViewExtension.swift */; };
17 | 9594C7E7243103DA00EAE357 /* RNBubbleSelectNodeViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9594C7E3243103DA00EAE357 /* RNBubbleSelectNodeViewManager.swift */; };
18 | 9594C7E8243103DA00EAE357 /* RNBubbleSelectNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9594C7E4243103DA00EAE357 /* RNBubbleSelectNode.swift */; };
19 | 9594C7E9243103DA00EAE357 /* RNBubbleSelectViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9594C7E5243103DA00EAE357 /* RNBubbleSelectViewManager.swift */; };
20 | 9594C7F02431047200EAE357 /* RNBubbleSelectNodeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9594C7EE2431047100EAE357 /* RNBubbleSelectNodeViewManager.m */; };
21 | 9594C7F12431047200EAE357 /* RNBubbleSelectViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9594C7EF2431047200EAE357 /* RNBubbleSelectViewManager.m */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXCopyFilesBuildPhase section */
25 | 58B511D91A9E6C8500147676 /* CopyFiles */ = {
26 | isa = PBXCopyFilesBuildPhase;
27 | buildActionMask = 2147483647;
28 | dstPath = "include/$(PRODUCT_NAME)";
29 | dstSubfolderSpec = 16;
30 | files = (
31 | );
32 | runOnlyForDeploymentPostprocessing = 0;
33 | };
34 | /* End PBXCopyFilesBuildPhase section */
35 |
36 | /* Begin PBXFileReference section */
37 | 134814201AA4EA6300B7C361 /* libBubbleSelect.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBubbleSelect.a; sourceTree = BUILT_PRODUCTS_DIR; };
38 | 95899B5C243399A600F2490D /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
39 | 95899B5D243399A600F2490D /* SKMultilineLabelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKMultilineLabelNode.swift; sourceTree = ""; };
40 | 95899B5E243399A600F2490D /* MagneticView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagneticView.swift; sourceTree = ""; };
41 | 95899B5F243399A600F2490D /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; };
42 | 95899B60243399A600F2490D /* SKAction+Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+Color.swift"; sourceTree = ""; };
43 | 95899B61243399A600F2490D /* Magnetic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Magnetic.swift; sourceTree = ""; };
44 | 9594C7D92431020E00EAE357 /* BubbleSelect-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BubbleSelect-Bridging-Header.h"; sourceTree = ""; };
45 | 9594C7E2243103D900EAE357 /* MagneticViewExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagneticViewExtension.swift; sourceTree = ""; };
46 | 9594C7E3243103DA00EAE357 /* RNBubbleSelectNodeViewManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNBubbleSelectNodeViewManager.swift; sourceTree = ""; };
47 | 9594C7E4243103DA00EAE357 /* RNBubbleSelectNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNBubbleSelectNode.swift; sourceTree = ""; };
48 | 9594C7E5243103DA00EAE357 /* RNBubbleSelectViewManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNBubbleSelectViewManager.swift; sourceTree = ""; };
49 | 9594C7EE2431047100EAE357 /* RNBubbleSelectNodeViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNBubbleSelectNodeViewManager.m; sourceTree = ""; };
50 | 9594C7EF2431047200EAE357 /* RNBubbleSelectViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNBubbleSelectViewManager.m; sourceTree = ""; };
51 | /* End PBXFileReference section */
52 |
53 | /* Begin PBXFrameworksBuildPhase section */
54 | 58B511D81A9E6C8500147676 /* Frameworks */ = {
55 | isa = PBXFrameworksBuildPhase;
56 | buildActionMask = 2147483647;
57 | files = (
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | /* End PBXFrameworksBuildPhase section */
62 |
63 | /* Begin PBXGroup section */
64 | 134814211AA4EA7D00B7C361 /* Products */ = {
65 | isa = PBXGroup;
66 | children = (
67 | 134814201AA4EA6300B7C361 /* libBubbleSelect.a */,
68 | );
69 | name = Products;
70 | sourceTree = "";
71 | };
72 | 58B511D21A9E6C8500147676 = {
73 | isa = PBXGroup;
74 | children = (
75 | 95899B5B2433997700F2490D /* Magnetic */,
76 | 9594C7E2243103D900EAE357 /* MagneticViewExtension.swift */,
77 | 9594C7EE2431047100EAE357 /* RNBubbleSelectNodeViewManager.m */,
78 | 9594C7EF2431047200EAE357 /* RNBubbleSelectViewManager.m */,
79 | 9594C7E4243103DA00EAE357 /* RNBubbleSelectNode.swift */,
80 | 9594C7E3243103DA00EAE357 /* RNBubbleSelectNodeViewManager.swift */,
81 | 9594C7E5243103DA00EAE357 /* RNBubbleSelectViewManager.swift */,
82 | 9594C7D92431020E00EAE357 /* BubbleSelect-Bridging-Header.h */,
83 | 134814211AA4EA7D00B7C361 /* Products */,
84 | );
85 | sourceTree = "";
86 | };
87 | 95899B5B2433997700F2490D /* Magnetic */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 95899B5C243399A600F2490D /* Extensions.swift */,
91 | 95899B61243399A600F2490D /* Magnetic.swift */,
92 | 95899B5E243399A600F2490D /* MagneticView.swift */,
93 | 95899B5F243399A600F2490D /* Node.swift */,
94 | 95899B60243399A600F2490D /* SKAction+Color.swift */,
95 | 95899B5D243399A600F2490D /* SKMultilineLabelNode.swift */,
96 | );
97 | path = Magnetic;
98 | sourceTree = "";
99 | };
100 | /* End PBXGroup section */
101 |
102 | /* Begin PBXNativeTarget section */
103 | 58B511DA1A9E6C8500147676 /* BubbleSelect */ = {
104 | isa = PBXNativeTarget;
105 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BubbleSelect" */;
106 | buildPhases = (
107 | 58B511D71A9E6C8500147676 /* Sources */,
108 | 58B511D81A9E6C8500147676 /* Frameworks */,
109 | 58B511D91A9E6C8500147676 /* CopyFiles */,
110 | );
111 | buildRules = (
112 | );
113 | dependencies = (
114 | );
115 | name = BubbleSelect;
116 | productName = RCTDataManager;
117 | productReference = 134814201AA4EA6300B7C361 /* libBubbleSelect.a */;
118 | productType = "com.apple.product-type.library.static";
119 | };
120 | /* End PBXNativeTarget section */
121 |
122 | /* Begin PBXProject section */
123 | 58B511D31A9E6C8500147676 /* Project object */ = {
124 | isa = PBXProject;
125 | attributes = {
126 | LastUpgradeCheck = 0920;
127 | ORGANIZATIONNAME = Facebook;
128 | TargetAttributes = {
129 | 58B511DA1A9E6C8500147676 = {
130 | CreatedOnToolsVersion = 6.1.1;
131 | LastSwiftMigration = 1130;
132 | };
133 | };
134 | };
135 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BubbleSelect" */;
136 | compatibilityVersion = "Xcode 3.2";
137 | developmentRegion = English;
138 | hasScannedForEncodings = 0;
139 | knownRegions = (
140 | English,
141 | en,
142 | );
143 | mainGroup = 58B511D21A9E6C8500147676;
144 | productRefGroup = 58B511D21A9E6C8500147676;
145 | projectDirPath = "";
146 | projectRoot = "";
147 | targets = (
148 | 58B511DA1A9E6C8500147676 /* BubbleSelect */,
149 | );
150 | };
151 | /* End PBXProject section */
152 |
153 | /* Begin PBXSourcesBuildPhase section */
154 | 58B511D71A9E6C8500147676 /* Sources */ = {
155 | isa = PBXSourcesBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | 9594C7E8243103DA00EAE357 /* RNBubbleSelectNode.swift in Sources */,
159 | 95899B65243399A600F2490D /* Node.swift in Sources */,
160 | 9594C7E9243103DA00EAE357 /* RNBubbleSelectViewManager.swift in Sources */,
161 | 9594C7F02431047200EAE357 /* RNBubbleSelectNodeViewManager.m in Sources */,
162 | 9594C7E7243103DA00EAE357 /* RNBubbleSelectNodeViewManager.swift in Sources */,
163 | 95899B63243399A600F2490D /* SKMultilineLabelNode.swift in Sources */,
164 | 95899B66243399A600F2490D /* SKAction+Color.swift in Sources */,
165 | 9594C7F12431047200EAE357 /* RNBubbleSelectViewManager.m in Sources */,
166 | 9594C7E6243103DA00EAE357 /* MagneticViewExtension.swift in Sources */,
167 | 95899B67243399A600F2490D /* Magnetic.swift in Sources */,
168 | 95899B62243399A600F2490D /* Extensions.swift in Sources */,
169 | 95899B64243399A600F2490D /* MagneticView.swift in Sources */,
170 | );
171 | runOnlyForDeploymentPostprocessing = 0;
172 | };
173 | /* End PBXSourcesBuildPhase section */
174 |
175 | /* Begin XCBuildConfiguration section */
176 | 58B511ED1A9E6C8500147676 /* Debug */ = {
177 | isa = XCBuildConfiguration;
178 | buildSettings = {
179 | ALWAYS_SEARCH_USER_PATHS = NO;
180 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
181 | CLANG_CXX_LIBRARY = "libc++";
182 | CLANG_ENABLE_MODULES = YES;
183 | CLANG_ENABLE_OBJC_ARC = YES;
184 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
185 | CLANG_WARN_BOOL_CONVERSION = YES;
186 | CLANG_WARN_COMMA = YES;
187 | CLANG_WARN_CONSTANT_CONVERSION = YES;
188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
189 | CLANG_WARN_EMPTY_BODY = YES;
190 | CLANG_WARN_ENUM_CONVERSION = YES;
191 | CLANG_WARN_INFINITE_RECURSION = YES;
192 | CLANG_WARN_INT_CONVERSION = YES;
193 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
194 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
195 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
196 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
197 | CLANG_WARN_STRICT_PROTOTYPES = YES;
198 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
199 | CLANG_WARN_UNREACHABLE_CODE = YES;
200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
201 | COPY_PHASE_STRIP = NO;
202 | ENABLE_STRICT_OBJC_MSGSEND = YES;
203 | ENABLE_TESTABILITY = YES;
204 | GCC_C_LANGUAGE_STANDARD = gnu99;
205 | GCC_DYNAMIC_NO_PIC = NO;
206 | GCC_NO_COMMON_BLOCKS = YES;
207 | GCC_OPTIMIZATION_LEVEL = 0;
208 | GCC_PREPROCESSOR_DEFINITIONS = (
209 | "DEBUG=1",
210 | "$(inherited)",
211 | );
212 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
215 | GCC_WARN_UNDECLARED_SELECTOR = YES;
216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
217 | GCC_WARN_UNUSED_FUNCTION = YES;
218 | GCC_WARN_UNUSED_VARIABLE = YES;
219 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
220 | MTL_ENABLE_DEBUG_INFO = YES;
221 | ONLY_ACTIVE_ARCH = YES;
222 | SDKROOT = iphoneos;
223 | };
224 | name = Debug;
225 | };
226 | 58B511EE1A9E6C8500147676 /* Release */ = {
227 | isa = XCBuildConfiguration;
228 | buildSettings = {
229 | ALWAYS_SEARCH_USER_PATHS = NO;
230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
231 | CLANG_CXX_LIBRARY = "libc++";
232 | CLANG_ENABLE_MODULES = YES;
233 | CLANG_ENABLE_OBJC_ARC = YES;
234 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
235 | CLANG_WARN_BOOL_CONVERSION = YES;
236 | CLANG_WARN_COMMA = YES;
237 | CLANG_WARN_CONSTANT_CONVERSION = YES;
238 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
239 | CLANG_WARN_EMPTY_BODY = YES;
240 | CLANG_WARN_ENUM_CONVERSION = YES;
241 | CLANG_WARN_INFINITE_RECURSION = YES;
242 | CLANG_WARN_INT_CONVERSION = YES;
243 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
247 | CLANG_WARN_STRICT_PROTOTYPES = YES;
248 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
249 | CLANG_WARN_UNREACHABLE_CODE = YES;
250 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
251 | COPY_PHASE_STRIP = YES;
252 | ENABLE_NS_ASSERTIONS = NO;
253 | ENABLE_STRICT_OBJC_MSGSEND = YES;
254 | GCC_C_LANGUAGE_STANDARD = gnu99;
255 | GCC_NO_COMMON_BLOCKS = YES;
256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
258 | GCC_WARN_UNDECLARED_SELECTOR = YES;
259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
260 | GCC_WARN_UNUSED_FUNCTION = YES;
261 | GCC_WARN_UNUSED_VARIABLE = YES;
262 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
263 | MTL_ENABLE_DEBUG_INFO = NO;
264 | SDKROOT = iphoneos;
265 | VALIDATE_PRODUCT = YES;
266 | };
267 | name = Release;
268 | };
269 | 58B511F01A9E6C8500147676 /* Debug */ = {
270 | isa = XCBuildConfiguration;
271 | buildSettings = {
272 | CLANG_ENABLE_MODULES = YES;
273 | HEADER_SEARCH_PATHS = (
274 | "$(inherited)",
275 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
276 | "$(SRCROOT)/../../../React/**",
277 | "$(SRCROOT)/../../react-native/React/**",
278 | );
279 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
280 | LIBRARY_SEARCH_PATHS = "$(inherited)";
281 | OTHER_LDFLAGS = "-ObjC";
282 | PRODUCT_NAME = BubbleSelect;
283 | SKIP_INSTALL = YES;
284 | SWIFT_OBJC_BRIDGING_HEADER = "BubbleSelect-Bridging-Header.h";
285 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
286 | SWIFT_VERSION = 5.0;
287 | };
288 | name = Debug;
289 | };
290 | 58B511F11A9E6C8500147676 /* Release */ = {
291 | isa = XCBuildConfiguration;
292 | buildSettings = {
293 | CLANG_ENABLE_MODULES = YES;
294 | HEADER_SEARCH_PATHS = (
295 | "$(inherited)",
296 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
297 | "$(SRCROOT)/../../../React/**",
298 | "$(SRCROOT)/../../react-native/React/**",
299 | );
300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
301 | LIBRARY_SEARCH_PATHS = "$(inherited)";
302 | OTHER_LDFLAGS = "-ObjC";
303 | PRODUCT_NAME = BubbleSelect;
304 | SKIP_INSTALL = YES;
305 | SWIFT_OBJC_BRIDGING_HEADER = "BubbleSelect-Bridging-Header.h";
306 | SWIFT_VERSION = 5.0;
307 | };
308 | name = Release;
309 | };
310 | /* End XCBuildConfiguration section */
311 |
312 | /* Begin XCConfigurationList section */
313 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BubbleSelect" */ = {
314 | isa = XCConfigurationList;
315 | buildConfigurations = (
316 | 58B511ED1A9E6C8500147676 /* Debug */,
317 | 58B511EE1A9E6C8500147676 /* Release */,
318 | );
319 | defaultConfigurationIsVisible = 0;
320 | defaultConfigurationName = Release;
321 | };
322 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BubbleSelect" */ = {
323 | isa = XCConfigurationList;
324 | buildConfigurations = (
325 | 58B511F01A9E6C8500147676 /* Debug */,
326 | 58B511F11A9E6C8500147676 /* Release */,
327 | );
328 | defaultConfigurationIsVisible = 0;
329 | defaultConfigurationName = Release;
330 | };
331 | /* End XCConfigurationList section */
332 | };
333 | rootObject = 58B511D31A9E6C8500147676 /* Project object */;
334 | }
335 |
--------------------------------------------------------------------------------
/ios/BubbleSelect.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/BubbleSelect.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Magnetic/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // Magnetic
4 | //
5 | // Created by Lasha Efremidze on 3/25/17.
6 | // Copyright © 2017 efremidze. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | extension CGFloat {
12 | static func random(_ lower: CGFloat = 0, _ upper: CGFloat = 1) -> CGFloat {
13 | return CGFloat(Float(arc4random()) / Float(UINT32_MAX)) * (upper - lower) + lower
14 | }
15 | }
16 |
17 | extension CGPoint {
18 | func distance(from point: CGPoint) -> CGFloat {
19 | return hypot(point.x - x, point.y - y)
20 | }
21 | }
22 |
23 | extension UIImage {
24 | func aspectFill(_ size: CGSize) -> UIImage {
25 | let aspectWidth = size.width / self.size.width
26 | let aspectHeight = size.height / self.size.height
27 | let aspectRatio = max(aspectWidth, aspectHeight)
28 |
29 | var newSize = self.size
30 | newSize.width *= aspectRatio
31 | newSize.height *= aspectRatio
32 | return resize(newSize)
33 | }
34 | func resize(_ size: CGSize) -> UIImage {
35 | var rect = CGRect.zero
36 | rect.size = size
37 | UIGraphicsBeginImageContextWithOptions(size, false, scale)
38 | draw(in: rect)
39 | let image = UIGraphicsGetImageFromCurrentImageContext()
40 | UIGraphicsEndImageContext()
41 | return image!
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ios/Magnetic/Magnetic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Magnetic.swift
3 | // Magnetic
4 | //
5 | // Created by Lasha Efremidze on 3/8/17.
6 | // Copyright © 2017 efremidze. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | @objc public protocol MagneticDelegate: class {
12 | func magnetic(_ magnetic: Magnetic, didSelect node: Node)
13 | func magnetic(_ magnetic: Magnetic, didDeselect node: Node)
14 | @objc optional func magnetic(_ magnetic: Magnetic, didRemove node: Node)
15 | }
16 |
17 | @objcMembers open class Magnetic: SKScene {
18 |
19 | /**
20 | The field node that accelerates the nodes.
21 | */
22 | open lazy var magneticField: SKFieldNode = { [unowned self] in
23 | let field = SKFieldNode.radialGravityField()
24 | self.addChild(field)
25 | return field
26 | }()
27 |
28 | /**
29 | Controls whether you can select multiple nodes.
30 | */
31 | open var allowsMultipleSelection: Bool = true
32 |
33 |
34 | /**
35 | Controls whether an item can be removed by holding down
36 | */
37 | open var removeNodeOnLongPress: Bool = false
38 |
39 | /**
40 | The length of time (in seconds) the node must be held on to trigger a remove event
41 | */
42 | open var longPressDuration: TimeInterval = 0.35
43 |
44 | open var isDragging: Bool = false
45 |
46 | /**
47 | Returns the magnetic nodes
48 | */
49 | open var nodes: [Node] {
50 | return children.compactMap { $0 as? Node }
51 | }
52 |
53 | /**
54 | The selected children.
55 | */
56 | open var selectedChildren: [Node] {
57 | return nodes.filter { $0.isSelected }
58 | }
59 |
60 | /**
61 | The removed children.
62 | */
63 | open var removedChildren: [Node] {
64 | return nodes.filter { $0.isRemoved }
65 | }
66 |
67 | /**
68 | The object that acts as the delegate of the scene.
69 |
70 | The delegate must adopt the MagneticDelegate protocol. The delegate is not retained.
71 | */
72 | open weak var magneticDelegate: MagneticDelegate?
73 |
74 | private var touchStarted: TimeInterval?
75 |
76 | override open var size: CGSize {
77 | didSet {
78 | configure()
79 | }
80 | }
81 |
82 | override public init(size: CGSize) {
83 | super.init(size: size)
84 |
85 | commonInit()
86 | }
87 |
88 | required public init?(coder aDecoder: NSCoder) {
89 | super.init(coder: aDecoder)
90 |
91 | commonInit()
92 | }
93 |
94 | func commonInit() {
95 | backgroundColor = .white
96 | scaleMode = .aspectFill
97 | configure()
98 | }
99 |
100 | func configure() {
101 | let strength = Float(max(size.width, size.height))
102 | let radius = strength.squareRoot() * 100
103 |
104 | physicsWorld.gravity = CGVector(dx: 0, dy: 0)
105 | physicsBody = SKPhysicsBody(edgeLoopFrom: { () -> CGRect in
106 | var frame = self.frame
107 | frame.size.width = CGFloat(radius)
108 | frame.origin.x -= frame.size.width / 2
109 | return frame
110 | }())
111 |
112 | magneticField.region = SKRegion(radius: radius)
113 | magneticField.minimumRadius = radius
114 | magneticField.strength = strength
115 | magneticField.position = CGPoint(x: size.width / 2, y: size.height / 2)
116 | }
117 |
118 | override open func addChild(_ node: SKNode) {
119 | var x = -node.frame.width // left
120 | if children.count % 2 == 0 {
121 | x = frame.width + node.frame.width // right
122 | }
123 | let y = CGFloat.random(node.frame.height, frame.height - node.frame.height)
124 | node.position = CGPoint(x: x, y: y)
125 | super.addChild(node)
126 | }
127 |
128 | }
129 |
130 | extension Magnetic {
131 |
132 | open override func touchesBegan(_ touches: Set, with event: UIEvent?) {
133 | guard removeNodeOnLongPress, let touch = touches.first else { return }
134 | touchStarted = touch.timestamp
135 | }
136 |
137 | override open func touchesMoved(_ touches: Set, with event: UIEvent?) {
138 | guard let touch = touches.first else { return }
139 | let location = touch.location(in: self)
140 | let previous = touch.previousLocation(in: self)
141 | guard location.distance(from: previous) != 0 else { return }
142 |
143 | isDragging = true
144 |
145 | moveNodes(location: location, previous: previous)
146 | }
147 |
148 | override open func touchesEnded(_ touches: Set, with event: UIEvent?) {
149 | guard let touch = touches.first else { return }
150 | let location = touch.location(in: self)
151 |
152 | defer { isDragging = false }
153 | guard !isDragging, let node = node(at: location) else { return }
154 |
155 | if removeNodeOnLongPress && !node.isSelected {
156 | guard let touchStarted = touchStarted else { return }
157 | let touchEnded = touch.timestamp
158 | let timeDiff = touchEnded - touchStarted
159 |
160 | if (timeDiff >= longPressDuration) {
161 | node.removedAnimation {
162 | node.isRemoved = true
163 | self.magneticDelegate?.magnetic?(self, didRemove: node)
164 | }
165 | return
166 | }
167 | }
168 |
169 | if node.isSelected {
170 | node.isSelected = false
171 | magneticDelegate?.magnetic(self, didDeselect: node)
172 | } else {
173 | if !allowsMultipleSelection, let selectedNode = selectedChildren.first {
174 | selectedNode.isSelected = false
175 | magneticDelegate?.magnetic(self, didDeselect: selectedNode)
176 | }
177 | node.isSelected = true
178 | magneticDelegate?.magnetic(self, didSelect: node)
179 | }
180 | }
181 |
182 | override open func touchesCancelled(_ touches: Set, with event: UIEvent?) {
183 | isDragging = false
184 | }
185 |
186 | }
187 |
188 | extension Magnetic {
189 |
190 | open func moveNodes(location: CGPoint, previous: CGPoint) {
191 | let x = location.x - previous.x
192 | let y = location.y - previous.y
193 |
194 | for node in children {
195 | let distance = node.position.distance(from: location)
196 | let acceleration: CGFloat = 3 * pow(distance, 1/2)
197 | let direction = CGVector(dx: x * acceleration, dy: y * acceleration)
198 | node.physicsBody?.applyForce(direction)
199 | }
200 | }
201 |
202 | open func node(at point: CGPoint) -> Node? {
203 | return nodes(at: point).compactMap { $0 as? Node }.filter { $0.path!.contains(convert(point, to: $0)) }.first
204 | }
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/ios/Magnetic/MagneticView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MagneticView.swift
3 | // Magnetic
4 | //
5 | // Created by Lasha Efremidze on 3/28/17.
6 | // Copyright © 2017 efremidze. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | public class MagneticView: SKView {
12 |
13 | @objc
14 | public lazy var magnetic: Magnetic = { [unowned self] in
15 | let scene = Magnetic(size: self.bounds.size)
16 | self.presentScene(scene)
17 | return scene
18 | }()
19 |
20 | override public init(frame: CGRect) {
21 | super.init(frame: frame)
22 |
23 | commonInit()
24 | }
25 |
26 | required public init?(coder aDecoder: NSCoder) {
27 | super.init(coder: aDecoder)
28 |
29 | commonInit()
30 | }
31 |
32 | func commonInit() {
33 | _ = magnetic
34 | }
35 |
36 | public override func layoutSubviews() {
37 | super.layoutSubviews()
38 |
39 | magnetic.size = bounds.size
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/ios/Magnetic/Node.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Node.swift
3 | // Magnetic
4 | //
5 | // Created by Lasha Efremidze on 3/25/17.
6 | // Copyright © 2017 efremidze. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | @objcMembers open class Node: SKShapeNode {
12 |
13 | public lazy var label: SKMultilineLabelNode = { [unowned self] in
14 | let label = SKMultilineLabelNode()
15 | label.fontName = Defaults.fontName
16 | label.fontSize = Defaults.fontSize
17 | label.fontColor = Defaults.fontColor
18 | label.verticalAlignmentMode = .center
19 | label.width = self.frame.width
20 | label.separator = " "
21 | addChild(label)
22 | return label
23 | }()
24 |
25 | open var id: String!
26 |
27 | /**
28 | The text displayed by the node.
29 | */
30 | open var text: String? {
31 | get { return label.text }
32 | set {
33 | label.text = newValue
34 | resize()
35 | }
36 | }
37 |
38 | /**
39 | The image displayed by the node.
40 | */
41 | open var image: UIImage? {
42 | didSet {
43 | // let url = URL(string: "https://picsum.photos/1200/600")!
44 | // let image = UIImage(data: try! Data(contentsOf: url))
45 | texture = image.map { SKTexture(image: $0.aspectFill(self.frame.size)) }
46 | }
47 | }
48 |
49 | /**
50 | The color of the node.
51 |
52 | Also blends the color with the image.
53 | */
54 | open var color: UIColor = Defaults.color {
55 | didSet {
56 | self.fillColor = color
57 | }
58 | }
59 |
60 | open var texture: SKTexture?
61 |
62 | /**
63 | The selection state of the node.
64 | */
65 | open var isSelected: Bool = false {
66 | didSet {
67 | guard isSelected != oldValue else { return }
68 | if isSelected {
69 | selectedAnimation()
70 | } else {
71 | deselectedAnimation()
72 | }
73 | }
74 | }
75 |
76 | /**
77 | The removal state of the node
78 | */
79 | open var isRemoved: Bool = false
80 |
81 | /**
82 | Controls whether the node should auto resize to fit its content
83 | */
84 | open var scaleToFitContent: Bool = Defaults.scaleToFitContent {
85 | didSet {
86 | resize()
87 | }
88 | }
89 |
90 | /**
91 | Additional padding to be applied on resize
92 | */
93 | open var padding: CGFloat = Defaults.padding {
94 | didSet {
95 | resize()
96 | }
97 | }
98 |
99 | /**
100 | The scale of the selected animation
101 | */
102 | open var selectedScale: CGFloat = 4 / 3
103 |
104 | /**
105 | The scale of the deselected animation
106 | */
107 | open var deselectedScale: CGFloat = 1
108 |
109 | /**
110 | The original color of the node before animation
111 | */
112 | private var originalColor: UIColor = Defaults.color
113 |
114 | /**
115 | The color of the seleted node
116 | */
117 | open var selectedColor: UIColor?
118 |
119 | /**
120 | The text color of the seleted node
121 | */
122 | open var selectedFontColor: UIColor?
123 |
124 | /**
125 | The original text color of the node before animation
126 | */
127 | private var originalFontColor: UIColor = Defaults.fontColor
128 |
129 | /**
130 | The duration of the selected/deselected animations
131 | */
132 | open var animationDuration: TimeInterval = 0.2
133 |
134 | /**
135 | The name of the label's font
136 | */
137 | open var fontName: String {
138 | get { label.fontName ?? Defaults.fontName }
139 | set {
140 | label.fontName = newValue
141 | resize()
142 | }
143 | }
144 |
145 | /**
146 | The size of the label's font
147 | */
148 | open var fontSize: CGFloat {
149 | get { label.fontSize }
150 | set {
151 | label.fontSize = newValue
152 | resize()
153 | }
154 | }
155 |
156 | /**
157 | The color of the label's font
158 | */
159 | open var fontColor: UIColor {
160 | get { label.fontColor ?? Defaults.fontColor }
161 | set { label.fontColor = newValue }
162 | }
163 |
164 | /**
165 | The margin scale of the node
166 | */
167 | open var marginScale: CGFloat = Defaults.marginScale {
168 | didSet {
169 | guard let path = path else { return }
170 | regeneratePhysicsBody(withPath: path)
171 | }
172 | }
173 |
174 | /**
175 | The border color of the node
176 | */
177 | open var borderColor: UIColor {
178 | get { strokeColor }
179 | set { strokeColor = newValue }
180 | }
181 |
182 | /**
183 | The border width of the node
184 | */
185 | open var borderWidth: CGFloat {
186 | get { lineWidth }
187 | set {
188 | lineWidth = newValue
189 | resize()
190 | }
191 | }
192 |
193 | open private(set) var radius: CGFloat?
194 |
195 | /**
196 | Set of default values
197 | */
198 | struct Defaults {
199 | static let fontName = "Avenir-Black"
200 | static let fontColor = UIColor.white
201 | static let fontSize = CGFloat(12)
202 | static let color = UIColor.clear
203 | static let marginScale = CGFloat(1.01)
204 | static let scaleToFitContent = false // backwards compatability
205 | static let padding = CGFloat(20)
206 | static let borderWidth = CGFloat(0)
207 | static let borderColor = UIColor.clear
208 | }
209 |
210 | /**
211 | Creates a node with a custom path.
212 |
213 | - Parameters:
214 | - text: The text of the node.
215 | - image: The image of the node.
216 | - color: The color of the node.
217 | - path: The path of the node.
218 | - marginScale: The margin scale of the node.
219 |
220 | - Returns: A new node.
221 | */
222 | public init(text: String? = nil, image: UIImage? = nil, color: UIColor, path: CGPath, marginScale: CGFloat = 1.01) {
223 | super.init()
224 | self.path = path
225 | regeneratePhysicsBody(withPath: path)
226 | self.color = color
227 | self.strokeColor = .white
228 | _ = self.text
229 | configure(text: text, image: image, color: color)
230 | }
231 |
232 | /**
233 | Creates a node with a circular path.
234 |
235 | - Parameters:
236 | - text: The text of the node.
237 | - image: The image of the node.
238 | - color: The color of the node.
239 | - radius: The radius of the node.
240 | - marginScale: The margin scale of the node.
241 |
242 | - Returns: A new node.
243 | */
244 | public convenience init(text: String? = nil, image: UIImage? = nil, color: UIColor, radius: CGFloat, marginScale: CGFloat = 1.01) {
245 | let path = SKShapeNode(circleOfRadius: radius).path!
246 | self.init(text: text, image: image, color: color, path: path, marginScale: marginScale)
247 | }
248 |
249 | required public init?(coder aDecoder: NSCoder) {
250 | fatalError("init(coder:) has not been implemented")
251 | }
252 |
253 | open func configure(text: String?, image: UIImage?, color: UIColor) {
254 | self.text = text
255 | self.image = image
256 | self.color = color
257 | }
258 |
259 | override open func removeFromParent() {
260 | removedAnimation() {
261 | super.removeFromParent()
262 | }
263 | }
264 |
265 | /**
266 | Resizes the node to fit its current content
267 | */
268 | public func resize() {
269 | guard scaleToFitContent, let text = text, let font = UIFont(name: fontName, size: fontSize) else { return }
270 | let fontAttributes = [NSAttributedString.Key.font: font]
271 | let size = (text as NSString).size(withAttributes: fontAttributes)
272 | let radius = size.width / 2 + CGFloat(padding)
273 | update(radius: radius, withLabelWidth: size.width)
274 | }
275 |
276 | /**
277 | Updates the radius of the node and sets the label width to a given width or the radius
278 |
279 | - Parameters:
280 | - radius: The new radius
281 | - withLabelWidth: A custom width for the text label
282 | */
283 | public func update(radius: CGFloat, withLabelWidth width: CGFloat? = nil) {
284 | guard let path = SKShapeNode(circleOfRadius: radius).path else { return }
285 | self.path = path
286 | self.label.width = width ?? radius
287 | self.radius = radius
288 | regeneratePhysicsBody(withPath: path)
289 | }
290 |
291 | /**
292 | Regenerates the physics body with a given path after the path has changed .i.e. after resize
293 | */
294 | public func regeneratePhysicsBody(withPath path: CGPath) {
295 | self.physicsBody = {
296 | var transform = CGAffineTransform.identity.scaledBy(x: marginScale, y: marginScale)
297 | let body = SKPhysicsBody(polygonFrom: path.copy(using: &transform)!)
298 | body.allowsRotation = false
299 | body.friction = 0
300 | body.linearDamping = 3
301 | return body
302 | }()
303 | }
304 |
305 | /**
306 | The animation to execute when the node is selected.
307 | */
308 | open func selectedAnimation() {
309 | self.originalFontColor = fontColor
310 | self.originalColor = fillColor
311 |
312 | let scaleAction = SKAction.scale(to: selectedScale, duration: animationDuration)
313 |
314 | if let selectedFontColor = selectedFontColor {
315 | label.run(.colorTransition(from: originalFontColor, to: selectedFontColor))
316 | }
317 |
318 | if let selectedColor = selectedColor {
319 | run(.group([
320 | scaleAction,
321 | .colorTransition(from: originalColor, to: selectedColor, duration: animationDuration)
322 | ]))
323 | } else {
324 | run(scaleAction)
325 | }
326 |
327 | if let texture = texture {
328 | fillTexture = texture
329 | }
330 | }
331 |
332 | /**
333 | The animation to execute when the node is deselected.
334 | */
335 | open func deselectedAnimation() {
336 | let scaleAction = SKAction.scale(to: deselectedScale, duration: animationDuration)
337 |
338 | if let selectedColor = selectedColor {
339 | run(.group([
340 | scaleAction,
341 | .colorTransition(from: selectedColor, to: originalColor, duration: animationDuration)
342 | ]))
343 | } else {
344 | run(scaleAction)
345 | }
346 |
347 | if let selectedFontColor = selectedFontColor {
348 | label.run(.colorTransition(from: selectedFontColor, to: originalFontColor, duration: animationDuration))
349 | }
350 |
351 | self.fillTexture = nil
352 | }
353 |
354 | /**
355 | The animation to execute when the node is removed.
356 |
357 | - important: You must call the completion block.
358 |
359 | - parameter completion: The block to execute when the animation is complete. You must call this handler and should do so as soon as possible.
360 | */
361 | open func removedAnimation(completion: @escaping () -> Void) {
362 | run(.group([.fadeOut(withDuration: animationDuration), .scale(to: 0, duration: animationDuration)]), completion: completion)
363 | }
364 |
365 | }
366 |
--------------------------------------------------------------------------------
/ios/Magnetic/SKAction+Color.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKAction+Color.swift
3 | // Magnetic
4 | //
5 | // Created by Jesse Onolememen on 31/03/2020.
6 | // Copyright © 2020 efremidze. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SpriteKit
11 |
12 | func lerp(a : CGFloat, b : CGFloat, fraction : CGFloat) -> CGFloat {
13 | return (b-a) * fraction + a
14 | }
15 |
16 | struct ColorComponents {
17 | var red = CGFloat(0)
18 | var green = CGFloat(0)
19 | var blue = CGFloat(0)
20 | var alpha = CGFloat(0)
21 | }
22 |
23 | extension UIColor {
24 | var components: ColorComponents {
25 | get {
26 | var components = ColorComponents()
27 | getRed(&components.red, green: &components.green, blue: &components.blue, alpha: &components.alpha)
28 | return components
29 | }
30 | }
31 | }
32 |
33 | extension SKAction {
34 | typealias ColorTransitionConfigure = ((_ node: SKNode) -> Void)?
35 |
36 | static func colorTransition(from fromColor: UIColor, to toColor: UIColor, duration: Double = 0.4, configure: ColorTransitionConfigure = nil) -> SKAction {
37 | return SKAction.customAction(withDuration: duration, actionBlock: { (node : SKNode!, elapsedTime : CGFloat) -> Void in
38 | let fraction = CGFloat(elapsedTime / CGFloat(duration))
39 | let startColorComponents = fromColor.components
40 | let endColorComponents = toColor.components
41 | let transColor = UIColor(
42 | red: lerp(a: startColorComponents.red, b: endColorComponents.red, fraction: fraction),
43 | green: lerp(a: startColorComponents.green, b: endColorComponents.green, fraction: fraction),
44 | blue: lerp(a: startColorComponents.blue, b: endColorComponents.blue, fraction: fraction),
45 | alpha: lerp(a: startColorComponents.alpha, b: endColorComponents.alpha, fraction: fraction)
46 | )
47 |
48 | if let configure = configure {
49 | configure(node)
50 | } else {
51 | if let node = node as? SKShapeNode {
52 | node.fillColor = transColor
53 | }
54 |
55 | if let label = node as? SKMultilineLabelNode {
56 | label.fontColor = transColor
57 | }
58 | }
59 | })
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ios/Magnetic/SKMultilineLabelNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SKMultilineLabelNode.swift
3 | // Magnetic
4 | //
5 | // Created by Lasha Efremidze on 5/11/17.
6 | // Copyright © 2017 efremidze. All rights reserved.
7 | //
8 |
9 | import SpriteKit
10 |
11 | @objcMembers open class SKMultilineLabelNode: SKNode {
12 |
13 | open var text: String? { didSet { update() } }
14 |
15 | open var fontName: String? { didSet { update() } }
16 | open var fontSize: CGFloat = 32 { didSet { update() } }
17 | open var fontColor: UIColor? { didSet { update() } }
18 |
19 | open var separator: String? { didSet { update() } }
20 |
21 | open var verticalAlignmentMode: SKLabelVerticalAlignmentMode = .baseline { didSet { update() } }
22 | open var horizontalAlignmentMode: SKLabelHorizontalAlignmentMode = .center { didSet { update() } }
23 |
24 | open var lineHeight: CGFloat? { didSet { update() } }
25 |
26 | open var width: CGFloat! { didSet { update() } }
27 |
28 | func update() {
29 | self.removeAllChildren()
30 |
31 | guard let text = text else { return }
32 |
33 | var stack = Stack()
34 | var sizingLabel = makeSizingLabel()
35 | let words = separator.map { text.components(separatedBy: $0) } ?? text.map { String($0) }
36 | for (index, word) in words.enumerated() {
37 | sizingLabel.text += word
38 | if sizingLabel.frame.width > width, index > 0 {
39 | stack.add(toStack: word)
40 | sizingLabel = makeSizingLabel()
41 | } else {
42 | stack.add(toCurrent: word)
43 | }
44 | }
45 |
46 | let lines = stack.values.map { $0.joined(separator: separator ?? "") }
47 | for (index, line) in lines.enumerated() {
48 | let label = SKLabelNode(fontNamed: fontName)
49 | label.text = line
50 | label.fontSize = fontSize
51 | label.fontColor = fontColor
52 | label.verticalAlignmentMode = verticalAlignmentMode
53 | label.horizontalAlignmentMode = horizontalAlignmentMode
54 | let y = (CGFloat(index) - (CGFloat(lines.count) / 2) + 0.5) * -(lineHeight ?? fontSize)
55 | label.position = CGPoint(x: 0, y: y)
56 | self.addChild(label)
57 | }
58 | }
59 |
60 | private func makeSizingLabel() -> SKLabelNode {
61 | let label = SKLabelNode(fontNamed: fontName)
62 | label.fontSize = fontSize
63 | return label
64 | }
65 |
66 | }
67 |
68 | private struct Stack {
69 | typealias T = (stack: [[U]], current: [U])
70 | private var value: T
71 | var values: [[U]] {
72 | return value.stack + [value.current]
73 | }
74 | init() {
75 | self.value = (stack: [], current: [])
76 | }
77 | mutating func add(toStack element: U) {
78 | self.value = (stack: value.stack + [value.current], current: [element])
79 | }
80 | mutating func add(toCurrent element: U) {
81 | self.value = (stack: value.stack, current: value.current + [element])
82 | }
83 | }
84 |
85 | private func +=(lhs: inout String?, rhs: String) {
86 | lhs = (lhs ?? "") + rhs
87 | }
88 |
--------------------------------------------------------------------------------
/ios/MagneticViewExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MagneticViewExtension.swift
3 | // react-native-bubble-select
4 | //
5 | // Created by Jesse Onolememen on 29/03/2020.
6 | //
7 |
8 | import Foundation
9 |
10 | extension MagneticView {
11 |
12 | override public func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
13 | guard let subview = subview as? RNBubbleSelectNodeView else { return }
14 | magnetic.addChild(subview.node)
15 | }
16 |
17 | public override func removeReactSubview(_ subview: UIView!) {
18 | guard let subview = subview as? RNBubbleSelectNodeView else { return }
19 | subview.node.removeFromParent()
20 | }
21 |
22 |
23 | // Stub functions to make sure RN works
24 | @objc func setOnSelect(_ onSelectNode: RCTDirectEventBlock?) {
25 | }
26 |
27 | @objc func setOnDeselectNode(_ onDeselectNode: RCTDirectEventBlock?) {
28 | }
29 |
30 | @objc func setAllowsMultipleSelection(_ allowsMultipleSelection: Bool) {
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/ios/RNBubbleMagneticView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RNBubbleMagneticView.swift
3 | // react-native-bubble-select
4 | //
5 | // Created by Jesse Onolememen on 29/03/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class RNBubbleMagneticView: UIView {
12 | var magnetic: Magnetic!
13 |
14 | var allowsMultipleSelection: Bool = true {
15 | didSet {
16 | magnetic.allowsMultipleSelection = allowsMultipleSelection
17 | }
18 | }
19 |
20 | var removeNodeOnLongPress: Bool = false {
21 | didSet {
22 | magnetic.removeNodeOnLongPress = removeNodeOnLongPress
23 | }
24 | }
25 |
26 | var longPressDuration: TimeInterval? {
27 | didSet {
28 | guard let duration = longPressDuration else { return }
29 | magnetic.longPressDuration = duration
30 | }
31 | }
32 |
33 | var magneticBackgroundColor: UIColor = .white {
34 | didSet {
35 | magneticView.backgroundColor = magneticBackgroundColor
36 | magnetic.backgroundColor = magneticBackgroundColor
37 | }
38 | }
39 |
40 | var initialSelection: [String] = [] {
41 | didSet {
42 | magnetic.nodes.filter {
43 | self.initialSelection.contains($0.id)
44 | }.forEach { $0.isSelected = true }
45 | }
46 | }
47 |
48 | var onSelect: RCTDirectEventBlock?
49 | var onDeselect: RCTDirectEventBlock?
50 | var onRemove: RCTDirectEventBlock?
51 |
52 | lazy var magneticView: MagneticView = {
53 | let magneticView = MagneticView()
54 | magnetic = magneticView.magnetic
55 | magnetic.magneticDelegate = self
56 | // magnetic.scene?.view?.showsFPS = true
57 | // magnetic.scene?.view?.showsPhysics = true
58 | // magnetic.scene?.view?.showsNodeCount = true
59 | magneticView.frame = frame
60 | magneticView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
61 | return magneticView
62 | }()
63 |
64 | override init(frame: CGRect) {
65 | super.init(frame: frame)
66 | setupView()
67 | }
68 |
69 | required init?(coder: NSCoder) {
70 | super.init(coder: coder)
71 | setupView()
72 | }
73 |
74 | private func setupView() {
75 | addSubview(magneticView)
76 | magnetic.calculateAccumulatedFrame()
77 | }
78 |
79 | override public func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
80 | guard let subview = subview as? RNBubbleSelectNodeView else { return }
81 | subview.updateNode()
82 | magnetic.addChild(subview.node)
83 | }
84 |
85 | public override func removeReactSubview(_ subview: UIView!) {
86 | guard let subview = subview as? RNBubbleSelectNodeView else { return }
87 | subview.node.removeFromParent()
88 | }
89 | }
90 |
91 | // MARK: - Setters
92 | extension RNBubbleMagneticView {
93 | @objc func setAllowsMultipleSelection(_ allowsMultipleSelection: Bool) {
94 | self.allowsMultipleSelection = allowsMultipleSelection
95 | }
96 |
97 | // Stub functions to make sure RN works
98 | @objc func setOnSelect(_ onSelect: RCTDirectEventBlock?) {
99 | self.onSelect = onSelect
100 | }
101 |
102 | @objc func setOnDeselect(_ onDeselect: RCTDirectEventBlock?) {
103 | self.onDeselect = onDeselect
104 | }
105 |
106 | @objc func setOnRemove(_ onRemove: RCTDirectEventBlock?) {
107 | self.onRemove = onRemove
108 | }
109 |
110 | @objc func setLongPressDuration(_ longPressDuration: CGFloat) {
111 | self.longPressDuration = TimeInterval(longPressDuration)
112 | }
113 |
114 | @objc func setRemoveNodeOnLongPress(_ removeNodeOnLongPress: Bool) {
115 | self.removeNodeOnLongPress = removeNodeOnLongPress
116 | }
117 |
118 | @objc func setMagneticBackgroundColor(_ magneticBackgroundColor: UIColor?) {
119 | self.magneticBackgroundColor = magneticBackgroundColor ?? .white
120 | }
121 |
122 | @objc func setInitialSelection(_ initialSelection: [String]) {
123 | self.initialSelection = initialSelection ?? []
124 | }
125 | }
126 |
127 | extension RNBubbleMagneticView: MagneticDelegate {
128 | func magnetic(_ magnetic: Magnetic, didSelect node: Node) {
129 | onSelect?([
130 | "text": node.text ?? "",
131 | "id": node.id ?? ""
132 | ])
133 | }
134 |
135 | func magnetic(_ magnetic: Magnetic, didDeselect node: Node) {
136 | onDeselect?([
137 | "text": node.text ?? "",
138 | "id": node.id ?? ""
139 | ])
140 | }
141 |
142 | func magnetic(_ magnetic: Magnetic, didRemove node: Node) {
143 | onRemove?([
144 | "text": node.text ?? "",
145 | "id": node.id ?? ""
146 | ])
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/ios/RNBubbleSelectNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MagneticNode.swift
3 | // BubbleSelect
4 | //
5 | // Created by Jesse Onolememen on 29/03/2020.
6 | // Copyright © 2020 Facebook. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import SpriteKit
12 |
13 | class RNBubbleSelectNodeView: UIView {
14 | var id: String?
15 | var text: String?
16 | var image: UIImage?
17 | var color: UIColor?
18 | var radius: CGFloat?
19 | var marginScale: CGFloat?
20 | var padding: CGFloat?
21 |
22 | // Label Styling
23 | var fontName: String?
24 | var fontSize: CGFloat?
25 | var fontColor: UIColor?
26 | var lineHeight: CGFloat?
27 | var borderColor: UIColor?
28 | var borderWidth: CGFloat?
29 |
30 | var selectedScale: CGFloat?
31 | var deselectedScale: CGFloat?
32 | var animationDuration: CGFloat?
33 | var selectedColor: UIColor?
34 | var selectedFontColor: UIColor?
35 | var scaleToFitContent: Bool = true
36 |
37 | lazy var node: Node = {
38 | let node = Node(
39 | text: text,
40 | image: image,
41 | color: color ?? .black,
42 | radius: radius ?? 30
43 | )
44 | return node
45 | }()
46 |
47 | override func didUpdateReactSubviews() {
48 | updateNode()
49 | }
50 |
51 | func updateNode() {
52 | node.id = id
53 | node.fontName = fontName ?? Node.Defaults.fontName
54 | node.fontSize = fontSize ?? Node.Defaults.fontSize
55 | node.fontColor = fontColor ?? Node.Defaults.fontColor
56 | node.label.lineHeight = lineHeight
57 | node.borderColor = borderColor ?? Node.Defaults.borderColor
58 | node.borderWidth = borderWidth ?? Node.Defaults.borderWidth
59 | node.color = color ?? Node.Defaults.color
60 | node.text = text
61 | node.padding = padding ?? Node.Defaults.padding
62 | node.scaleToFitContent = scaleToFitContent
63 |
64 | if let selectedScale = selectedScale {
65 | node.selectedScale = selectedScale
66 | }
67 |
68 | if let deselectedScale = deselectedScale {
69 | node.deselectedScale = deselectedScale
70 | }
71 |
72 | if let animationDuration = animationDuration {
73 | node.animationDuration = TimeInterval(animationDuration)
74 | }
75 |
76 | if let selectedColor = selectedColor {
77 | node.selectedColor = selectedColor
78 | }
79 |
80 | if let selectedFontColor = selectedFontColor {
81 | node.selectedFontColor = selectedFontColor
82 | }
83 |
84 | if let radius = radius {
85 | node.update(radius: radius)
86 | }
87 | }
88 | }
89 |
90 | // MARK:- Setters
91 | extension RNBubbleSelectNodeView {
92 | @objc func setId(_ id: String?) {
93 | self.id = id
94 | }
95 |
96 | @objc func setText(_ text: String?) {
97 | self.text = text
98 | updateNode()
99 | }
100 |
101 | @objc func setImage(_ image: UIImage?) {
102 | self.image = image
103 | updateNode()
104 | }
105 |
106 | @objc func setColor(_ color: UIColor?) {
107 | self.color = color
108 | updateNode()
109 | }
110 |
111 | @objc func setRadius(_ radius: CGFloat) {
112 | self.radius = radius
113 | updateNode()
114 | }
115 |
116 | @objc func setMarginScale(_ marginScale: CGFloat) {
117 | self.marginScale = marginScale
118 | }
119 |
120 | // Label Styling
121 | @objc func setFontSize(_ fontSize: CGFloat) {
122 | self.fontSize = fontSize
123 | updateNode()
124 | }
125 |
126 | @objc func setFontName(_ fontName: String?) {
127 | self.fontName = fontName
128 | updateNode()
129 | }
130 |
131 | @objc func setFontColor(_ fontColor: UIColor?) {
132 | self.fontColor = fontColor
133 | updateNode()
134 | }
135 |
136 | @objc func setLineHeight(_ lineHeight: CGFloat) {
137 | self.lineHeight = lineHeight
138 | updateNode()
139 | }
140 |
141 | @objc func setBorderColor(_ color: UIColor?) {
142 | self.borderColor = color
143 | updateNode()
144 | }
145 |
146 | @objc func setBorderWidth(_ width: CGFloat) {
147 | self.borderWidth = width
148 | updateNode()
149 | }
150 |
151 | @objc func setPadding(_ padding: CGFloat) {
152 | self.padding = padding
153 | updateNode()
154 | }
155 |
156 | @objc func setSelectedScale(_ selectedScale: CGFloat) {
157 | self.selectedScale = selectedScale
158 | updateNode()
159 | }
160 |
161 | @objc func setSelectedColor(_ selectedColor: UIColor?) {
162 | self.selectedColor = selectedColor
163 | updateNode()
164 | }
165 |
166 | @objc func setDeselectedScale(_ deselectedScale: CGFloat) {
167 | self.deselectedScale = deselectedScale
168 | updateNode()
169 | }
170 |
171 | @objc func setAnimationDuration(_ animationDuration: CGFloat) {
172 | self.animationDuration = animationDuration
173 | updateNode()
174 | }
175 |
176 | @objc func setSelectedFontColor(_ fontColor: UIColor?) {
177 | self.selectedFontColor = fontColor
178 | updateNode()
179 | }
180 |
181 | @objc func setAutoSize(_ autoSize: Bool) {
182 | self.scaleToFitContent = autoSize
183 | updateNode()
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/ios/RNBubbleSelectNodeViewManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // RNBubbleSelectNodeViewManager.m
3 | // react-native-bubble-select
4 | //
5 | // Created by Jesse Onolememen on 29/03/2020.
6 | //
7 |
8 | #import
9 | #import
10 |
11 | @interface RCT_EXTERN_MODULE(RNBubbleSelectNodeViewManager, RCTViewManager)
12 |
13 | RCT_EXPORT_VIEW_PROPERTY(id, NSString)
14 | RCT_EXPORT_VIEW_PROPERTY(text, NSString)
15 | RCT_EXPORT_VIEW_PROPERTY(image, UIImage)
16 | RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
17 | RCT_EXPORT_VIEW_PROPERTY(radius, CGFloat)
18 | RCT_EXPORT_VIEW_PROPERTY(marginScale, CGFloat)
19 |
20 | RCT_EXPORT_VIEW_PROPERTY(fontSize, CGFloat)
21 | RCT_EXPORT_VIEW_PROPERTY(fontColor, UIColor)
22 | RCT_EXPORT_VIEW_PROPERTY(fontName, NSString)
23 | RCT_EXPORT_VIEW_PROPERTY(lineHeight, CGFloat)
24 |
25 | RCT_EXPORT_VIEW_PROPERTY(borderColor, UIColor)
26 | RCT_EXPORT_VIEW_PROPERTY(borderWidth, CGFloat)
27 | RCT_EXPORT_VIEW_PROPERTY(padding, CGFloat)
28 | RCT_EXPORT_VIEW_PROPERTY(selectedScale, CGFloat)
29 | RCT_EXPORT_VIEW_PROPERTY(deselectedScale, CGFloat)
30 | RCT_EXPORT_VIEW_PROPERTY(animationDuration, CGFloat)
31 | RCT_EXPORT_VIEW_PROPERTY(selectedColor, UIColor)
32 | RCT_EXPORT_VIEW_PROPERTY(selectedFontColor, UIColor)
33 | RCT_EXPORT_VIEW_PROPERTY(autoSize, BOOL)
34 |
35 | + (BOOL)requiresMainQueueSetup
36 | {
37 | return NO;
38 | }
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/ios/RNBubbleSelectNodeViewManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MagneticNodeViewManager.swift
3 | // react-native-bubble-select
4 | //
5 | // Created by Jesse Onolememen on 29/03/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | @objc(RNBubbleSelectNodeViewManager)
12 | class RNBubbleSelectNodeViewManager: RCTViewManager {
13 |
14 | var id: String?
15 | var text: String?
16 | var image: UIImage?
17 | var color: UIColor?
18 | var radius: CGFloat?
19 | var marginScale: CGFloat?
20 |
21 | // Label Styling
22 | var fontName: String?
23 | var fontSize: CGFloat?
24 | var fontColor: UIColor?
25 | var lineHeight: CGFloat?
26 |
27 | override func view() -> UIView! {
28 | let node = RNBubbleSelectNodeView()
29 | node.id = id
30 | node.text = text
31 | node.image = image
32 | node.color = color
33 | node.radius = radius
34 | node.marginScale = marginScale
35 | node.fontName = fontName
36 | node.fontSize = fontSize
37 | node.fontColor = fontColor ?? .white
38 | node.lineHeight = lineHeight ?? 1.1
39 | node.updateNode()
40 | return node
41 | }
42 | }
43 |
44 | // MARK:- Setters
45 | extension RNBubbleSelectNodeViewManager {
46 | @objc func setId(_ id: String?) {
47 | self.id = id
48 | }
49 |
50 | @objc func setText(_ text: String?) {
51 | self.text = text
52 | }
53 |
54 | @objc func setImage(_ image: UIImage?) {
55 | self.image = image
56 | }
57 |
58 | @objc func setColor(_ color: UIColor?) {
59 | self.color = color
60 | }
61 |
62 | @objc func setRadius(_ radius: CGFloat) {
63 | self.radius = radius
64 | }
65 |
66 | @objc func setMarginScale(_ marginScale: CGFloat) {
67 | self.marginScale = marginScale
68 | }
69 |
70 | @objc func setFontSize(_ fontSize: CGFloat) {
71 | self.fontSize = fontSize
72 | }
73 |
74 | @objc func setFontName(_ fontName: String?) {
75 | self.fontName = fontName
76 | }
77 |
78 | @objc func setFontColor(_ fontColor: UIColor?) {
79 | self.fontColor = fontColor
80 | }
81 |
82 | @objc func setLineHeight(_ lineHeight: CGFloat) {
83 | self.lineHeight = lineHeight
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/ios/RNBubbleSelectViewManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // RNBubbleSelectViewManager.m
3 | // react-native-bubble-select
4 | //
5 | // Created by Jesse Onolememen on 29/03/2020.
6 | //
7 |
8 | #import
9 | #import
10 | #import
11 |
12 | @interface RCT_EXTERN_MODULE(RNBubbleSelectViewManager, RCTViewManager)
13 |
14 | RCT_EXPORT_VIEW_PROPERTY(onSelect, RCTDirectEventBlock)
15 | RCT_EXPORT_VIEW_PROPERTY(onDeselect, RCTDirectEventBlock)
16 | RCT_EXPORT_VIEW_PROPERTY(onRemove, RCTDirectEventBlock)
17 | RCT_EXPORT_VIEW_PROPERTY(allowsMultipleSelection, BOOL)
18 | RCT_EXPORT_VIEW_PROPERTY(longPressDuration, CGFloat)
19 | RCT_EXPORT_VIEW_PROPERTY(removeNodeOnLongPress, BOOL)
20 | RCT_EXPORT_VIEW_PROPERTY(magneticBackgroundColor, UIColor)
21 | RCT_EXPORT_VIEW_PROPERTY(initialSelection, NSArray*)
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/ios/RNBubbleSelectViewManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RNBubbleSelectViewManager.swift
3 | // react-native-bubble-select
4 | //
5 | // Created by Jesse Onolememen on 29/03/2020.
6 | //
7 |
8 | import Foundation
9 |
10 | @objc(RNBubbleSelectViewManager)
11 | class RNBubbleSelectViewManager: RCTViewManager {
12 | override func view() -> UIView! {
13 | return RNBubbleMagneticView()
14 | }
15 |
16 | override class func requiresMainQueueSetup() -> Bool {
17 | return false
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-bubble-select",
3 | "version": "0.6.0",
4 | "description": "An easy-to-use customizable bubble animation picker, similar to the Apple Music genre selection",
5 | "main": "lib/commonjs/index.js",
6 | "module": "lib/module/index.js",
7 | "types": "lib/typescript/src/index.d.ts",
8 | "react-native": "src/index.ts",
9 | "files": [
10 | "src",
11 | "lib",
12 | "android",
13 | "ios",
14 | "react-native-bubble-select.podspec"
15 | ],
16 | "scripts": {
17 | "test": "jest",
18 | "typescript": "tsc --noEmit",
19 | "lint": "eslint --ext .js,.ts,.tsx .",
20 | "prepare": "bob build",
21 | "release": "release-it",
22 | "example": "yarn --cwd example",
23 | "pods": "cd example/ios && node -e \"process.exit(require('os').platform() === 'darwin')\" || pod install",
24 | "bootstrap": "yarn example && yarn && yarn pods"
25 | },
26 | "keywords": [
27 | "react-native",
28 | "ios",
29 | "android"
30 | ],
31 | "repository": "https://github.com/jesster2k10/react-native-bubble-select",
32 | "author": "Jesse Onolememen (https://github.com/jesster2k10)",
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/jesster2k10/react-native-bubble-select/issues"
36 | },
37 | "homepage": "https://github.com/jesster2k10/react-native-bubble-select#readme",
38 | "devDependencies": {
39 | "@commitlint/config-conventional": "^8.3.4",
40 | "@react-native-community/bob": "^0.10.0",
41 | "@react-native-community/eslint-config": "^0.0.7",
42 | "@release-it/conventional-changelog": "^1.1.0",
43 | "@types/jest": "^25.1.2",
44 | "@types/react": "^16.9.19",
45 | "@types/react-native": "0.61.10",
46 | "commitlint": "^8.3.4",
47 | "eslint": "^6.8.0",
48 | "eslint-config-prettier": "^6.10.0",
49 | "eslint-plugin-prettier": "^3.1.2",
50 | "husky": "^4.0.1",
51 | "jest": "^25.1.0",
52 | "prettier": "^1.19.1",
53 | "react": "~16.9.0",
54 | "react-native": "~0.61.5",
55 | "release-it": "^12.6.3",
56 | "typescript": "^3.7.5"
57 | },
58 | "peerDependencies": {
59 | "react": "*",
60 | "react-native": "*"
61 | },
62 | "jest": {
63 | "preset": "react-native",
64 | "modulePathIgnorePatterns": [
65 | "/example/node_modules",
66 | "/lib/"
67 | ]
68 | },
69 | "husky": {
70 | "hooks": {
71 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
72 | "pre-commit": "yarn lint && yarn typescript"
73 | }
74 | },
75 | "eslintConfig": {
76 | "extends": [
77 | "@react-native-community",
78 | "prettier"
79 | ],
80 | "rules": {
81 | "prettier/prettier": [
82 | "error",
83 | {
84 | "singleQuote": true,
85 | "tabWidth": 2,
86 | "trailingComma": "es5",
87 | "useTabs": false
88 | }
89 | ]
90 | }
91 | },
92 | "eslintIgnore": [
93 | "node_modules/",
94 | "lib/"
95 | ],
96 | "prettier": {
97 | "singleQuote": true,
98 | "tabWidth": 2,
99 | "trailingComma": "es5",
100 | "useTabs": false
101 | },
102 | "release-it": {
103 | "git": {
104 | "commitMessage": "chore: release %s",
105 | "tagName": "v%s"
106 | },
107 | "npm": {
108 | "publish": true
109 | },
110 | "github": {
111 | "release": true
112 | },
113 | "plugins": {
114 | "@release-it/conventional-changelog": {
115 | "preset": "angular"
116 | }
117 | }
118 | },
119 | "@react-native-community/bob": {
120 | "source": "src",
121 | "output": "lib",
122 | "targets": [
123 | "commonjs",
124 | "module",
125 | "typescript"
126 | ]
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/react-native-bubble-select.podspec:
--------------------------------------------------------------------------------
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 = "react-native-bubble-select"
7 | s.version = package["version"]
8 | s.summary = package["description"]
9 | s.homepage = package["homepage"]
10 | s.license = package["license"]
11 | s.authors = package["author"]
12 |
13 | s.platforms = { :ios => "9.0" }
14 | s.source = { :git => "https://github.com/jesster2k10/react-native-bubble-select.git", :tag => "#{s.version}" }
15 |
16 | s.source_files = "ios/**/*.{h,m,swift}"
17 | s.swift_version = "5.0"
18 |
19 | s.dependency "React"
20 | end
21 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/screenshot.png
--------------------------------------------------------------------------------
/src/Bubble.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { requireNativeComponent, Platform } from 'react-native';
3 |
4 | const RNBubble = requireNativeComponent('RNBubbleSelectNodeView');
5 |
6 | export interface BubbleNode {
7 | text: string;
8 | id: string;
9 | }
10 |
11 | export type BubbleProps = BubbleNode & {
12 | color?: string;
13 | radius?: number;
14 | marginScale?: number;
15 | fontName?: string;
16 | fontSize?: number;
17 | fontColor?: string;
18 | fontStyle?: 'bold' | 'bold-italic' | 'normal';
19 | lineHeight?: number;
20 | borderColor?: string;
21 | borderWidth?: number;
22 | padding?: number;
23 | selectedScale?: number;
24 | deselectedScale?: number;
25 | animationDuration?: number;
26 | selectedColor?: string;
27 | selectedFontColor?: string;
28 | autoSize?: boolean;
29 | gradient?: {
30 | startColor: string;
31 | endColor: string;
32 | direction: 'horizontal' | 'vertical';
33 | };
34 | };
35 |
36 | const Bubble = ({
37 | text,
38 | color,
39 | radius,
40 | marginScale,
41 | id,
42 | fontName,
43 | fontSize,
44 | fontColor,
45 | lineHeight,
46 | fontStyle,
47 | padding,
48 | borderColor,
49 | borderWidth,
50 | selectedScale,
51 | deselectedScale,
52 | selectedColor,
53 | animationDuration,
54 | selectedFontColor,
55 | autoSize,
56 | gradient,
57 | }: BubbleProps) => {
58 | const props = Platform.select({
59 | ios: {
60 | text,
61 | color,
62 | radius,
63 | marginScale,
64 | id,
65 | fontName,
66 | fontSize,
67 | fontColor,
68 | lineHeight,
69 | padding,
70 | borderColor,
71 | borderWidth,
72 | selectedScale,
73 | deselectedScale,
74 | animationDuration,
75 | selectedColor,
76 | selectedFontColor,
77 | autoSize,
78 | },
79 | android: {
80 | text,
81 | color,
82 | id,
83 | fontFamily: fontName,
84 | fontSize,
85 | fontColor,
86 | fontStyle,
87 | gradient,
88 | },
89 | });
90 |
91 | return ;
92 | };
93 |
94 | export default Bubble;
95 |
--------------------------------------------------------------------------------
/src/BubbleSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BubbleNode, BubbleProps } from './Bubble';
3 | import {
4 | NativeSyntheticEvent,
5 | requireNativeComponent,
6 | Platform,
7 | } from 'react-native';
8 |
9 | const RNBubbleSelect = requireNativeComponent('RNBubbleSelectView');
10 |
11 | export type BubbleSelectProps = Omit & {
12 | onSelect?: (bubble: BubbleNode) => void;
13 | onDeselect?: (bubble: BubbleNode) => void;
14 | onRemove?: (bubble: BubbleNode) => void;
15 | bubbleSize?: number;
16 | allowsMultipleSelection?: boolean;
17 | children: React.ReactNode;
18 | style?: object;
19 | width?: number;
20 | height?: number;
21 | removeOnLongPress?: boolean;
22 | longPressDuration?: number;
23 | backgroundColor?: string;
24 | maxSelectedItems?: number;
25 | initialSelection?: string[];
26 | };
27 |
28 | const BubbleSelect = ({
29 | onSelect,
30 | onDeselect,
31 | style,
32 | allowsMultipleSelection = true,
33 | children,
34 | bubbleSize,
35 | onRemove,
36 | removeOnLongPress = true,
37 | longPressDuration,
38 | width = 200,
39 | height = 200,
40 | backgroundColor,
41 | maxSelectedItems,
42 | initialSelection = [],
43 | ...bubbleProps
44 | }: BubbleSelectProps) => {
45 | const defaultStyle = {
46 | flex: 1,
47 | width,
48 | height,
49 | };
50 |
51 | const handleSelect = (event: NativeSyntheticEvent) => {
52 | if (onSelect) {
53 | onSelect(event.nativeEvent);
54 | }
55 | };
56 |
57 | const handleDeselect = (event: NativeSyntheticEvent) => {
58 | if (onDeselect) {
59 | onDeselect(event.nativeEvent);
60 | }
61 | };
62 |
63 | const handleRemove = (event: NativeSyntheticEvent) => {
64 | if (onRemove) {
65 | onRemove(event.nativeEvent);
66 | }
67 | };
68 |
69 | const platformProps = Platform.select({
70 | android: {
71 | onSelectNode: handleSelect,
72 | onDeselectNode: handleDeselect,
73 | onRemoveNode: onRemove,
74 | bubbleSize,
75 | backgroundColor,
76 | maxSelectedItems,
77 | },
78 | ios: {
79 | initialSelection,
80 | },
81 | default: {},
82 | });
83 |
84 | return (
85 |
96 | {React.Children.map(children, (child: any) =>
97 | React.cloneElement(child, bubbleProps)
98 | )}
99 |
100 | );
101 | };
102 |
103 | export default BubbleSelect;
104 |
--------------------------------------------------------------------------------
/src/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | it.todo('write a test');
2 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import BubbleSelect from './BubbleSelect';
2 |
3 | export * from './Bubble';
4 | export * from './BubbleSelect';
5 |
6 | export { default as Bubble } from './Bubble';
7 | export default BubbleSelect;
8 |
--------------------------------------------------------------------------------
/swift-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesster2k10/react-native-bubble-select/5e6b091e3ed7f243d87ded8b85880a7dbf68de89/swift-error.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "react-native-bubble-select": ["./src/index"]
6 | },
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "jsx": "react",
12 | "lib": ["esnext"],
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "noFallthroughCasesInSwitch": true,
16 | "noImplicitReturns": true,
17 | "noImplicitUseStrict": false,
18 | "noStrictGenericChecks": false,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "resolveJsonModule": true,
22 | "skipLibCheck": true,
23 | "strict": true,
24 | "target": "esnext"
25 | },
26 | "exclude": ["example/node_modules/**", "node_modules/**"]
27 | }
28 |
--------------------------------------------------------------------------------