├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── canopas
│ │ │ │ └── campose
│ │ │ │ └── jetcountypicker
│ │ │ │ ├── ui
│ │ │ │ └── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Type.kt
│ │ │ │ │ └── Theme.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── canopas
│ │ │ └── campose
│ │ │ └── jetcountypicker
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── canopas
│ │ └── campose
│ │ └── jetcountypicker
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── countrypicker
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── canopas
│ │ │ │ └── campose
│ │ │ │ └── countrypicker
│ │ │ │ ├── model
│ │ │ │ ├── Country.kt
│ │ │ │ └── PickerType.kt
│ │ │ │ ├── CountyUtils.kt
│ │ │ │ ├── CountryPickerDialog.kt
│ │ │ │ ├── CountryPickerTextField.kt
│ │ │ │ ├── CountrySearchView.kt
│ │ │ │ ├── CountryPickerView.kt
│ │ │ │ └── CountryPickerBottomSheet.kt
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ └── assets
│ │ │ └── Countries.json
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── canopas
│ │ │ └── campose
│ │ │ └── countrypicker
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── canopas
│ │ └── campose
│ │ └── countrypicker
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── gif
└── Peek 2022-04-11 11-46.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── .gitignore
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── vcs.xml
└── gradle.xml
├── .gitignore
├── .github
└── workflows
│ ├── app-build.yml
│ └── publish.yml
├── settings.gradle
├── License
├── gradle.properties
├── scripts
├── publish-root.gradle
└── publish-module.gradle
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/countrypicker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/countrypicker/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/countrypicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gif/Peek 2022-04-11 11-46.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/gif/Peek 2022-04-11 11-46.gif
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # GitHub Copilot persisted chat sessions
5 | /copilot/chatSessions
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/compose-country-picker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/model/Country.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker.model
2 |
3 | data class Country(val name: String, val dial_code: String, val code: String)
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | JetCountyPicker
3 | Select country
4 | Picker Type
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Aug 11 12:20:35 IST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | /local.properties
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/canopas/campose/jetcountypicker/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.jetcountypicker.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/countrypicker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Search
4 | Dialog
5 | Full Screen Dialog
6 | Bottom Sheet
7 |
--------------------------------------------------------------------------------
/.github/workflows/app-build.yml:
--------------------------------------------------------------------------------
1 | name: Android Build
2 |
3 | on: [ push ]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - name: set up JDK 17
11 | uses: actions/setup-java@v2
12 | with:
13 | distribution: adopt
14 | java-version: 17
15 | - name: Build with Gradle
16 | run: ./gradlew build
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "JetCountyPicker"
16 | include ':app'
17 | include ':countrypicker'
18 |
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/model/PickerType.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker.model
2 |
3 | import com.canopas.campose.countrypicker.R
4 |
5 | /**
6 | * Enum class for the type of picker to display.
7 | */
8 | enum class PickerType(val value: Int) {
9 | DIALOG(R.string.picker_type_dialog),
10 | FULL_SCREEN_DIALOG(R.string.picker_type_full_screen),
11 | BOTTOM_SHEET(R.string.picker_type_bottom_sheet)
12 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/canopas/campose/jetcountypicker/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.jetcountypicker
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/countrypicker/src/test/java/com/canopas/campose/countrypicker/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/License:
--------------------------------------------------------------------------------
1 | Copyright 2022 Canopas Software LLP
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/canopas/campose/jetcountypicker/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.jetcountypicker
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.canopas.campose.jetcountypicker", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/countrypicker/src/androidTest/java/com/canopas/campose/countrypicker/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.canopas.campose.countrypicker.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/countrypicker/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class com.canopas.campose.countrypicker.model.**{ *; }
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | publish:
10 | name: Release build and publish
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Check out code
14 | uses: actions/checkout@v2
15 | - name: Set up JDK 17
16 | uses: actions/setup-java@v2
17 | with:
18 | distribution: adopt
19 | java-version: 17
20 |
21 | - name: Publish to MavenCentral
22 | run: ./gradlew countrypicker:publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository
23 | env:
24 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
25 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
26 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
27 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
28 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
29 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/canopas/campose/jetcountypicker/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.jetcountypicker.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/scripts/publish-root.gradle:
--------------------------------------------------------------------------------
1 | // Create variables with empty default values
2 | ext["ossrhUsername"] = ''
3 | ext["ossrhPassword"] = ''
4 | ext["sonatypeStagingProfileId"] = ''
5 | ext["signing.keyId"] = ''
6 | ext["signing.password"] = ''
7 | ext["signing.key"] = ''
8 | ext["snapshot"] = ''
9 |
10 | File secretPropsFile = project.rootProject.file('local.properties')
11 |
12 | if (secretPropsFile.exists()) {
13 |
14 | // Read local.properties file first if it exists
15 |
16 | Properties p = new Properties()
17 |
18 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
19 |
20 | p.each { name, value -> ext[name] = value }
21 |
22 | } else {
23 | // Use system environment variables
24 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
25 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
26 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
27 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
28 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
29 | ext["signing.key"] = System.getenv('SIGNING_KEY')
30 | ext["snapshot"] = System.getenv('SNAPSHOT')
31 | }
32 |
33 | // Set up Sonatype repository
34 | nexusPublishing {
35 | repositories {
36 | sonatype {
37 | stagingProfileId = sonatypeStagingProfileId
38 | username = ossrhUsername
39 | password = ossrhPassword
40 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
41 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/countrypicker/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | ext {
7 | PUBLISH_GROUP_ID = 'com.canopas.jetcountrypicker'
8 | PUBLISH_VERSION = '1.1.1'
9 | PUBLISH_ARTIFACT_ID = 'jetcountrypicker'
10 | }
11 | apply from: "${rootDir}/scripts/publish-module.gradle"
12 |
13 | android {
14 | compileSdk 34
15 | namespace 'com.canopas.campose.countrypicker'
16 |
17 | defaultConfig {
18 | minSdk 21
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | consumerProguardFiles "consumer-rules.pro"
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | consumerProguardFiles 'proguard-rules.pro'
28 | }
29 | }
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_11
32 | targetCompatibility JavaVersion.VERSION_11
33 | }
34 | kotlinOptions {
35 | jvmTarget = '11'
36 | }
37 | buildFeatures {
38 | compose true
39 | }
40 | composeOptions {
41 | kotlinCompilerExtensionVersion compose_compiler_version
42 | }
43 | publishing {
44 | singleVariant("release") {
45 | withSourcesJar()
46 | }
47 | }
48 | }
49 |
50 | dependencies {
51 |
52 | implementation 'androidx.core:core-ktx:1.13.0'
53 | implementation 'androidx.appcompat:appcompat:1.6.1'
54 | implementation 'com.google.android.material:material:1.11.0'
55 | testImplementation 'junit:junit:4.13.2'
56 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
58 |
59 | implementation platform("androidx.compose:compose-bom:$compose_bom_version")
60 | implementation "androidx.compose.ui:ui"
61 | implementation "androidx.compose.material3:material3"
62 | implementation "androidx.compose.ui:ui-tooling-preview"
63 | implementation "androidx.compose.material:material-icons-extended"
64 |
65 | implementation "com.squareup.moshi:moshi-kotlin:1.15.0"
66 | }
67 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdk 34
8 | namespace 'com.canopas.campose.countypickerdemo'
9 |
10 | defaultConfig {
11 | applicationId "com.canopas.campose.countypickerdemo"
12 | minSdk 21
13 |
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_11
32 | targetCompatibility JavaVersion.VERSION_11
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '11'
37 | }
38 |
39 | buildFeatures {
40 | compose true
41 | }
42 |
43 | packagingOptions {
44 | resources {
45 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
46 | }
47 | }
48 | composeOptions {
49 | kotlinCompilerExtensionVersion compose_compiler_version
50 | }
51 | }
52 |
53 | dependencies {
54 | implementation project(":countrypicker")
55 | implementation 'androidx.core:core-ktx:1.13.0'
56 | implementation 'androidx.appcompat:appcompat:1.6.1'
57 | implementation 'com.google.android.material:material:1.11.0'
58 |
59 | implementation platform("androidx.compose:compose-bom:$compose_bom_version")
60 | implementation "androidx.compose.ui:ui"
61 | implementation "androidx.compose.material3:material3"
62 | implementation "androidx.compose.ui:ui-tooling-preview"
63 |
64 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
65 | implementation 'androidx.activity:activity-compose:1.9.0'
66 | testImplementation 'junit:junit:4.13.2'
67 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
68 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
69 | }
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/CountyUtils.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import android.content.Context
4 | import com.canopas.campose.countrypicker.model.Country
5 | import com.squareup.moshi.JsonAdapter
6 | import com.squareup.moshi.Moshi
7 | import com.squareup.moshi.Types
8 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
9 | import java.io.IOException
10 |
11 | /**
12 | * Retrieves a list of countries from a JSON file stored in the assets directory.
13 | *
14 | * @param context The context used to access the assets directory.
15 | * @return A mutable list of countries parsed from the JSON file, or an empty list if parsing fails.
16 | */
17 | fun countryList(context: Context): MutableList {
18 | val moshi =
19 | Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
20 |
21 | val personListType = Types.newParameterizedType(List::class.java, Country::class.java)
22 | val jsonAdapter: JsonAdapter> = moshi.adapter(personListType)
23 |
24 | val jsonFileString = getJsonDataFromAsset(context, "Countries.json")
25 | return jsonFileString?.let { jsonAdapter.fromJson(it) } ?: mutableListOf()
26 | }
27 |
28 | internal fun localeToEmoji(countryCode: String): String {
29 | val firstLetter = Character.codePointAt(countryCode, 0) - 0x41 + 0x1F1E6
30 | val secondLetter = Character.codePointAt(countryCode, 1) - 0x41 + 0x1F1E6
31 | return String(Character.toChars(firstLetter)) + String(Character.toChars(secondLetter))
32 | }
33 |
34 | internal fun getJsonDataFromAsset(context: Context, fileName: String): String? {
35 | val jsonString: String
36 | try {
37 | jsonString = context.assets.open(fileName).bufferedReader().use { it.readText() }
38 | } catch (ioException: IOException) {
39 | ioException.printStackTrace()
40 | return null
41 | }
42 | return jsonString
43 | }
44 |
45 | internal fun List.searchCountryList(countryName: String): MutableList {
46 | val countryList = mutableListOf()
47 | this.forEach {
48 | if (it.name.lowercase().contains(countryName.lowercase()) ||
49 | it.dial_code.contains(countryName.lowercase())
50 | ) {
51 | countryList.add(it)
52 | }
53 | }
54 | return countryList
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/canopas/campose/jetcountypicker/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.jetcountypicker.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.SideEffect
13 | import androidx.compose.ui.graphics.toArgb
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.platform.LocalView
16 | import androidx.core.view.WindowCompat
17 |
18 | private val DarkColorScheme = darkColorScheme().copy(
19 | primary = Purple200,
20 | secondary = Purple700,
21 | tertiary = Teal200
22 | )
23 |
24 | private val LightColorScheme = lightColorScheme().copy(
25 | primary = Purple500,
26 | secondary = Purple700,
27 | tertiary = Teal200
28 |
29 | /* Other default colors to override
30 | background = Color.White,
31 | surface = Color.White,
32 | onPrimary = Color.White,
33 | onSecondary = Color.Black,
34 | onBackground = Color.Black,
35 | onSurface = Color.Black,
36 | */
37 | )
38 |
39 | @Composable
40 | fun JetCountyPickerTheme(
41 | darkTheme: Boolean = isSystemInDarkTheme(),
42 | dynamicColor: Boolean = true,
43 | content: @Composable() () -> Unit
44 | ) {
45 | val colorScheme = when {
46 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
47 | val context = LocalContext.current
48 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
49 | }
50 |
51 | darkTheme -> DarkColorScheme
52 | else -> LightColorScheme
53 | }
54 | val view = LocalView.current
55 | if (!view.isInEditMode) {
56 | SideEffect {
57 | val window = (view.context as Activity).window
58 | window.statusBarColor = colorScheme.primary.toArgb()
59 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
60 | }
61 | }
62 |
63 | MaterialTheme(
64 | colorScheme = colorScheme,
65 | typography = Typography,
66 | content = content
67 | )
68 | }
--------------------------------------------------------------------------------
/scripts/publish-module.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | group = PUBLISH_GROUP_ID
5 | version = PUBLISH_VERSION
6 |
7 | tasks.register('androidSourcesJar', Jar) {
8 | archiveClassifier.set('sources')
9 | if (project.plugins.findPlugin("com.android.library")) {
10 | from android.sourceSets.main.java.srcDirs
11 | } else {
12 | from sourceSets.main.java.srcDirs
13 | }
14 | }
15 |
16 | artifacts {
17 | archives androidSourcesJar
18 | }
19 |
20 | afterEvaluate {
21 | publishing {
22 | publications {
23 | release(MavenPublication) {
24 | tasks.named("generateMetadataFileForReleasePublication").configure { dependsOn("androidSourcesJar") }
25 |
26 | groupId PUBLISH_GROUP_ID
27 | artifactId PUBLISH_ARTIFACT_ID
28 | version PUBLISH_VERSION
29 |
30 | if (project.plugins.findPlugin("com.android.library")) {
31 | from components.release
32 | } else {
33 | artifact("$buildDir/libs/${project.getName()}-${version}.jar")
34 | }
35 |
36 | // artifact androidSourcesJar
37 | // Mostly self-explanatory metadata
38 | pom {
39 | name = PUBLISH_ARTIFACT_ID
40 | description = 'Country code bottomsheet picker in Jetpack Compose'
41 | url = 'https://github.com/canopas/JetCountrypicker'
42 | licenses {
43 | license {
44 | name = 'License'
45 | url = 'https://github.com/canopas/JetCountrypicker/blob/main/License'
46 | }
47 | }
48 | developers {
49 | developer {
50 | id = 'cp-radhika-s'
51 | name = 'Radhika canopas'
52 | email = 'radhika.s@canopas.com'
53 | }
54 | // Add all other devs here...
55 | }
56 |
57 | // Version control info - if you're using GitHub, follow the
58 | // format as seen here
59 | scm {
60 | connection = 'scm:git:github.com/canopas/JetCountrypicker.git'
61 | developerConnection = 'scm:git:ssh://github.com/canopas/JetCountrypicker.git'
62 | url = 'https://github.com/canopas/JetCountrypicker.git'
63 | }
64 | }
65 | }
66 | }
67 | }
68 | }
69 | signing {
70 | useInMemoryPgpKeys(
71 | rootProject.ext["signing.keyId"],
72 | rootProject.ext["signing.key"],
73 | rootProject.ext["signing.password"],
74 | )
75 |
76 | sign publishing.publications
77 | }
--------------------------------------------------------------------------------
/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 https://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 Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JetCountryPicker
2 |
3 | Country code bottom sheet picker in Jetpack Compose with Search functionality.
4 |
5 |
6 |
7 | ## How to add in your project
8 |
9 | Available on [Maven Central](https://repo1.maven.org/maven2/com/canopas/jetcountrypicker/jetcountrypicker/).
10 |
11 | Add the dependency
12 | ```gradle
13 | implementation 'com.canopas.jetcountrypicker:jetcountrypicker:1.0.9'
14 | ```
15 |
16 | ## How to use ?
17 |
18 | ```kotlin
19 | var openBottomSheet by rememberSaveable { mutableStateOf(false) }
20 | var selectedCountry by remember { mutableStateOf(null) }
21 |
22 | Box {
23 | CountryTextField(
24 | label = stringResource(R.string.select_country_text),
25 | modifier = Modifier
26 | .fillMaxWidth()
27 | .padding(top = 50.dp, start = 40.dp, end = 40.dp),
28 | selectedCountry = selectedCountry,
29 | defaultCountry = countryList(LocalContext.current).firstOrNull { it.code == "IN" },
30 | onShowCountryPicker = {
31 | openBottomSheet = true
32 | }, isPickerVisible = openBottomSheet
33 | )
34 | }
35 |
36 | if (openBottomSheet) {
37 | CountryPickerBottomSheet(
38 | bottomSheetTitle = {
39 | Text(
40 | modifier = Modifier
41 | .fillMaxWidth()
42 | .padding(16.dp),
43 | text = stringResource(R.string.select_country_text),
44 | textAlign = TextAlign.Center,
45 | fontWeight = FontWeight.Bold,
46 | fontSize = 20.sp
47 | )
48 | },
49 | containerColor = Color.White,
50 | onItemSelected = {
51 | selectedCountry = it
52 | openBottomSheet = false
53 | }, onDismissRequest = {
54 | openBottomSheet = false
55 | }
56 | )
57 | }
58 | ```
59 |
60 | # Demo
61 | [Sample](https://github.com/canopas/JetCountrypicker/tree/main/app) app demonstrates how simple the usage of the library actually is.
62 |
63 | # Bugs and Feedback
64 | For bugs, questions and discussions please use the [Github Issues](https://github.com/canopas/JetCountrypicker/issues).
65 |
66 | # Credits
67 |
68 | JetCountryPicker is owned and maintained by the [Canopas team](https://canopas.com/). You can follow them on X at [@canopassoftware](https://x.com/canopassoftware) for project updates and releases.
69 |
70 | # Licence
71 |
72 | ```
73 | Copyright 2022 Canopas Software LLP
74 |
75 | Licensed under the Apache License, Version 2.0 (the "License");
76 | you may not use this file except in compliance with the License.
77 | You may obtain a copy of the License at
78 |
79 | http://www.apache.org/licenses/LICENSE-2.0
80 |
81 | Unless required by applicable law or agreed to in writing, software
82 | distributed under the License is distributed on an "AS IS" BASIS,
83 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
84 | See the License for the specific language governing permissions and
85 | limitations under the License.
86 | ```
87 |
88 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | xmlns:android
15 |
16 | ^$
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | xmlns:.*
26 |
27 | ^$
28 |
29 |
30 | BY_NAME
31 |
32 |
33 |
34 |
35 |
36 |
37 | .*:id
38 |
39 | http://schemas.android.com/apk/res/android
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | .*:name
49 |
50 | http://schemas.android.com/apk/res/android
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | name
60 |
61 | ^$
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | style
71 |
72 | ^$
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | .*
82 |
83 | ^$
84 |
85 |
86 | BY_NAME
87 |
88 |
89 |
90 |
91 |
92 |
93 | .*
94 |
95 | http://schemas.android.com/apk/res/android
96 |
97 |
98 | ANDROID_ATTRIBUTE_ORDER
99 |
100 |
101 |
102 |
103 |
104 |
105 | .*
106 |
107 | .*
108 |
109 |
110 | BY_NAME
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/CountryPickerDialog.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxHeight
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.heightIn
9 | import androidx.compose.foundation.layout.wrapContentHeight
10 | import androidx.compose.foundation.shape.CornerSize
11 | import androidx.compose.material3.LocalTextStyle
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.rememberCoroutineScope
17 | import androidx.compose.runtime.saveable.rememberSaveable
18 | import androidx.compose.runtime.setValue
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.composed
21 | import androidx.compose.ui.draw.clip
22 | import androidx.compose.ui.graphics.Color
23 | import androidx.compose.ui.graphics.Shape
24 | import androidx.compose.ui.platform.LocalConfiguration
25 | import androidx.compose.ui.text.TextStyle
26 | import androidx.compose.ui.unit.dp
27 | import androidx.compose.ui.unit.sp
28 | import androidx.compose.ui.window.Dialog
29 | import androidx.compose.ui.window.DialogProperties
30 | import com.canopas.campose.countrypicker.model.Country
31 | import kotlinx.coroutines.launch
32 |
33 |
34 | /**
35 | * Composable for displaying a country picker as a dialog.
36 | *
37 | * @param shape The shape of the dialog.
38 | * @param backgroundColor The color of the dialog container.
39 | * @param dialogTitle The title composable for the dialog.
40 | * @param onItemSelected Callback when a country is selected.
41 | * @param searchFieldTextStyle The text style for the search field.
42 | * @param placeholderTextStyle The text style for the search field placeholder.
43 | * @param countriesTextStyle The text style for the countries list.
44 | * @param showFullScreenDialog Whether to show the dialog as a full screen dialog.
45 | * @param onDismissRequest Callback when the dialog is dismissed.
46 | */
47 | @Composable
48 | fun CountryPickerDialog(
49 | shape: Shape = MaterialTheme.shapes.small,
50 | backgroundColor: Color = MaterialTheme.colorScheme.background,
51 | dialogTitle: @Composable () -> Unit,
52 | onItemSelected: (country: Country) -> Unit,
53 | searchFieldTextStyle: TextStyle = LocalTextStyle.current.copy(fontSize = 14.sp),
54 | placeholderTextStyle: TextStyle = MaterialTheme.typography.labelMedium.copy(
55 | color = Color.Gray,
56 | fontSize = 16.sp,
57 | ),
58 | countriesTextStyle: TextStyle = TextStyle(),
59 | showFullScreenDialog: Boolean = false,
60 | onDismissRequest: () -> Unit
61 | ) {
62 | var searchValue by rememberSaveable { mutableStateOf("") }
63 | val configuration = LocalConfiguration.current
64 | val screenHeight = configuration.screenHeightDp.dp
65 | val scope = rememberCoroutineScope()
66 | val modifier = if (showFullScreenDialog) {
67 | Modifier
68 | .fillMaxSize()
69 | .background(color = backgroundColor)
70 | } else {
71 | Modifier
72 | .wrapContentHeight()
73 | .heightIn(max = (screenHeight * 0.85f))
74 | .clip(shape = shape)
75 | .background(color = backgroundColor, shape)
76 | }
77 |
78 | Dialog(
79 | onDismissRequest = onDismissRequest,
80 | properties = DialogProperties(
81 | usePlatformDefaultWidth = !showFullScreenDialog,
82 | dismissOnBackPress = true,
83 | dismissOnClickOutside = !showFullScreenDialog
84 | )
85 | ) {
86 | Column(
87 | modifier = modifier
88 | ) {
89 | dialogTitle()
90 | CountrySearchView(searchValue, searchFieldTextStyle, placeholderTextStyle) {
91 | searchValue = it
92 | }
93 |
94 | Countries(searchValue, countriesTextStyle) {
95 | scope.launch {
96 | onDismissRequest()
97 | onItemSelected(it)
98 | }
99 | }
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/CountryPickerTextField.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import androidx.compose.foundation.gestures.awaitEachGesture
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.filled.ArrowDropDown
6 | import androidx.compose.material3.ExperimentalMaterial3Api
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.OutlinedTextField
10 | import androidx.compose.material3.OutlinedTextFieldDefaults
11 | import androidx.compose.material3.Text
12 | import androidx.compose.material3.TextFieldColors
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Shape
17 | import androidx.compose.ui.graphics.graphicsLayer
18 | import androidx.compose.ui.input.pointer.PointerEvent
19 | import androidx.compose.ui.input.pointer.PointerEventPass
20 | import androidx.compose.ui.input.pointer.changedToUp
21 | import androidx.compose.ui.input.pointer.pointerInput
22 | import androidx.compose.ui.platform.LocalContext
23 | import androidx.compose.ui.semantics.onClick
24 | import androidx.compose.ui.semantics.semantics
25 | import androidx.compose.ui.text.TextStyle
26 | import com.canopas.campose.countrypicker.model.Country
27 |
28 | /**
29 | * Composable for displaying a text field with a country picker.
30 | *
31 | * @param label The label for the text field.
32 | * @param isError Whether the text field should display an error state.
33 | * @param modifier The modifier for the text field.
34 | * @param shape The shape of the text field.
35 | * @param selectedCountry The currently selected country.
36 | * @param defaultCountry The default country to display if none is selected.
37 | * @param colors The colors for the text field.
38 | * @param textStyle The text style for the text field.
39 | * @param labelTextStyle The text style for the label.
40 | * @param isPickerVisible Whether the country picker bottom sheet is visible.
41 | * @param onShowCountryPicker Callback when the country picker is shown.
42 | */
43 | @Composable
44 | fun CountryTextField(
45 | label: String = "",
46 | isError: Boolean = false,
47 | modifier: Modifier,
48 | shape: Shape = MaterialTheme.shapes.small,
49 | selectedCountry: Country? = null,
50 | defaultCountry: Country? = null,
51 | colors: TextFieldColors = OutlinedTextFieldDefaults.colors(),
52 | textStyle: TextStyle = TextStyle(),
53 | labelTextStyle: TextStyle = TextStyle(),
54 | isPickerVisible: Boolean = false,
55 | onShowCountryPicker: () -> Unit
56 | ) {
57 |
58 | val context = LocalContext.current
59 | val defaultSelectedCountry = remember {
60 | defaultCountry ?: countryList(context).first()
61 | }
62 |
63 | val countryValue = "${defaultSelectedCountry.dial_code} ${defaultSelectedCountry.name}"
64 |
65 | OutlinedTextField(
66 | modifier = modifier
67 | .expandable(onExpandedChange = {
68 | onShowCountryPicker()
69 | }),
70 | readOnly = true,
71 | isError = isError,
72 | textStyle = textStyle,
73 | label = { Text(label, style = labelTextStyle) },
74 | value = if (selectedCountry == null) countryValue else "${selectedCountry.dial_code} ${selectedCountry.name}",
75 | onValueChange = {},
76 | colors = colors,
77 | shape = shape,
78 | trailingIcon = {
79 | Icon(
80 | Icons.Filled.ArrowDropDown,
81 | null,
82 | Modifier.graphicsLayer {
83 | rotationZ = if (isPickerVisible) 180f else 0f
84 | }
85 | )
86 | }
87 | )
88 | }
89 |
90 | /**
91 | * Modifier for making a composable expandable when clicked.
92 | *
93 | * @param onExpandedChange Callback when the expandable state changes.
94 | */
95 | internal fun Modifier.expandable(
96 | onExpandedChange: () -> Unit
97 | ) = this.pointerInput(Unit) {
98 | awaitEachGesture {
99 | var event: PointerEvent
100 | do {
101 | event = awaitPointerEvent(PointerEventPass.Initial)
102 | } while (
103 | !event.changes.all { it.changedToUp() }
104 | )
105 | onExpandedChange.invoke()
106 | }
107 | }.semantics {
108 | onClick {
109 | onExpandedChange()
110 | true
111 | }
112 | }
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/CountrySearchView.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.foundation.text.KeyboardActions
11 | import androidx.compose.foundation.text.KeyboardOptions
12 | import androidx.compose.material.icons.Icons
13 | import androidx.compose.material.icons.filled.Search
14 | import androidx.compose.material.icons.rounded.Cancel
15 | import androidx.compose.material3.Icon
16 | import androidx.compose.material3.IconButton
17 | import androidx.compose.material3.Text
18 | import androidx.compose.material3.TextField
19 | import androidx.compose.material3.TextFieldDefaults
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.graphics.Color
23 | import androidx.compose.ui.platform.LocalFocusManager
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.text.TextStyle
26 | import androidx.compose.ui.text.input.ImeAction
27 | import androidx.compose.ui.text.input.KeyboardType
28 | import androidx.compose.ui.tooling.preview.Preview
29 | import androidx.compose.ui.unit.dp
30 |
31 | /**
32 | * Composable for displaying a search field.
33 | *
34 | * @param searchValue The current search value.
35 | * @param searchFieldTextStyle The text style for the search field.
36 | * @param placeholderTextStyle The text style for the placeholder.
37 | * @param onSearch Callback when the search value changes.
38 | */
39 | @Composable
40 | internal fun CountrySearchView(
41 | searchValue: String,
42 | searchFieldTextStyle: TextStyle = TextStyle(),
43 | placeholderTextStyle: TextStyle = TextStyle(),
44 | onSearch: (searchValue: String) -> Unit
45 | ) {
46 | val focusManager = LocalFocusManager.current
47 |
48 | Row {
49 | Box(
50 | modifier = Modifier.padding(start = 20.dp, end = 20.dp)
51 | ) {
52 | TextField(modifier = Modifier
53 | .fillMaxWidth()
54 | .height(52.dp)
55 | .background(
56 | Color.LightGray.copy(0.6f), shape = RoundedCornerShape(10.dp)
57 | ), value = searchValue, onValueChange = {
58 | onSearch(it)
59 | }, textStyle = searchFieldTextStyle, placeholder = {
60 | Text(
61 | text = stringResource(R.string.search_text),
62 | style = placeholderTextStyle
63 | )
64 | }, singleLine = true,
65 | leadingIcon = {
66 | Icon(
67 | Icons.Default.Search,
68 | contentDescription = null,
69 | tint = Color.Black.copy(0.3f)
70 | )
71 | }, trailingIcon = {
72 | if (searchValue.isNotEmpty()) {
73 | IconButton(onClick = {
74 | onSearch("")
75 | }) {
76 | Icon(
77 | imageVector = Icons.Rounded.Cancel,
78 | tint = Color.Black.copy(0.3f),
79 | contentDescription = "Clear icon"
80 | )
81 | }
82 | }
83 | }, colors = TextFieldDefaults.colors(
84 | focusedContainerColor = Color.Transparent,
85 | unfocusedContainerColor = Color.Transparent,
86 | focusedIndicatorColor = Color.Transparent,
87 | unfocusedIndicatorColor = Color.Transparent,
88 | disabledIndicatorColor = Color.Transparent
89 | ), keyboardOptions = KeyboardOptions(
90 | keyboardType = KeyboardType.Text,
91 | imeAction = ImeAction.Done
92 | ), keyboardActions = KeyboardActions(onDone = {
93 | focusManager.clearFocus()
94 | })
95 | )
96 | }
97 | }
98 | }
99 |
100 | @Preview
101 | @Composable
102 | fun PreviewSearchView() {
103 | CountrySearchView("search", onSearch = {})
104 | }
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/CountryPickerView.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import androidx.compose.material3.BottomSheetDefaults
4 | import androidx.compose.material3.ExperimentalMaterial3Api
5 | import androidx.compose.material3.LocalTextStyle
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.SheetState
8 | import androidx.compose.material3.contentColorFor
9 | import androidx.compose.material3.rememberModalBottomSheetState
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.graphics.Shape
13 | import androidx.compose.ui.text.TextStyle
14 | import androidx.compose.ui.unit.Dp
15 | import androidx.compose.ui.unit.sp
16 | import com.canopas.campose.countrypicker.model.Country
17 | import com.canopas.campose.countrypicker.model.PickerType
18 |
19 | /**
20 | * Composable for displaying a country picker.
21 | *
22 | * @param sheetState The state of the bottom sheet.
23 | * @param shape The shape of the bottom sheet or dialog.
24 | * @param containerColor The color of the bottom sheet or dialog container.
25 | * @param contentColor The color of the bottom sheet items.
26 | * **[For bottom sheet only].**
27 | * @param tonalElevation The elevation of the bottom sheet
28 | * **[For bottom sheet only].**
29 | * @param scrimColor Color of the scrim that obscures content when the bottom sheet is open.
30 | * **[For bottom sheet only].**
31 | * @param pickerTitle The title composable for the bottom sheet or dialog.
32 | * @param onItemSelected Callback when a country is selected.
33 | * @param searchFieldTextStyle The text style for the search field.
34 | * @param placeholderTextStyle The text style for the search field placeholder.
35 | * @param countriesTextStyle The text style for the countries list.
36 | * @param pickerType The type of picker to display (bottom sheet, full-screen dialog, or dialog).
37 | * Also see [PickerType].
38 | * @param onDismissRequest Callback when the bottom sheet or dialog is dismissed.
39 | */
40 | @OptIn(ExperimentalMaterial3Api::class)
41 | @Composable
42 | fun CountryPickerView(
43 | sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false),
44 | shape: Shape = MaterialTheme.shapes.medium,
45 | containerColor: Color = MaterialTheme.colorScheme.background,
46 | contentColor: Color = contentColorFor(containerColor),
47 | tonalElevation: Dp = BottomSheetDefaults.Elevation,
48 | scrimColor: Color = BottomSheetDefaults.ScrimColor,
49 | pickerTitle: @Composable () -> Unit,
50 | onItemSelected: (country: Country) -> Unit,
51 | searchFieldTextStyle: TextStyle = LocalTextStyle.current.copy(fontSize = 14.sp),
52 | placeholderTextStyle: TextStyle = MaterialTheme.typography.labelMedium.copy(
53 | color = Color.Gray,
54 | fontSize = 16.sp,
55 | ),
56 | countriesTextStyle: TextStyle = TextStyle(),
57 | pickerType: PickerType = PickerType.BOTTOM_SHEET,
58 | onDismissRequest: () -> Unit
59 | ) {
60 | when(pickerType) {
61 | PickerType.BOTTOM_SHEET -> {
62 | CountryPickerBottomSheet(
63 | sheetState = sheetState,
64 | shape = shape,
65 | containerColor = containerColor,
66 | contentColor = contentColor,
67 | tonalElevation = tonalElevation,
68 | scrimColor = scrimColor,
69 | bottomSheetTitle = pickerTitle,
70 | onItemSelected = onItemSelected,
71 | searchFieldTextStyle = searchFieldTextStyle,
72 | placeholderTextStyle = placeholderTextStyle,
73 | countriesTextStyle = countriesTextStyle,
74 | onDismissRequest = onDismissRequest
75 | )
76 | }
77 | PickerType.FULL_SCREEN_DIALOG -> {
78 | CountryPickerDialog(
79 | shape = shape,
80 | backgroundColor = containerColor,
81 | onItemSelected = onItemSelected,
82 | dialogTitle = pickerTitle,
83 | searchFieldTextStyle = searchFieldTextStyle,
84 | placeholderTextStyle = placeholderTextStyle,
85 | countriesTextStyle = countriesTextStyle,
86 | showFullScreenDialog = true,
87 | onDismissRequest = onDismissRequest
88 | )
89 | }
90 | PickerType.DIALOG -> {
91 | CountryPickerDialog(
92 | shape = shape,
93 | backgroundColor = containerColor,
94 | onItemSelected = onItemSelected,
95 | dialogTitle = pickerTitle,
96 | searchFieldTextStyle = searchFieldTextStyle,
97 | placeholderTextStyle = placeholderTextStyle,
98 | countriesTextStyle = countriesTextStyle,
99 | onDismissRequest = onDismissRequest
100 | )
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/canopas/campose/jetcountypicker/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.jetcountypicker
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.clickable
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.Row
11 | import androidx.compose.foundation.layout.Spacer
12 | import androidx.compose.foundation.layout.fillMaxWidth
13 | import androidx.compose.foundation.layout.height
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.material3.ExperimentalMaterial3Api
16 | import androidx.compose.material3.MaterialTheme
17 | import androidx.compose.material3.RadioButton
18 | import androidx.compose.material3.Surface
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.getValue
22 | import androidx.compose.runtime.mutableStateOf
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.saveable.rememberSaveable
25 | import androidx.compose.runtime.setValue
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.platform.LocalContext
29 | import androidx.compose.ui.res.stringResource
30 | import androidx.compose.ui.text.font.FontWeight
31 | import androidx.compose.ui.text.style.TextAlign
32 | import androidx.compose.ui.tooling.preview.Preview
33 | import androidx.compose.ui.unit.dp
34 | import androidx.compose.ui.unit.sp
35 | import com.canopas.campose.countrypicker.CountryPickerView
36 | import com.canopas.campose.countrypicker.CountryTextField
37 | import com.canopas.campose.countrypicker.countryList
38 | import com.canopas.campose.countrypicker.model.Country
39 | import com.canopas.campose.countrypicker.model.PickerType
40 | import com.canopas.campose.countypickerdemo.R
41 | import com.canopas.campose.jetcountypicker.ui.theme.JetCountyPickerTheme
42 |
43 | class MainActivity : ComponentActivity() {
44 | override fun onCreate(savedInstanceState: Bundle?) {
45 | super.onCreate(savedInstanceState)
46 | setContent {
47 | JetCountyPickerTheme {
48 | Surface(color = MaterialTheme.colorScheme.background) {
49 | Column {
50 | SampleCountryPickerDialog()
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 | @OptIn(ExperimentalMaterial3Api::class)
59 | @Composable
60 | fun SampleCountryPickerDialog() {
61 | var showCountryPicker by rememberSaveable { mutableStateOf(false) }
62 | var selectedCountry by remember { mutableStateOf(null) }
63 | var pickerType by remember {
64 | mutableStateOf(PickerType.DIALOG)
65 | }
66 |
67 | Box {
68 | CountryTextField(
69 | label = stringResource(R.string.select_country_text),
70 | modifier = Modifier
71 | .fillMaxWidth()
72 | .padding(top = 50.dp, start = 40.dp, end = 40.dp),
73 | textStyle = MaterialTheme.typography.bodyMedium,
74 | labelTextStyle = MaterialTheme.typography.labelMedium,
75 | selectedCountry = selectedCountry,
76 | defaultCountry = countryList(LocalContext.current).firstOrNull { it.code == "IN" },
77 | onShowCountryPicker = {
78 | showCountryPicker = true
79 | }, isPickerVisible = showCountryPicker
80 | )
81 | }
82 |
83 | Column(horizontalAlignment = Alignment.Start) {
84 | Spacer(modifier = Modifier.height(28.dp))
85 | Text(
86 | text = stringResource(R.string.picker_type),
87 | modifier = Modifier.padding(16.dp),
88 | style = MaterialTheme.typography.bodyMedium
89 | )
90 | PickerType.entries.forEach {
91 | Row(
92 | modifier = Modifier
93 | .fillMaxWidth()
94 | .clickable(
95 | onClick = {
96 | pickerType = it
97 | }
98 | ),
99 | horizontalArrangement = Arrangement.Start,
100 | verticalAlignment = Alignment.CenterVertically
101 | ) {
102 | RadioButton(
103 | selected = pickerType == it,
104 | onClick = {
105 | pickerType = it
106 | }
107 | )
108 | Text(
109 | text = stringResource(it.value),
110 | modifier = Modifier.padding(start = 8.dp),
111 | style = MaterialTheme.typography.bodyMedium
112 | )
113 | }
114 | }
115 | }
116 |
117 | if (showCountryPicker) {
118 | CountryPickerView(
119 | pickerTitle = {
120 | Text(
121 | modifier = Modifier
122 | .fillMaxWidth()
123 | .padding(16.dp),
124 | text = stringResource(R.string.select_country_text),
125 | textAlign = TextAlign.Center,
126 | fontWeight = FontWeight.Bold,
127 | fontSize = 20.sp
128 | )
129 | },
130 | searchFieldTextStyle = MaterialTheme.typography.bodyMedium,
131 | placeholderTextStyle = MaterialTheme.typography.labelMedium,
132 | countriesTextStyle = MaterialTheme.typography.bodyMedium,
133 | onItemSelected = {
134 | selectedCountry = it
135 | showCountryPicker = false
136 | },
137 | pickerType = pickerType,
138 | onDismissRequest = {
139 | showCountryPicker = false
140 | }
141 | )
142 | }
143 | }
144 |
145 | @Preview(showBackground = true)
146 | @Composable
147 | fun DefaultPreview() {
148 | JetCountyPickerTheme {
149 | SampleCountryPickerDialog()
150 | }
151 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/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 | # https://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 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/countrypicker/src/main/java/com/canopas/campose/countrypicker/CountryPickerBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.canopas.campose.countrypicker
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.ExperimentalLayoutApi
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.imeNestedScroll
9 | import androidx.compose.foundation.layout.imePadding
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.lazy.LazyColumn
12 | import androidx.compose.foundation.lazy.items
13 | import androidx.compose.material3.BottomSheetDefaults
14 | import androidx.compose.material3.ExperimentalMaterial3Api
15 | import androidx.compose.material3.HorizontalDivider
16 | import androidx.compose.material3.LocalTextStyle
17 | import androidx.compose.material3.MaterialTheme
18 | import androidx.compose.material3.ModalBottomSheet
19 | import androidx.compose.material3.SheetState
20 | import androidx.compose.material3.Text
21 | import androidx.compose.material3.contentColorFor
22 | import androidx.compose.material3.rememberModalBottomSheetState
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.getValue
25 | import androidx.compose.runtime.mutableStateOf
26 | import androidx.compose.runtime.remember
27 | import androidx.compose.runtime.rememberCoroutineScope
28 | import androidx.compose.runtime.saveable.rememberSaveable
29 | import androidx.compose.runtime.setValue
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.graphics.Color
32 | import androidx.compose.ui.graphics.Shape
33 | import androidx.compose.ui.platform.LocalContext
34 | import androidx.compose.ui.text.TextStyle
35 | import androidx.compose.ui.unit.Dp
36 | import androidx.compose.ui.unit.dp
37 | import androidx.compose.ui.unit.sp
38 | import com.canopas.campose.countrypicker.model.Country
39 | import kotlinx.coroutines.launch
40 |
41 | /**
42 | * Composable for displaying a bottom sheet country picker.
43 | *
44 | * @param sheetState The state of the bottom sheet.
45 | * @param shape The shape of the bottom sheet.
46 | * @param containerColor The color of the bottom sheet container.
47 | * @param contentColor The color of the bottom sheet content.
48 | * @param tonalElevation The elevation of the bottom sheet.
49 | * @param scrimColor The color of the bottom sheet scrim.
50 | * @param bottomSheetTitle The title composable for the bottom sheet.
51 | * @param onItemSelected Callback when a country is selected.
52 | * @param searchFieldTextStyle The text style for the search field.
53 | * @param placeholderTextStyle The text style for the search field placeholder.
54 | * @param countriesTextStyle The text style for the countries list.
55 | * @param onDismissRequest Callback when the bottom sheet is dismissed.
56 | */
57 | @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
58 | @Composable
59 | fun CountryPickerBottomSheet(
60 | sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false),
61 | shape: Shape = MaterialTheme.shapes.medium,
62 | containerColor: Color = BottomSheetDefaults.ContainerColor,
63 | contentColor: Color = contentColorFor(containerColor),
64 | tonalElevation: Dp = BottomSheetDefaults.Elevation,
65 | scrimColor: Color = BottomSheetDefaults.ScrimColor,
66 | bottomSheetTitle: @Composable () -> Unit,
67 | onItemSelected: (country: Country) -> Unit,
68 | searchFieldTextStyle: TextStyle = LocalTextStyle.current.copy(fontSize = 14.sp),
69 | placeholderTextStyle: TextStyle = MaterialTheme.typography.labelMedium.copy(
70 | color = Color.Gray,
71 | fontSize = 16.sp,
72 | ),
73 | countriesTextStyle: TextStyle = TextStyle(),
74 | onDismissRequest: () -> Unit
75 | ) {
76 | var searchValue by rememberSaveable { mutableStateOf("") }
77 | val scope = rememberCoroutineScope()
78 |
79 | ModalBottomSheet(
80 | onDismissRequest = onDismissRequest,
81 | sheetState = sheetState,
82 | shape = shape,
83 | containerColor = containerColor,
84 | contentColor = contentColor,
85 | tonalElevation = tonalElevation,
86 | scrimColor = scrimColor,
87 | modifier = Modifier.fillMaxSize().imePadding().imeNestedScroll()
88 | ) {
89 | bottomSheetTitle()
90 |
91 | CountrySearchView(searchValue, searchFieldTextStyle, placeholderTextStyle) {
92 | searchValue = it
93 | }
94 |
95 | Countries(searchValue, countriesTextStyle) {
96 | scope.launch {
97 | sheetState.hide()
98 | onItemSelected(it)
99 | }
100 | }
101 | }
102 | }
103 |
104 | /**
105 | * Composable for displaying a list of countries.
106 | *
107 | * @param searchValue The search value for filtering countries.
108 | * @param textStyle The text style for the country list.
109 | * @param onItemSelected Callback when a country is selected.
110 | */
111 | @Composable
112 | internal fun Countries(
113 | searchValue: String,
114 | textStyle: TextStyle = TextStyle(),
115 | onItemSelected: (country: Country) -> Unit
116 | ) {
117 | val context = LocalContext.current
118 | val defaultCountries = remember { countryList(context) }
119 |
120 | val countries = remember(searchValue) {
121 | if (searchValue.isEmpty()) {
122 | defaultCountries
123 | } else {
124 | defaultCountries.searchCountryList(searchValue)
125 | }
126 | }
127 |
128 | LazyColumn(contentPadding = PaddingValues(16.dp)) {
129 | items(countries) { country ->
130 | Row(modifier = Modifier
131 | .clickable { onItemSelected(country) }
132 | .padding(12.dp)
133 | ) {
134 | Text(
135 | text = localeToEmoji(country.code),
136 | style = textStyle
137 | )
138 | Text(
139 | text = country.name,
140 | modifier = Modifier
141 | .padding(start = 8.dp)
142 | .weight(2f),
143 | style = textStyle
144 | )
145 | Text(
146 | text = country.dial_code,
147 | modifier = Modifier
148 | .padding(start = 8.dp),
149 | style = textStyle
150 | )
151 | }
152 | HorizontalDivider(thickness = 0.5.dp, color = Color.LightGray)
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/countrypicker/src/main/assets/Countries.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Afghanistan",
4 | "dial_code": "+93",
5 | "code": "AF"
6 | },
7 | {
8 | "name": "Aland Islands",
9 | "dial_code": "+358",
10 | "code": "AX"
11 | },
12 | {
13 | "name": "Albania",
14 | "dial_code": "+355",
15 | "code": "AL"
16 | },
17 | {
18 | "name": "Algeria",
19 | "dial_code": "+213",
20 | "code": "DZ"
21 | },
22 | {
23 | "name": "American Samoa",
24 | "dial_code": "+1684",
25 | "code": "AS"
26 | },
27 | {
28 | "name": "Andorra",
29 | "dial_code": "+376",
30 | "code": "AD"
31 | },
32 | {
33 | "name": "Angola",
34 | "dial_code": "+244",
35 | "code": "AO"
36 | },
37 | {
38 | "name": "Anguilla",
39 | "dial_code": "+1264",
40 | "code": "AI"
41 | },
42 | {
43 | "name": "Antarctica",
44 | "dial_code": "+672",
45 | "code": "AQ"
46 | },
47 | {
48 | "name": "Antigua and Barbuda",
49 | "dial_code": "+1268",
50 | "code": "AG"
51 | },
52 | {
53 | "name": "Argentina",
54 | "dial_code": "+54",
55 | "code": "AR"
56 | },
57 | {
58 | "name": "Armenia",
59 | "dial_code": "+374",
60 | "code": "AM"
61 | },
62 | {
63 | "name": "Aruba",
64 | "dial_code": "+297",
65 | "code": "AW"
66 | },
67 | {
68 | "name": "Australia",
69 | "dial_code": "+61",
70 | "code": "AU"
71 | },
72 | {
73 | "name": "Austria",
74 | "dial_code": "+43",
75 | "code": "AT"
76 | },
77 | {
78 | "name": "Azerbaijan",
79 | "dial_code": "+994",
80 | "code": "AZ"
81 | },
82 | {
83 | "name": "Bahamas",
84 | "dial_code": "+1242",
85 | "code": "BS"
86 | },
87 | {
88 | "name": "Bahrain",
89 | "dial_code": "+973",
90 | "code": "BH"
91 | },
92 | {
93 | "name": "Bangladesh",
94 | "dial_code": "+880",
95 | "code": "BD"
96 | },
97 | {
98 | "name": "Barbados",
99 | "dial_code": "+1246",
100 | "code": "BB"
101 | },
102 | {
103 | "name": "Belarus",
104 | "dial_code": "+375",
105 | "code": "BY"
106 | },
107 | {
108 | "name": "Belgium",
109 | "dial_code": "+32",
110 | "code": "BE"
111 | },
112 | {
113 | "name": "Belize",
114 | "dial_code": "+501",
115 | "code": "BZ"
116 | },
117 | {
118 | "name": "Benin",
119 | "dial_code": "+229",
120 | "code": "BJ"
121 | },
122 | {
123 | "name": "Bermuda",
124 | "dial_code": "+1441",
125 | "code": "BM"
126 | },
127 | {
128 | "name": "Bhutan",
129 | "dial_code": "+975",
130 | "code": "BT"
131 | },
132 | {
133 | "name": "Bolivia, Plurinational State of",
134 | "dial_code": "+591",
135 | "code": "BO"
136 | },
137 | {
138 | "name": "Bosnia and Herzegovina",
139 | "dial_code": "+387",
140 | "code": "BA"
141 | },
142 | {
143 | "name": "Botswana",
144 | "dial_code": "+267",
145 | "code": "BW"
146 | },
147 | {
148 | "name": "Brazil",
149 | "dial_code": "+55",
150 | "code": "BR"
151 | },
152 | {
153 | "name": "British Indian Ocean Territory",
154 | "dial_code": "+246",
155 | "code": "IO"
156 | },
157 | {
158 | "name": "Brunei Darussalam",
159 | "dial_code": "+673",
160 | "code": "BN"
161 | },
162 | {
163 | "name": "Bulgaria",
164 | "dial_code": "+359",
165 | "code": "BG"
166 | },
167 | {
168 | "name": "Burkina Faso",
169 | "dial_code": "+226",
170 | "code": "BF"
171 | },
172 | {
173 | "name": "Burundi",
174 | "dial_code": "+257",
175 | "code": "BI"
176 | },
177 | {
178 | "name": "Cambodia",
179 | "dial_code": "+855",
180 | "code": "KH"
181 | },
182 | {
183 | "name": "Cameroon",
184 | "dial_code": "+237",
185 | "code": "CM"
186 | },
187 | {
188 | "name": "Canada",
189 | "dial_code": "+1",
190 | "code": "CA"
191 | },
192 | {
193 | "name": "Cape Verde",
194 | "dial_code": "+238",
195 | "code": "CV"
196 | },
197 | {
198 | "name": "Cayman Islands",
199 | "dial_code": "+1345",
200 | "code": "KY"
201 | },
202 | {
203 | "name": "Central African Republic",
204 | "dial_code": "+236",
205 | "code": "CF"
206 | },
207 | {
208 | "name": "Chad",
209 | "dial_code": "+235",
210 | "code": "TD"
211 | },
212 | {
213 | "name": "Chile",
214 | "dial_code": "+56",
215 | "code": "CL"
216 | },
217 | {
218 | "name": "China",
219 | "dial_code": "+86",
220 | "code": "CN"
221 | },
222 | {
223 | "name": "Christmas Island",
224 | "dial_code": "+61",
225 | "code": "CX"
226 | },
227 | {
228 | "name": "Cocos (Keeling) Islands",
229 | "dial_code": "+61",
230 | "code": "CC"
231 | },
232 | {
233 | "name": "Colombia",
234 | "dial_code": "+57",
235 | "code": "CO"
236 | },
237 | {
238 | "name": "Comoros",
239 | "dial_code": "+269",
240 | "code": "KM"
241 | },
242 | {
243 | "name": "Congo",
244 | "dial_code": "+242",
245 | "code": "CG"
246 | },
247 | {
248 | "name": "Congo, The Democratic Republic of the Congo",
249 | "dial_code": "+243",
250 | "code": "CD"
251 | },
252 | {
253 | "name": "Cook Islands",
254 | "dial_code": "+682",
255 | "code": "CK"
256 | },
257 | {
258 | "name": "Costa Rica",
259 | "dial_code": "+506",
260 | "code": "CR"
261 | },
262 | {
263 | "name": "Cote d'Ivoire",
264 | "dial_code": "+225",
265 | "code": "CI"
266 | },
267 | {
268 | "name": "Croatia",
269 | "dial_code": "+385",
270 | "code": "HR"
271 | },
272 | {
273 | "name": "Cuba",
274 | "dial_code": "+53",
275 | "code": "CU"
276 | },
277 | {
278 | "name": "Curacao",
279 | "dial_code": "+599",
280 | "code": "CW"
281 | },
282 | {
283 | "name": "Cyprus",
284 | "dial_code": "+357",
285 | "code": "CY"
286 | },
287 | {
288 | "name": "Czech Republic",
289 | "dial_code": "+420",
290 | "code": "CZ"
291 | },
292 | {
293 | "name": "Denmark",
294 | "dial_code": "+45",
295 | "code": "DK"
296 | },
297 | {
298 | "name": "Djibouti",
299 | "dial_code": "+253",
300 | "code": "DJ"
301 | },
302 | {
303 | "name": "Dominica",
304 | "dial_code": "+1767",
305 | "code": "DM"
306 | },
307 | {
308 | "name": "Dominican Republic",
309 | "dial_code": "+1",
310 | "code": "DO"
311 | },
312 | {
313 | "name": "Ecuador",
314 | "dial_code": "+593",
315 | "code": "EC"
316 | },
317 | {
318 | "name": "Egypt",
319 | "dial_code": "+20",
320 | "code": "EG"
321 | },
322 | {
323 | "name": "El Salvador",
324 | "dial_code": "+503",
325 | "code": "SV"
326 | },
327 | {
328 | "name": "Equatorial Guinea",
329 | "dial_code": "+240",
330 | "code": "GQ"
331 | },
332 | {
333 | "name": "Eritrea",
334 | "dial_code": "+291",
335 | "code": "ER"
336 | },
337 | {
338 | "name": "Estonia",
339 | "dial_code": "+372",
340 | "code": "EE"
341 | },
342 | {
343 | "name": "Ethiopia",
344 | "dial_code": "+251",
345 | "code": "ET"
346 | },
347 | {
348 | "name": "Falkland Islands (Malvinas)",
349 | "dial_code": "+500",
350 | "code": "FK"
351 | },
352 | {
353 | "name": "Faroe Islands",
354 | "dial_code": "+298",
355 | "code": "FO"
356 | },
357 | {
358 | "name": "Fiji",
359 | "dial_code": "+679",
360 | "code": "FJ"
361 | },
362 | {
363 | "name": "Finland",
364 | "dial_code": "+358",
365 | "code": "FI"
366 | },
367 | {
368 | "name": "France",
369 | "dial_code": "+33",
370 | "code": "FR"
371 | },
372 | {
373 | "name": "French Guiana",
374 | "dial_code": "+594",
375 | "code": "GF"
376 | },
377 | {
378 | "name": "French Polynesia",
379 | "dial_code": "+689",
380 | "code": "PF"
381 | },
382 | {
383 | "name": "Gabon",
384 | "dial_code": "+241",
385 | "code": "GA"
386 | },
387 | {
388 | "name": "Gambia",
389 | "dial_code": "+220",
390 | "code": "GM"
391 | },
392 | {
393 | "name": "Georgia",
394 | "dial_code": "+995",
395 | "code": "GE"
396 | },
397 | {
398 | "name": "Germany",
399 | "dial_code": "+49",
400 | "code": "DE"
401 | },
402 | {
403 | "name": "Ghana",
404 | "dial_code": "+233",
405 | "code": "GH"
406 | },
407 | {
408 | "name": "Gibraltar",
409 | "dial_code": "+350",
410 | "code": "GI"
411 | },
412 | {
413 | "name": "Greece",
414 | "dial_code": "+30",
415 | "code": "GR"
416 | },
417 | {
418 | "name": "Greenland",
419 | "dial_code": "+299",
420 | "code": "GL"
421 | },
422 | {
423 | "name": "Grenada",
424 | "dial_code": "+1473",
425 | "code": "GD"
426 | },
427 | {
428 | "name": "Guadeloupe",
429 | "dial_code": "+590",
430 | "code": "GP"
431 | },
432 | {
433 | "name": "Guam",
434 | "dial_code": "+1671",
435 | "code": "GU"
436 | },
437 | {
438 | "name": "Guatemala",
439 | "dial_code": "+502",
440 | "code": "GT"
441 | },
442 | {
443 | "name": "Guernsey",
444 | "dial_code": "+44",
445 | "code": "GG"
446 | },
447 | {
448 | "name": "Guinea",
449 | "dial_code": "+224",
450 | "code": "GN"
451 | },
452 | {
453 | "name": "Guinea-Bissau",
454 | "dial_code": "+245",
455 | "code": "GW"
456 | },
457 | {
458 | "name": "Guyana",
459 | "dial_code": "+592",
460 | "code": "GY"
461 | },
462 | {
463 | "name": "Haiti",
464 | "dial_code": "+509",
465 | "code": "HT"
466 | },
467 | {
468 | "name": "Holy See (Vatican City State)",
469 | "dial_code": "+379",
470 | "code": "VA"
471 | },
472 | {
473 | "name": "Honduras",
474 | "dial_code": "+504",
475 | "code": "HN"
476 | },
477 | {
478 | "name": "Hong Kong",
479 | "dial_code": "+852",
480 | "code": "HK"
481 | },
482 | {
483 | "name": "Hungary",
484 | "dial_code": "+36",
485 | "code": "HU"
486 | },
487 | {
488 | "name": "Iceland",
489 | "dial_code": "+354",
490 | "code": "IS"
491 | },
492 | {
493 | "name": "India",
494 | "dial_code": "+91",
495 | "code": "IN"
496 | },
497 | {
498 | "name": "Indonesia",
499 | "dial_code": "+62",
500 | "code": "ID"
501 | },
502 | {
503 | "name": "Iran",
504 | "dial_code": "+98",
505 | "code": "IR"
506 | },
507 | {
508 | "name": "Iraq",
509 | "dial_code": "+964",
510 | "code": "IQ"
511 | },
512 | {
513 | "name": "Ireland",
514 | "dial_code": "+353",
515 | "code": "IE"
516 | },
517 | {
518 | "name": "Isle of Man",
519 | "dial_code": "+44",
520 | "code": "IM"
521 | },
522 | {
523 | "name": "Israel",
524 | "dial_code": "+972",
525 | "code": "IL"
526 | },
527 | {
528 | "name": "Italy",
529 | "dial_code": "+39",
530 | "code": "IT"
531 | },
532 | {
533 | "name": "Jamaica",
534 | "dial_code": "+876",
535 | "code": "JM"
536 | },
537 | {
538 | "name": "Japan",
539 | "dial_code": "+81",
540 | "code": "JP"
541 | },
542 | {
543 | "name": "Jersey",
544 | "dial_code": "+44",
545 | "code": "JE"
546 | },
547 | {
548 | "name": "Jordan",
549 | "dial_code": "+962",
550 | "code": "JO"
551 | },
552 | {
553 | "name": "Kazakhstan",
554 | "dial_code": "+7",
555 | "code": "KZ"
556 | },
557 | {
558 | "name": "Kenya",
559 | "dial_code": "+254",
560 | "code": "KE"
561 | },
562 | {
563 | "name": "Kiribati",
564 | "dial_code": "+686",
565 | "code": "KI"
566 | },
567 | {
568 | "name": "Kosovo",
569 | "dial_code": "+383",
570 | "code": "XK"
571 | },
572 | {
573 | "name": "Kuwait",
574 | "dial_code": "+965",
575 | "code": "KW"
576 | },
577 | {
578 | "name": "Kyrgyzstan",
579 | "dial_code": "+996",
580 | "code": "KG"
581 | },
582 | {
583 | "name": "Laos",
584 | "dial_code": "+856",
585 | "code": "LA"
586 | },
587 | {
588 | "name": "Latvia",
589 | "dial_code": "+371",
590 | "code": "LV"
591 | },
592 | {
593 | "name": "Lebanon",
594 | "dial_code": "+961",
595 | "code": "LB"
596 | },
597 | {
598 | "name": "Lesotho",
599 | "dial_code": "+266",
600 | "code": "LS"
601 | },
602 | {
603 | "name": "Liberia",
604 | "dial_code": "+231",
605 | "code": "LR"
606 | },
607 | {
608 | "name": "Libyan Arab Jamahiriya",
609 | "dial_code": "+218",
610 | "code": "LY"
611 | },
612 | {
613 | "name": "Liechtenstein",
614 | "dial_code": "+423",
615 | "code": "LI"
616 | },
617 | {
618 | "name": "Lithuania",
619 | "dial_code": "+370",
620 | "code": "LT"
621 | },
622 | {
623 | "name": "Luxembourg",
624 | "dial_code": "+352",
625 | "code": "LU"
626 | },
627 | {
628 | "name": "Macao",
629 | "dial_code": "+853",
630 | "code": "MO"
631 | },
632 | {
633 | "name": "Macedonia",
634 | "dial_code": "+389",
635 | "code": "MK"
636 | },
637 | {
638 | "name": "Madagascar",
639 | "dial_code": "+261",
640 | "code": "MG"
641 | },
642 | {
643 | "name": "Malawi",
644 | "dial_code": "+265",
645 | "code": "MW"
646 | },
647 | {
648 | "name": "Malaysia",
649 | "dial_code": "+60",
650 | "code": "MY"
651 | },
652 | {
653 | "name": "Maldives",
654 | "dial_code": "+960",
655 | "code": "MV"
656 | },
657 | {
658 | "name": "Mali",
659 | "dial_code": "+223",
660 | "code": "ML"
661 | },
662 | {
663 | "name": "Malta",
664 | "dial_code": "+356",
665 | "code": "MT"
666 | },
667 | {
668 | "name": "Marshall Islands",
669 | "dial_code": "+692",
670 | "code": "MH"
671 | },
672 | {
673 | "name": "Martinique",
674 | "dial_code": "+596",
675 | "code": "MQ"
676 | },
677 | {
678 | "name": "Mauritania",
679 | "dial_code": "+222",
680 | "code": "MR"
681 | },
682 | {
683 | "name": "Mauritius",
684 | "dial_code": "+230",
685 | "code": "MU"
686 | },
687 | {
688 | "name": "Mayotte",
689 | "dial_code": "+262",
690 | "code": "YT"
691 | },
692 | {
693 | "name": "Mexico",
694 | "dial_code": "+52",
695 | "code": "MX"
696 | },
697 | {
698 | "name": "Micronesia, Federated States of Micronesia",
699 | "dial_code": "+691",
700 | "code": "FM"
701 | },
702 | {
703 | "name": "Moldova",
704 | "dial_code": "+373",
705 | "code": "MD"
706 | },
707 | {
708 | "name": "Monaco",
709 | "dial_code": "+377",
710 | "code": "MC"
711 | },
712 | {
713 | "name": "Mongolia",
714 | "dial_code": "+976",
715 | "code": "MN"
716 | },
717 | {
718 | "name": "Montenegro",
719 | "dial_code": "+382",
720 | "code": "ME"
721 | },
722 | {
723 | "name": "Montserrat",
724 | "dial_code": "+1664",
725 | "code": "MS"
726 | },
727 | {
728 | "name": "Morocco",
729 | "dial_code": "+212",
730 | "code": "MA"
731 | },
732 | {
733 | "name": "Mozambique",
734 | "dial_code": "+258",
735 | "code": "MZ"
736 | },
737 | {
738 | "name": "Myanmar",
739 | "dial_code": "+95",
740 | "code": "MM"
741 | },
742 | {
743 | "name": "Namibia",
744 | "dial_code": "+264",
745 | "code": "NA"
746 | },
747 | {
748 | "name": "Nauru",
749 | "dial_code": "+674",
750 | "code": "NR"
751 | },
752 | {
753 | "name": "Nepal",
754 | "dial_code": "+977",
755 | "code": "NP"
756 | },
757 | {
758 | "name": "Netherlands",
759 | "dial_code": "+31",
760 | "code": "NL"
761 | },
762 | {
763 | "name": "New Caledonia",
764 | "dial_code": "+687",
765 | "code": "NC"
766 | },
767 | {
768 | "name": "New Zealand",
769 | "dial_code": "+64",
770 | "code": "NZ"
771 | },
772 | {
773 | "name": "Nicaragua",
774 | "dial_code": "+505",
775 | "code": "NI"
776 | },
777 | {
778 | "name": "Niger",
779 | "dial_code": "+227",
780 | "code": "NE"
781 | },
782 | {
783 | "name": "Nigeria",
784 | "dial_code": "+234",
785 | "code": "NG"
786 | },
787 | {
788 | "name": "Niue",
789 | "dial_code": "+683",
790 | "code": "NU"
791 | },
792 | {
793 | "name": "Norfolk Island",
794 | "dial_code": "+672",
795 | "code": "NF"
796 | },
797 | {
798 | "name": "North Korea",
799 | "dial_code": "+850",
800 | "code": "KP"
801 | },
802 | {
803 | "name": "Northern Mariana Islands",
804 | "dial_code": "+1670",
805 | "code": "MP"
806 | },
807 | {
808 | "name": "Norway",
809 | "dial_code": "+47",
810 | "code": "NO"
811 | },
812 | {
813 | "name": "Oman",
814 | "dial_code": "+968",
815 | "code": "OM"
816 | },
817 | {
818 | "name": "Pakistan",
819 | "dial_code": "+92",
820 | "code": "PK"
821 | },
822 | {
823 | "name": "Palau",
824 | "dial_code": "+680",
825 | "code": "PW"
826 | },
827 | {
828 | "name": "Palestinian Territory, Occupied",
829 | "dial_code": "+970",
830 | "code": "PS"
831 | },
832 | {
833 | "name": "Panama",
834 | "dial_code": "+507",
835 | "code": "PA"
836 | },
837 | {
838 | "name": "Papua New Guinea",
839 | "dial_code": "+675",
840 | "code": "PG"
841 | },
842 | {
843 | "name": "Paraguay",
844 | "dial_code": "+595",
845 | "code": "PY"
846 | },
847 | {
848 | "name": "Peru",
849 | "dial_code": "+51",
850 | "code": "PE"
851 | },
852 | {
853 | "name": "Philippines",
854 | "dial_code": "+63",
855 | "code": "PH"
856 | },
857 | {
858 | "name": "Pitcairn",
859 | "dial_code": "+64",
860 | "code": "PN"
861 | },
862 | {
863 | "name": "Poland",
864 | "dial_code": "+48",
865 | "code": "PL"
866 | },
867 | {
868 | "name": "Portugal",
869 | "dial_code": "+351",
870 | "code": "PT"
871 | },
872 | {
873 | "name": "Puerto Rico",
874 | "dial_code": "+1",
875 | "code": "PR"
876 | },
877 | {
878 | "name": "Qatar",
879 | "dial_code": "+974",
880 | "code": "QA"
881 | },
882 | {
883 | "name": "Romania",
884 | "dial_code": "+40",
885 | "code": "RO"
886 | },
887 | {
888 | "name": "Russia",
889 | "dial_code": "+7",
890 | "code": "RU"
891 | },
892 | {
893 | "name": "Rwanda",
894 | "dial_code": "+250",
895 | "code": "RW"
896 | },
897 | {
898 | "name": "Reunion",
899 | "dial_code": "+262",
900 | "code": "RE"
901 | },
902 | {
903 | "name": "Saint Barthelemy",
904 | "dial_code": "+590",
905 | "code": "BL"
906 | },
907 | {
908 | "name": "Saint Helena, Ascension and Tristan Da Cunha",
909 | "dial_code": "+290",
910 | "code": "SH"
911 | },
912 | {
913 | "name": "Saint Kitts and Nevis",
914 | "dial_code": "+1869",
915 | "code": "KN"
916 | },
917 | {
918 | "name": "Saint Lucia",
919 | "dial_code": "+1758",
920 | "code": "LC"
921 | },
922 | {
923 | "name": "Saint Martin",
924 | "dial_code": "+590",
925 | "code": "MF"
926 | },
927 | {
928 | "name": "Saint Pierre and Miquelon",
929 | "dial_code": "+508",
930 | "code": "PM"
931 | },
932 | {
933 | "name": "Saint Vincent and the Grenadines",
934 | "dial_code": "+1784",
935 | "code": "VC"
936 | },
937 | {
938 | "name": "Samoa",
939 | "dial_code": "+685",
940 | "code": "WS"
941 | },
942 | {
943 | "name": "San Marino",
944 | "dial_code": "+378",
945 | "code": "SM"
946 | },
947 | {
948 | "name": "Sao Tome and Principe",
949 | "dial_code": "+239",
950 | "code": "ST"
951 | },
952 | {
953 | "name": "Saudi Arabia",
954 | "dial_code": "+966",
955 | "code": "SA"
956 | },
957 | {
958 | "name": "Senegal",
959 | "dial_code": "+221",
960 | "code": "SN"
961 | },
962 | {
963 | "name": "Serbia",
964 | "dial_code": "+381",
965 | "code": "RS"
966 | },
967 | {
968 | "name": "Seychelles",
969 | "dial_code": "+248",
970 | "code": "SC"
971 | },
972 | {
973 | "name": "Sierra Leone",
974 | "dial_code": "+232",
975 | "code": "SL"
976 | },
977 | {
978 | "name": "Singapore",
979 | "dial_code": "+65",
980 | "code": "SG"
981 | },
982 | {
983 | "name": "Sint Maarten",
984 | "dial_code": "+1721",
985 | "code": "SX"
986 | },
987 | {
988 | "name": "Slovakia",
989 | "dial_code": "+421",
990 | "code": "SK"
991 | },
992 | {
993 | "name": "Slovenia",
994 | "dial_code": "+386",
995 | "code": "SI"
996 | },
997 | {
998 | "name": "Solomon Islands",
999 | "dial_code": "+677",
1000 | "code": "SB"
1001 | },
1002 | {
1003 | "name": "Somalia",
1004 | "dial_code": "+252",
1005 | "code": "SO"
1006 | },
1007 | {
1008 | "name": "South Africa",
1009 | "dial_code": "+27",
1010 | "code": "ZA"
1011 | },
1012 | {
1013 | "name": "South Georgia and the South Sandwich Islands",
1014 | "dial_code": "+500",
1015 | "code": "GS"
1016 | },
1017 | {
1018 | "name": "South Korea",
1019 | "dial_code": "+82",
1020 | "code": "KR"
1021 | },
1022 | {
1023 | "name": "South Sudan",
1024 | "dial_code": "+211",
1025 | "code": "SS"
1026 | },
1027 | {
1028 | "name": "Spain",
1029 | "dial_code": "+34",
1030 | "code": "ES"
1031 | },
1032 | {
1033 | "name": "Sri Lanka",
1034 | "dial_code": "+94",
1035 | "code": "LK"
1036 | },
1037 | {
1038 | "name": "Sudan",
1039 | "dial_code": "+249",
1040 | "code": "SD"
1041 | },
1042 | {
1043 | "name": "Suriname",
1044 | "dial_code": "+597",
1045 | "code": "SR"
1046 | },
1047 | {
1048 | "name": "Svalbard and Jan Mayen",
1049 | "dial_code": "+47",
1050 | "code": "SJ"
1051 | },
1052 | {
1053 | "name": "Swaziland",
1054 | "dial_code": "+268",
1055 | "code": "SZ"
1056 | },
1057 | {
1058 | "name": "Sweden",
1059 | "dial_code": "+46",
1060 | "code": "SE"
1061 | },
1062 | {
1063 | "name": "Switzerland",
1064 | "dial_code": "+41",
1065 | "code": "CH"
1066 | },
1067 | {
1068 | "name": "Syrian Arab Republic",
1069 | "dial_code": "+963",
1070 | "code": "SY"
1071 | },
1072 | {
1073 | "name": "Taiwan",
1074 | "dial_code": "+886",
1075 | "code": "TW"
1076 | },
1077 | {
1078 | "name": "Tajikistan",
1079 | "dial_code": "+992",
1080 | "code": "TJ"
1081 | },
1082 | {
1083 | "name": "Tanzania, United Republic of Tanzania",
1084 | "dial_code": "+255",
1085 | "code": "TZ"
1086 | },
1087 | {
1088 | "name": "Thailand",
1089 | "dial_code": "+66",
1090 | "code": "TH"
1091 | },
1092 | {
1093 | "name": "Timor-Leste",
1094 | "dial_code": "+670",
1095 | "code": "TL"
1096 | },
1097 | {
1098 | "name": "Togo",
1099 | "dial_code": "+228",
1100 | "code": "TG"
1101 | },
1102 | {
1103 | "name": "Tokelau",
1104 | "dial_code": "+690",
1105 | "code": "TK"
1106 | },
1107 | {
1108 | "name": "Tonga",
1109 | "dial_code": "+676",
1110 | "code": "TO"
1111 | },
1112 | {
1113 | "name": "Trinidad and Tobago",
1114 | "dial_code": "+1868",
1115 | "code": "TT"
1116 | },
1117 | {
1118 | "name": "Tunisia",
1119 | "dial_code": "+216",
1120 | "code": "TN"
1121 | },
1122 | {
1123 | "name": "Turkey",
1124 | "dial_code": "+90",
1125 | "code": "TR"
1126 | },
1127 | {
1128 | "name": "Turkmenistan",
1129 | "dial_code": "+993",
1130 | "code": "TM"
1131 | },
1132 | {
1133 | "name": "Turks and Caicos Islands",
1134 | "dial_code": "+1649",
1135 | "code": "TC"
1136 | },
1137 | {
1138 | "name": "Tuvalu",
1139 | "dial_code": "+688",
1140 | "code": "TV"
1141 | },
1142 | {
1143 | "name": "Uganda",
1144 | "dial_code": "+256",
1145 | "code": "UG"
1146 | },
1147 | {
1148 | "name": "Ukraine",
1149 | "dial_code": "+380",
1150 | "code": "UA"
1151 | },
1152 | {
1153 | "name": "United Arab Emirates",
1154 | "dial_code": "+971",
1155 | "code": "AE"
1156 | },
1157 | {
1158 | "name": "United Kingdom",
1159 | "dial_code": "+44",
1160 | "code": "GB"
1161 | },
1162 | {
1163 | "name": "United States",
1164 | "dial_code": "+1",
1165 | "code": "US"
1166 | },
1167 | {
1168 | "name": "Uruguay",
1169 | "dial_code": "+598",
1170 | "code": "UY"
1171 | },
1172 | {
1173 | "name": "Uzbekistan",
1174 | "dial_code": "+998",
1175 | "code": "UZ"
1176 | },
1177 | {
1178 | "name": "Vanuatu",
1179 | "dial_code": "+678",
1180 | "code": "VU"
1181 | },
1182 | {
1183 | "name": "Venezuela, Bolivarian Republic of Venezuela",
1184 | "dial_code": "+58",
1185 | "code": "VE"
1186 | },
1187 | {
1188 | "name": "Vietnam",
1189 | "dial_code": "+84",
1190 | "code": "VN"
1191 | },
1192 | {
1193 | "name": "Virgin Islands, British",
1194 | "dial_code": "+1284",
1195 | "code": "VG"
1196 | },
1197 | {
1198 | "name": "Virgin Islands, U.S.",
1199 | "dial_code": "+1340",
1200 | "code": "VI"
1201 | },
1202 | {
1203 | "name": "Wallis and Futuna",
1204 | "dial_code": "+681",
1205 | "code": "WF"
1206 | },
1207 | {
1208 | "name": "Yemen",
1209 | "dial_code": "+967",
1210 | "code": "YE"
1211 | },
1212 | {
1213 | "name": "Zambia",
1214 | "dial_code": "+260",
1215 | "code": "ZM"
1216 | },
1217 | {
1218 | "name": "Zimbabwe",
1219 | "dial_code": "+263",
1220 | "code": "ZW"
1221 | }
1222 | ]
1223 |
--------------------------------------------------------------------------------