├── .github
└── workflows
│ ├── android_implementation_package.yml
│ ├── app_facing_package.yml
│ ├── docs.yml
│ ├── ios_implementation_package.yml
│ ├── platform_interface_package.yml
│ └── web_implementation_package.yml
├── .gitignore
├── .markdownlint-cli2.yaml
├── .tool-versions
├── LICENSE
├── README.md
├── flutter_custom_tabs
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── doc
│ └── migration-guides.md
├── example
│ ├── .gitignore
│ ├── .metadata
│ ├── .pluginToolsConfig.yaml
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── app
│ │ │ ├── build.gradle
│ │ │ └── src
│ │ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ │ ├── main
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ └── res
│ │ │ │ │ ├── anim
│ │ │ │ │ ├── slide_down.xml
│ │ │ │ │ └── slide_up.xml
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── values-night
│ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle
│ │ ├── gradle.properties
│ │ └── settings.gradle
│ ├── ios
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── Flutter
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Podfile
│ │ ├── Podfile.lock
│ │ ├── Runner.xcodeproj
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata
│ │ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ ├── Runner.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ └── Runner
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ │ └── LaunchImage.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ └── LaunchImage@3x.png
│ │ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ ├── Resources
│ │ │ ├── Assets.xcassets
│ │ │ │ ├── AppIcon.appiconset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Icon-App-1024x1024@1x.png
│ │ │ │ └── LaunchImage.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── LaunchImage.png
│ │ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ │ └── LaunchImage@3x.png
│ │ │ └── Base.lproj
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ └── Runner-Bridging-Header.h
│ ├── lib
│ │ └── main.dart
│ ├── pubspec.yaml
│ └── web
│ │ ├── favicon.png
│ │ ├── icons
│ │ ├── Icon-192.png
│ │ └── Icon-512.png
│ │ ├── index.html
│ │ └── manifest.json
├── lib
│ ├── flutter_custom_tabs.dart
│ ├── flutter_custom_tabs_lite.dart
│ └── src
│ │ ├── launcher.dart
│ │ └── lite
│ │ ├── launch_options.dart
│ │ ├── launcher_lite.dart
│ │ └── type_conversion.dart
├── pubspec.yaml
└── test
│ ├── launcher_test.dart
│ ├── lite
│ ├── launch_options_test.dart
│ └── launcher_lite_test.dart
│ └── mocks
│ └── mock_custom_tabs_platform.dart
├── flutter_custom_tabs_android
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle.properties
│ ├── settings.gradle
│ └── src
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── github
│ │ │ │ └── droibit
│ │ │ │ └── flutter
│ │ │ │ └── plugins
│ │ │ │ └── customtabs
│ │ │ │ ├── CustomTabsLauncher.kt
│ │ │ │ ├── CustomTabsPlugin.kt
│ │ │ │ ├── Messages.kt
│ │ │ │ └── core
│ │ │ │ ├── CustomTabsIntentFactory.kt
│ │ │ │ ├── ExternalBrowserLauncher.kt
│ │ │ │ ├── NativeAppLauncher.kt
│ │ │ │ ├── PartialCustomTabsLauncher.kt
│ │ │ │ ├── ResourceFactory.kt
│ │ │ │ ├── options
│ │ │ │ ├── BrowserConfiguration.kt
│ │ │ │ ├── CustomTabsAnimations.kt
│ │ │ │ ├── CustomTabsCloseButton.kt
│ │ │ │ ├── CustomTabsColorSchemes.kt
│ │ │ │ ├── CustomTabsIntentOptions.kt
│ │ │ │ ├── CustomTabsSessionOptions.kt
│ │ │ │ └── PartialCustomTabsConfiguration.kt
│ │ │ │ ├── session
│ │ │ │ ├── CustomTabsSessionController.kt
│ │ │ │ ├── CustomTabsSessionManager.kt
│ │ │ │ └── CustomTabsSessionProvider.kt
│ │ │ │ └── utils
│ │ │ │ └── Utils.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ └── fct_ic_arrow_back.xml
│ │ │ └── raw
│ │ │ └── keep.xml
│ │ └── test
│ │ ├── kotlin
│ │ └── com
│ │ │ └── github
│ │ │ └── droibit
│ │ │ └── flutter
│ │ │ └── plugins
│ │ │ └── customtabs
│ │ │ ├── CustomTabsLauncherTest.kt
│ │ │ └── core
│ │ │ ├── CustomTabsIntentFactoryTest.kt
│ │ │ ├── ExternalBrowserLauncherTest.kt
│ │ │ ├── PartialCustomTabsLauncherTest.kt
│ │ │ ├── options
│ │ │ ├── BrowserConfigurationBuilderTest.kt
│ │ │ ├── CustomTabsAnimationsBuilderTest.kt
│ │ │ ├── CustomTabsCloseButtonBuilderTest.kt
│ │ │ ├── CustomTabsColorSchemesBuilderTest.kt
│ │ │ ├── CustomTabsIntentOptionsBuilderTest.kt
│ │ │ ├── CustomTabsSessionOptionsBuilderTest.kt
│ │ │ ├── CustomTabsSessionOptionsTest.kt
│ │ │ └── PartialCustomTabsConfigurationBuilderTest.kt
│ │ │ └── session
│ │ │ ├── CustomTabsSessionControllerTest.kt
│ │ │ └── CustomTabsSessionManagerTest.kt
│ │ └── resources
│ │ └── robolectric.properties
├── example
│ ├── .gitignore
│ ├── .pluginToolsConfig.yaml
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── app
│ │ │ ├── build.gradle
│ │ │ └── src
│ │ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ │ ├── main
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ │ └── github
│ │ │ │ │ │ └── droibit
│ │ │ │ │ │ └── plugins
│ │ │ │ │ │ └── flutter
│ │ │ │ │ │ └── customtabs
│ │ │ │ │ │ └── android
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable
│ │ │ │ │ ├── ic_round_close.xml
│ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── values-night
│ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle
│ │ ├── gradle.properties
│ │ └── settings.gradle
│ ├── lib
│ │ └── main.dart
│ └── pubspec.yaml
├── lib
│ ├── flutter_custom_tabs_android.dart
│ └── src
│ │ ├── custom_tabs_plugin_android.dart
│ │ ├── messages
│ │ ├── messages.dart
│ │ ├── messages.g.dart
│ │ └── type_conversion.dart
│ │ └── types
│ │ ├── custom_tabs_animations.dart
│ │ ├── custom_tabs_browser.dart
│ │ ├── custom_tabs_close_button.dart
│ │ ├── custom_tabs_color_schemes.dart
│ │ ├── custom_tabs_options.dart
│ │ ├── custom_tabs_session.dart
│ │ ├── custom_tabs_share_state.dart
│ │ ├── partial_custom_tabs.dart
│ │ └── types.dart
├── pigeons
│ └── messages.dart
├── pubspec.yaml
└── test
│ ├── custom_tabs_plugin_android_test.dart
│ └── types
│ ├── custom_tabs_animations_test.dart
│ ├── custom_tabs_browser_test.dart
│ ├── custom_tabs_close_button_test.dart
│ ├── custom_tabs_color_schemes_test.dart
│ ├── custom_tabs_options_test.dart
│ ├── custom_tabs_session_test.dart
│ ├── custom_tabs_share_state_test.dart
│ └── partial_custom_tabs_test.dart
├── flutter_custom_tabs_ios
├── .gitignore
├── .metadata
├── .swiftformat
├── .swiftlint.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── Mintfile
├── README.md
├── analysis_options.yaml
├── example
│ ├── .gitignore
│ ├── Makefile
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── ios
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── Flutter
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Podfile
│ │ ├── Podfile.lock
│ │ ├── Runner.xcodeproj
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata
│ │ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ ├── Runner.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ ├── Runner
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Info.plist
│ │ │ ├── Resources
│ │ │ │ ├── Assets.xcassets
│ │ │ │ │ ├── AppIcon.appiconset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ └── Icon-App-1024x1024@1x.png
│ │ │ │ │ └── LaunchImage.imageset
│ │ │ │ │ │ ├── Contents.json
│ │ │ │ │ │ ├── LaunchImage.png
│ │ │ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ │ │ └── LaunchImage@3x.png
│ │ │ │ └── Base.lproj
│ │ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ │ └── Main.storyboard
│ │ │ └── Runner-Bridging-Header.h
│ │ └── RunnerTests
│ │ │ ├── CustomTabsPluginTest.swift
│ │ │ ├── MockLauncher.swift
│ │ │ └── SFSafariViewController+FactoryTest.swift
│ ├── lib
│ │ └── main.dart
│ └── pubspec.yaml
├── ios
│ ├── .gitignore
│ ├── Assets
│ │ └── .gitkeep
│ ├── flutter_custom_tabs_ios.podspec
│ └── flutter_custom_tabs_ios
│ │ ├── Package.swift
│ │ └── Sources
│ │ └── flutter_custom_tabs_ios
│ │ ├── CustomTabsPlugin.swift
│ │ ├── Launcher.swift
│ │ ├── Resources
│ │ └── PrivacyInfo.xcprivacy
│ │ ├── SFSafariViewController+Factory.swift
│ │ └── messages.g.swift
├── lib
│ ├── flutter_custom_tabs_ios.dart
│ └── src
│ │ ├── custom_tabs_plugin_ios.dart
│ │ ├── messages
│ │ ├── messages.dart
│ │ ├── messages.g.dart
│ │ └── type_conversion.dart
│ │ └── types
│ │ ├── safari_view_controller_options.dart
│ │ ├── safari_view_prewarming_session.dart
│ │ ├── sheet_presentation_controller.dart
│ │ └── types.dart
├── pigeons
│ └── messages.dart
├── pubspec.yaml
└── test
│ ├── custom_tabs_plugin_ios_ios_test.dart
│ └── types
│ ├── safari_view_controller_options_test.dart
│ └── sheet_presentation_controller_test.dart
├── flutter_custom_tabs_platform_interface
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── lib
│ ├── flutter_custom_tabs_platform_interface.dart
│ └── src
│ │ ├── custom_tabs_platform.dart
│ │ ├── method_channel_custom_tabs.dart
│ │ └── types.dart
├── pubspec.yaml
└── test
│ ├── custom_tabs_platform_test.dart
│ └── method_channel_custom_tabs_test.dart
└── flutter_custom_tabs_web
├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── example
├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── integration_test
│ ├── flutter_custom_tabs_web_test.dart
│ └── mock_url_launcher_plugin.dart
├── lib
│ └── main.dart
├── pubspec.yaml
├── run_test.sh
├── test_driver
│ └── integration_test.dart
└── web
│ ├── favicon.png
│ ├── icons
│ ├── Icon-192.png
│ └── Icon-512.png
│ ├── index.html
│ └── manifest.json
├── lib
└── flutter_custom_tabs_web.dart
└── pubspec.yaml
/.github/workflows/android_implementation_package.yml:
--------------------------------------------------------------------------------
1 | name: CI for android implementation package
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - develop
8 | paths:
9 | - 'flutter_custom_tabs_android/**'
10 | - '.github/workflows/android_implementation_package.yml'
11 | - '!**.md'
12 | pull_request:
13 | paths:
14 | - 'flutter_custom_tabs_android/**'
15 | - '.github/workflows/android_implementation_package.yml'
16 | - '!**.md'
17 |
18 | jobs:
19 | build:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Setup JDK
24 | uses: actions/setup-java@v4
25 | with:
26 | distribution: 'zulu'
27 | java-version: 17
28 | - name: Setup Gradle
29 | uses: gradle/actions/setup-gradle@v4
30 | - uses: subosito/flutter-action@v2
31 | with:
32 | channel: 'stable'
33 | - name: Get flutter dependencies
34 | run: flutter pub get
35 | working-directory: ./flutter_custom_tabs_android
36 | - name: Check for any formatting issues in the code
37 | run: dart format -o none --set-exit-if-changed $(find ./lib ./test -name "*.dart" -not -name "*.*g.dart")
38 | working-directory: ./flutter_custom_tabs_android
39 | - name: Statically analyze the Dart code for any errors
40 | run: flutter analyze .
41 | working-directory: ./flutter_custom_tabs_android
42 | - name: Run Flutter unit tests
43 | run: flutter test
44 | working-directory: ./flutter_custom_tabs_android
45 | - name: Build example android app
46 | run: flutter build apk --release
47 | working-directory: ./flutter_custom_tabs_android/example
48 | - name: Run Android lint
49 | run: ./gradlew :flutter_custom_tabs_android:lint
50 | working-directory: ./flutter_custom_tabs_android/example/android
51 | - name: Run Android unit tests
52 | run: ./gradlew :flutter_custom_tabs_android:testDebugUnitTest
53 | working-directory: ./flutter_custom_tabs_android/example/android
--------------------------------------------------------------------------------
/.github/workflows/app_facing_package.yml:
--------------------------------------------------------------------------------
1 | name: CI for app-facing package
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - develop
8 | paths:
9 | - 'flutter_custom_tabs/**'
10 | - '!**.md'
11 | pull_request:
12 | paths:
13 | - 'flutter_custom_tabs/**'
14 | - '!**.md'
15 | env:
16 | DEVELOPER_DIR: /Applications/Xcode_16.2.app
17 |
18 | jobs:
19 | build:
20 | runs-on: macos-15
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Setup JDK
24 | uses: actions/setup-java@v4
25 | with:
26 | distribution: 'zulu'
27 | java-version: 17
28 | - name: Setup Gradle
29 | uses: gradle/actions/setup-gradle@v4
30 | - uses: subosito/flutter-action@v2
31 | with:
32 | channel: 'stable'
33 | - name: Get flutter dependencies
34 | run: flutter pub get
35 | working-directory: ./flutter_custom_tabs
36 | - name: Check for any formatting issues in the code
37 | run: dart format --set-exit-if-changed .
38 | working-directory: ./flutter_custom_tabs
39 | - name: Statically analyze the Dart code for any errors
40 | run: flutter analyze .
41 | working-directory: ./flutter_custom_tabs
42 | - name: Run unit tests
43 | run: flutter test
44 | working-directory: ./flutter_custom_tabs
45 | - name: Build example android app
46 | run: flutter build apk --release
47 | working-directory: ./flutter_custom_tabs/example
48 | - name: Build example iOS app
49 | run: flutter build ios --no-codesign
50 | working-directory: ./flutter_custom_tabs/example
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: CI for all of docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - develop
8 | paths:
9 | - '**.md'
10 | - '.github/workflows/docs.yml'
11 | pull_request:
12 | paths:
13 | - '**.md'
14 | - '.github/workflows/docs.yml'
15 |
16 | jobs:
17 | lint:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: DavidAnson/markdownlint-cli2-action@v16
22 | with:
23 | globs: '**/*.md'
--------------------------------------------------------------------------------
/.github/workflows/ios_implementation_package.yml:
--------------------------------------------------------------------------------
1 | name: CI for iOS implementation package
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - develop
8 | paths:
9 | - 'flutter_custom_tabs_ios/**'
10 | - '.github/workflows/ios_implementation_package.yml'
11 | - '!**.md'
12 | pull_request:
13 | paths:
14 | - 'flutter_custom_tabs_ios/**'
15 | - '.github/workflows/ios_implementation_package.yml'
16 | - '!**.md'
17 | env:
18 | DEVELOPER_DIR: /Applications/Xcode_16.3.app
19 |
20 | jobs:
21 | build:
22 | runs-on: macos-15
23 | steps:
24 | - uses: actions/checkout@v4
25 | - uses: subosito/flutter-action@v2
26 | with:
27 | channel: 'stable'
28 | - uses: irgaly/setup-mint@v1
29 | with:
30 | mint-directory: ./flutter_custom_tabs_ios
31 | - name: Get flutter dependencies
32 | run: flutter pub get
33 | working-directory: ./flutter_custom_tabs_ios
34 | - name: Check for any formatting issues in the code
35 | run: dart format -o none --set-exit-if-changed $(find ./lib ./test -name "*.dart" -not -name "*.*g.dart")
36 | working-directory: ./flutter_custom_tabs_ios
37 | - name: Statically analyze the Dart code for any errors
38 | run: flutter analyze .
39 | working-directory: ./flutter_custom_tabs_ios
40 | - name: Statically analyze the Swift code for any errors
41 | run: make lint
42 | working-directory: ./flutter_custom_tabs_ios
43 | - name: Run flutter unit tests
44 | run: flutter test
45 | working-directory: ./flutter_custom_tabs_ios
46 | - name: Build example iOS app
47 | run: flutter build ios --no-codesign
48 | working-directory: ./flutter_custom_tabs_ios/example
49 | - name: Run iOS unit tests
50 | run: make test
51 | working-directory: ./flutter_custom_tabs_ios/example
52 |
53 | spm-integration:
54 | runs-on: macos-15
55 | steps:
56 | - uses: actions/checkout@v4
57 | - uses: subosito/flutter-action@v2
58 | with:
59 | channel: 'stable'
60 | - name: Enable Swift Package Manager integration
61 | run: flutter config --enable-swift-package-manager
62 | - name: Get flutter dependencies
63 | run: flutter pub get
64 | working-directory: ./flutter_custom_tabs_ios
65 | - name: Build example iOS app with SPM enabled
66 | run: flutter build ios --no-codesign
67 | working-directory: ./flutter_custom_tabs_ios/example
68 |
--------------------------------------------------------------------------------
/.github/workflows/platform_interface_package.yml:
--------------------------------------------------------------------------------
1 | name: CI for platform interface package
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - develop
8 | paths:
9 | - 'flutter_custom_tabs_platform_interface/**'
10 | - '.github/workflows/platform_interface_package.yml'
11 | - '!**.md'
12 | pull_request:
13 | paths:
14 | - 'flutter_custom_tabs_platform_interface/**'
15 | - '.github/workflows/platform_interface_package.yml'
16 | - '!**.md'
17 |
18 | jobs:
19 | build:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | - uses: subosito/flutter-action@v2
24 | with:
25 | channel: 'stable'
26 | - name: Get flutter dependencies
27 | run: flutter pub get
28 | working-directory: ./flutter_custom_tabs_platform_interface
29 | - name: Check for any formatting issues in the code
30 | run: dart format --set-exit-if-changed .
31 | working-directory: ./flutter_custom_tabs_platform_interface
32 | - name: Statically analyze the Dart code for any errors
33 | run: flutter analyze .
34 | working-directory: ./flutter_custom_tabs_platform_interface
35 | - name: Run unit tests
36 | run: flutter test
37 | working-directory: ./flutter_custom_tabs_platform_interface
--------------------------------------------------------------------------------
/.github/workflows/web_implementation_package.yml:
--------------------------------------------------------------------------------
1 |
2 | name: CI for the web implementation package
3 |
4 | on:
5 | push:
6 | branches:
7 | - main
8 | - develop
9 | paths:
10 | - 'flutter_custom_tabs_web/**'
11 | - '.github/workflows/web_implementation_package.yml'
12 | - '!**.md'
13 | pull_request:
14 | paths:
15 | - 'flutter_custom_tabs_web/**'
16 | - '.github/workflows/web_implementation_package.yml'
17 | - '!**.md'
18 |
19 | jobs:
20 | build:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - uses: actions/checkout@v4
24 | - uses: subosito/flutter-action@v2
25 | with:
26 | channel: 'stable'
27 | - name: Get flutter dependencies
28 | run: flutter pub get
29 | working-directory: ./flutter_custom_tabs_web
30 | - name: Check for any formatting issues in the code
31 | run: dart format --set-exit-if-changed .
32 | working-directory: ./flutter_custom_tabs_web
33 | - name: Statically analyze the Dart code for any errors
34 | run: flutter analyze .
35 | working-directory: ./flutter_custom_tabs_web
36 | # TODO: Consider how to do integration tests on GitHub Actions.
37 | # - uses: nanasess/setup-chromedriver@master
38 | # - name: Start ChromeDriver
39 | # run: chromedriver --port=444
40 | # - name: Run unit tests
41 | # run: ./run_test.sh
42 | # working-directory: ./flutter_custom_tabs_web/example
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Intellij project files
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .idea/
6 |
--------------------------------------------------------------------------------
/.markdownlint-cli2.yaml:
--------------------------------------------------------------------------------
1 | # Extended Markdownlint configuration in doc/.markdownlint/
2 | # See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for explanations of each rule
3 | config:
4 | # First, set the default
5 | default: true
6 |
7 | # MD001
8 | heading-increment: false
9 | # MD004
10 | ul-style:
11 | style: dash
12 | # MD013
13 | line-length: false
14 | # MD024
15 | no-duplicate-heading:
16 | siblings_only: true
17 | # MD025
18 | single-title: false
19 | # MD026
20 | no-trailing-punctuation:
21 | punctuation: ".,;:!。,;:!?"
22 | # MD029
23 | ol-prefix:
24 | style: one
25 | # MD033
26 | no-inline-html:
27 | allowed_elements:
28 | - br
29 | - table
30 | - tr
31 | - td
32 | # MD035
33 | hr-style:
34 | style: "---"
35 | # MD41
36 | first-line-h1: false
37 | # MD046
38 | code-block-style:
39 | style: fenced
40 | ignores:
41 | - LICENSE
42 | - "**/.symlinks/**"
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | flutter 3.19.3-stable
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter Custom Tabs Plugin
2 |
3 | A Flutter plugin to launch a URL in Custom Tabs with a similar feel to [url_launcher](https://pub.dev/packages/url_launcher),
4 | which allows you to add the browser experience that Custom Tabs provides to your mobile apps.
5 |
6 | This plugin is built as federated plugins, see the app facing package [flutter_custom_tabs](./flutter_custom_tabs) for details.
7 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/.gitignore:
--------------------------------------------------------------------------------
1 | # Intellij project files
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .idea/
6 | !.idea/codeStyleSettings.xml
7 | !.idea/runConfigurations/*
8 |
9 | # Files and directories created by pub
10 | .dart_tool/
11 | .packages
12 | .pub/
13 | build/
14 | # Remove the following pattern if you wish to check in your lock file
15 | pubspec.lock
16 |
17 | # Directory created by dartdoc
18 | doc/api/
19 |
20 | # Test Coverage
21 | /coverage/
22 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 | .flutter-plugins-dependencies
34 |
35 | # Web related
36 | lib/generated_plugin_registrant.dart
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 |
44 | # Android Studio will place build artifacts here
45 | /android/app/debug
46 | /android/app/profile
47 | /android/app/release
48 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 3352a3fb488df4742ff323243d3dc44d9b7cd3e8
8 | channel: dev
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/.pluginToolsConfig.yaml:
--------------------------------------------------------------------------------
1 | buildFlags:
2 | _pluginToolsConfigGlobalKey:
3 | - "--no-tree-shake-icons"
4 | - "--dart-define=buildmode=testing"
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_custom_tabs_example
2 |
3 | Demonstrates how to use the flutter_custom_tabs plugin.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
8 |
9 | linter:
10 | rules:
11 | depend_on_referenced_packages: false
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{kt,kts}]
12 | ij_kotlin_imports_layout = *
13 | ij_kotlin_allow_trailing_comma = true
14 | ij_kotlin_allow_trailing_comma_on_call_site = true
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | # Local configuration file (sdk path, etc)
2 | local.properties
3 |
4 | # Intellij project files
5 | *.iml
6 | *.ipr
7 | *.iws
8 | .idea/*
9 |
10 | # Android Studio files
11 | /captures
12 | .externalNativeBuild/
13 | projectFilesBackup/
14 | output.json
15 |
16 | # Gradle files
17 | /.gradle
18 | /build
19 | /gradle
20 | /gradlew
21 | /gradlew.bat
22 |
23 | .DS_Store
24 |
25 | # Flutter files
26 | GeneratedPluginRegistrant.java
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | android {
8 | namespace "com.github.droibit.flutter.plugins.customtabs.example"
9 | compileSdk flutter.compileSdkVersion
10 | ndkVersion flutter.ndkVersion
11 |
12 | defaultConfig {
13 | applicationId = "com.github.droibit.flutter.plugins.customtabs.example"
14 | minSdk = flutter.minSdkVersion
15 | targetSdk = flutter.targetSdkVersion
16 | versionCode = 1
17 | versionName = "1.0"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled true
23 | shrinkResources true
24 | // Signing with the debug keys for now, so `flutter run --release` works.
25 | signingConfig signingConfigs.debug
26 | }
27 | }
28 |
29 | lint {
30 | disable "InvalidPackage"
31 | }
32 | }
33 |
34 | flutter {
35 | source "../.."
36 | }
37 |
38 | dependencies {}
39 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
13 |
21 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
36 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/anim/slide_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/anim/slide_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 |
7 | gradle.projectsEvaluated {
8 | tasks.withType(JavaCompile).configureEach {
9 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
10 | }
11 | }
12 | }
13 |
14 | rootProject.buildDir = '../build'
15 | subprojects {
16 | project.buildDir = "${rootProject.buildDir}/${project.name}"
17 | }
18 | subprojects {
19 | project.evaluationDependsOn(':app')
20 | }
21 |
22 | tasks.register("clean", Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.enableJetifier=true
3 | android.useAndroidX=true
4 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.1.4" apply false
22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{swift,h,m}]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | max_line_length = 120
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | UIRequiredDeviceCapabilities
24 |
25 | arm64
26 |
27 | MinimumOSVersion
28 | 12.0
29 |
30 |
31 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_custom_tabs_ios (2.4.0):
4 | - Flutter
5 |
6 | DEPENDENCIES:
7 | - Flutter (from `Flutter`)
8 | - flutter_custom_tabs_ios (from `.symlinks/plugins/flutter_custom_tabs_ios/ios`)
9 |
10 | EXTERNAL SOURCES:
11 | Flutter:
12 | :path: Flutter
13 | flutter_custom_tabs_ios:
14 | :path: ".symlinks/plugins/flutter_custom_tabs_ios/ios"
15 |
16 | SPEC CHECKSUMS:
17 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
18 | flutter_custom_tabs_ios: 89e60122b553c69a79bfd45eb8eb99d911c1a9c0
19 |
20 | PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048
21 |
22 | COCOAPODS: 1.15.2
23 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Flutter Custom Tabs Example
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | flutter_custom_tabs_example
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UIApplicationSupportsIndirectInputEvents
30 |
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | UIMainStoryboardFile
34 | Main
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-App-1024x1024@1x.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Resources/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "GeneratedPluginRegistrant.h"
6 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs_example
2 | description: Demonstrates how to use the flutter_custom_tabs plugin.
3 |
4 | publish_to: none
5 |
6 | environment:
7 | sdk: ^3.3.0
8 | flutter: ">=3.19.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 | flutter_custom_tabs:
14 | path: ../
15 | cupertino_icons: ^1.0.2
16 |
17 | dev_dependencies:
18 | flutter_test:
19 | sdk: flutter
20 | flutter_lints: ^4.0.0
21 |
22 | flutter:
23 | uses-material-design: true
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/web/favicon.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/flutter_custom_tabs/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/lib/flutter_custom_tabs.dart:
--------------------------------------------------------------------------------
1 | export 'package:flutter_custom_tabs_android/flutter_custom_tabs_android.dart'
2 | hide CustomTabsPluginAndroid, CustomTabsOptionsConverter;
3 | export 'package:flutter_custom_tabs_ios/flutter_custom_tabs_ios.dart'
4 | hide CustomTabsPluginIOS;
5 |
6 | export 'src/launcher.dart';
7 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/lib/flutter_custom_tabs_lite.dart:
--------------------------------------------------------------------------------
1 | export 'src/lite/launch_options.dart';
2 | export 'src/lite/launcher_lite.dart';
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/lib/src/lite/launch_options.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// The Configuration for providing minimal options for mobile platforms when launching Custom Tabs by specifying a URL.
4 | @immutable
5 | class LaunchOptions {
6 | /// Creates a [LaunchOptions] instance with the specified options.
7 | const LaunchOptions({
8 | this.barColor,
9 | this.onBarColor,
10 | this.systemNavigationBarParams,
11 | this.barFixingEnabled,
12 | });
13 |
14 | /// The background color of the app bar and bottom bar.
15 | final Color? barColor;
16 |
17 | /// The color to tint the control buttons on the app bar and bottom bar.
18 | ///
19 | /// - Availability: **Only for iOS**
20 | final Color? onBarColor;
21 |
22 | /// The color configuration of the system navigation bar.
23 | ///
24 | /// - Availability: **Only for Android**
25 | final SystemNavigationBarParams? systemNavigationBarParams;
26 |
27 | /// A Boolean value that indicates whether to keep the app bar fixed, even when scrolling through the page.
28 | final bool? barFixingEnabled;
29 | }
30 |
31 | /// The color configuration of the system navigation bar.
32 | @immutable
33 | class SystemNavigationBarParams {
34 | /// The color of the system navigation bar.
35 | final Color backgroundColor;
36 |
37 | /// The color of the system navigation bar divider.
38 | final Color? dividerColor;
39 |
40 | const SystemNavigationBarParams({
41 | required this.backgroundColor,
42 | this.dividerColor,
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/lib/src/lite/launcher_lite.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
4 |
5 | import 'launch_options.dart';
6 | import 'type_conversion.dart';
7 |
8 | /// Passes [url] with options to the underlying platform for launching a Custom Tab.
9 | ///
10 | /// Example:
11 | ///
12 | /// ```dart
13 | /// final theme = ...;
14 | /// try {
15 | /// await launchUrl(
16 | /// Uri.parse('https://flutter.dev'),
17 | /// options: LaunchOptions(
18 | /// barColor: theme.colorScheme.surface,
19 | /// onBarColor: theme.colorScheme.onSurface,
20 | /// barFixingEnabled: false,
21 | /// ),
22 | /// );
23 | /// } catch (e) {
24 | /// // If the URL launch fails, an exception will be thrown. (For example, if no browser app is installed on the Android device.)
25 | /// }
26 | Future launchUrl(
27 | Uri url, {
28 | bool prefersDeepLink = false,
29 | LaunchOptions options = const LaunchOptions(),
30 | }) async {
31 | if (url.scheme != 'http' && url.scheme != 'https') {
32 | throw ArgumentError.value(
33 | url,
34 | 'url',
35 | 'must have an http or https scheme.',
36 | );
37 | }
38 |
39 | await CustomTabsPlatform.instance.launch(
40 | url.toString(),
41 | prefersDeepLink: prefersDeepLink,
42 | customTabsOptions: options.toCustomTabsOptions(),
43 | safariVCOptions: options.toSafariViewControllerOptions(),
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/lib/src/lite/type_conversion.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_android/flutter_custom_tabs_android.dart';
2 | import 'package:flutter_custom_tabs_ios/flutter_custom_tabs_ios.dart';
3 |
4 | import 'launch_options.dart';
5 |
6 | extension LaunchOptionsConverter on LaunchOptions {
7 | /// Converts to [CustomTabsOptions].
8 | CustomTabsOptions toCustomTabsOptions() {
9 | CustomTabsColorSchemes? colorSchemes;
10 | if (barColor != null || systemNavigationBarParams != null) {
11 | colorSchemes = CustomTabsColorSchemes.defaults(
12 | toolbarColor: barColor,
13 | navigationBarColor: systemNavigationBarParams?.backgroundColor,
14 | navigationBarDividerColor: systemNavigationBarParams?.dividerColor,
15 | );
16 | }
17 |
18 | bool? urlBarHidingEnabled;
19 | if (barFixingEnabled != null) {
20 | urlBarHidingEnabled = !(barFixingEnabled!);
21 | }
22 | return CustomTabsOptions(
23 | colorSchemes: colorSchemes,
24 | urlBarHidingEnabled: urlBarHidingEnabled,
25 | shareState: CustomTabsShareState.on,
26 | showTitle: true,
27 | shareIdentityEnabled: true,
28 | browser: const CustomTabsBrowserConfiguration(
29 | prefersDefaultBrowser: true,
30 | ),
31 | );
32 | }
33 |
34 | /// Converts to [SafariViewControllerOptions].
35 | SafariViewControllerOptions toSafariViewControllerOptions() {
36 | bool? barCollapsingEnabled;
37 | if (barFixingEnabled != null) {
38 | barCollapsingEnabled = !(barFixingEnabled!);
39 | }
40 | return SafariViewControllerOptions(
41 | preferredBarTintColor: barColor,
42 | preferredControlTintColor: onBarColor,
43 | barCollapsingEnabled: barCollapsingEnabled,
44 | dismissButtonStyle: SafariViewControllerDismissButtonStyle.done,
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs
2 | description: A Flutter plugin for mobile apps to launch a URL in Custom Tabs/SFSafariViewController.
3 | version: 2.4.0
4 | homepage: https://github.com/droibit/flutter_custom_tabs
5 | repository: https://github.com/droibit/flutter_custom_tabs/tree/main/flutter_custom_tabs
6 | issue_tracker: https://github.com/droibit/flutter_custom_tabs/issues
7 |
8 | environment:
9 | sdk: ^3.3.0
10 | flutter: ">=3.19.0"
11 |
12 | dependencies:
13 | flutter:
14 | sdk: flutter
15 | flutter_custom_tabs_platform_interface: ^2.3.0
16 | flutter_custom_tabs_android: ^2.3.1
17 | flutter_custom_tabs_ios: ^2.4.0
18 | flutter_custom_tabs_web: ^2.3.0
19 | meta: ^1.10.0
20 |
21 | dev_dependencies:
22 | flutter_test:
23 | sdk: flutter
24 | mockito: ^5.4.4
25 | flutter_lints: ^4.0.0
26 | plugin_platform_interface: ^2.1.8
27 |
28 | flutter:
29 | plugin:
30 | platforms:
31 | android:
32 | default_package: flutter_custom_tabs_android
33 | ios:
34 | default_package: flutter_custom_tabs_ios
35 | web:
36 | default_package: flutter_custom_tabs_web
37 |
--------------------------------------------------------------------------------
/flutter_custom_tabs/test/lite/launcher_lite_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs/flutter_custom_tabs_lite.dart';
2 | import 'package:flutter_custom_tabs/src/lite/type_conversion.dart';
3 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
4 | import 'package:flutter_test/flutter_test.dart';
5 |
6 | import '../mocks/mock_custom_tabs_platform.dart';
7 |
8 | void main() {
9 | final mock = MockCustomTabsPlatform();
10 | setUp(() => {CustomTabsPlatform.instance = mock});
11 |
12 | test('launchUrl() throws ArgumentError when launching with non-web URL',
13 | () async {
14 | final url = Uri.parse('file:/home');
15 | expect(
16 | () => launchUrl(url),
17 | throwsA(isA()),
18 | );
19 | expect(mock.launchUrlCalled, isFalse);
20 | });
21 |
22 | test('launchUrl() launch with empty options', () async {
23 | final url = Uri.parse('http://example.com/');
24 | const prefersDeepLink = false;
25 | const options = LaunchOptions();
26 | mock.setLaunchExpectations(
27 | url: url.toString(),
28 | prefersDeepLink: prefersDeepLink,
29 | customTabsOptions: options.toCustomTabsOptions(),
30 | safariVCOptions: options.toSafariViewControllerOptions(),
31 | );
32 |
33 | try {
34 | await launchUrl(
35 | url,
36 | prefersDeepLink: prefersDeepLink,
37 | options: options,
38 | );
39 | } catch (e) {
40 | fail(e.toString());
41 | }
42 | });
43 |
44 | test('launchUrl() launch with options', () async {
45 | final url = Uri.parse('http://example.com/');
46 | const prefersDeepLink = true;
47 | const options = LaunchOptions(
48 | barFixingEnabled: false,
49 | );
50 | mock.setLaunchExpectations(
51 | url: url.toString(),
52 | prefersDeepLink: prefersDeepLink,
53 | customTabsOptions: options.toCustomTabsOptions(),
54 | safariVCOptions: options.toSafariViewControllerOptions(),
55 | );
56 |
57 | try {
58 | await launchUrl(
59 | url,
60 | prefersDeepLink: prefersDeepLink,
61 | options: options,
62 | );
63 | } catch (e) {
64 | fail(e.toString());
65 | }
66 | });
67 | }
68 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | .packages
30 | build/
31 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "e1e47221e86272429674bec4f1bd36acc4fc7b77"
8 | channel: "stable"
9 |
10 | project_type: plugin
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
17 | base_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
18 | - platform: android
19 | create_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
20 | base_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/README.md:
--------------------------------------------------------------------------------
1 | # flutter_custom_tabs_android
2 |
3 | The Android platform implementation of [flutter_custom_tabs][1].
4 |
5 | ## Usage
6 |
7 | ### Import the package
8 |
9 | This package has been the endorsed implementation of `flutter_custom_tabs` for the Android platform since version `2.0.0`. This means it will be automatically included in your app when you add it, so you do not need to include it in your `pubspec.yaml`.
10 |
11 | [1]: https://pub.dev/packages/flutter_custom_tabs
12 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/.gitignore:
--------------------------------------------------------------------------------
1 | # Local configuration file (sdk path, etc)
2 | local.properties
3 |
4 | # Intellij project files
5 | *.iml
6 | *.ipr
7 | *.iws
8 | .idea/*
9 |
10 | # Android Studio files
11 | /captures
12 | .externalNativeBuild/
13 | projectFilesBackup/
14 | output.json
15 |
16 | # Gradle files
17 | /.gradle
18 | /build
19 |
20 | .DS_Store
21 | /gradle
22 | /gradlew
23 | /gradlew.bat
24 |
25 | # Flutter files
26 | GeneratedPluginRegistrant.java
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'com.github.droibit.plugins.flutter.customtabs'
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | }
8 |
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:7.4.2'
11 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10'
12 | }
13 | }
14 |
15 | rootProject.allprojects {
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | apply plugin: 'com.android.library'
23 | apply plugin: 'kotlin-android'
24 |
25 | android {
26 | if (project.android.hasProperty('namespace')) {
27 | namespace 'com.github.droibit.plugins.flutter.customtabs'
28 | }
29 |
30 | compileSdk 34
31 |
32 | defaultConfig {
33 | minSdk 19
34 |
35 | vectorDrawables.useSupportLibrary = true
36 | }
37 |
38 | buildFeatures {
39 | buildConfig false
40 | }
41 |
42 | compileOptions {
43 | sourceCompatibility JavaVersion.VERSION_1_8
44 | targetCompatibility JavaVersion.VERSION_1_8
45 | }
46 |
47 | kotlinOptions {
48 | jvmTarget = '1.8'
49 | // ref. https://www.reddit.com/r/androiddev/comments/mztyva/a_few_tips_for_testparameterinjector_library/
50 | freeCompilerArgs += ['-java-parameters']
51 | }
52 |
53 | lintOptions {
54 | checkAllWarnings true
55 | warningsAsErrors true
56 | disable 'InvalidPackage', 'AndroidGradlePluginVersion', 'GradleDependency'
57 | }
58 |
59 | testOptions {
60 | unitTests.all {
61 | testLogging {
62 | events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
63 | outputs.upToDateWhen { false }
64 | showStandardStreams = true
65 | }
66 | }
67 | }
68 | }
69 |
70 | dependencies {
71 | implementation 'androidx.core:core-ktx:1.9.0'
72 | implementation 'androidx.browser:browser:1.8.0'
73 | implementation 'io.github.droibit:customtabslauncher:3.0.0'
74 |
75 | testImplementation 'junit:junit:4.13.2'
76 | testImplementation 'org.robolectric:robolectric:4.11'
77 | testImplementation 'io.mockk:mockk:1.13.3'
78 | testImplementation 'com.google.truth:truth:1.4.4'
79 | testImplementation 'androidx.test.ext:truth:1.6.0'
80 | testImplementation 'androidx.test.ext:junit-ktx:1.2.1'
81 | testImplementation 'com.google.testparameterinjector:test-parameter-injector:1.18'
82 | }
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'flutter_custom_tabs_android'
2 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/CustomTabsPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs
2 |
3 | import io.flutter.embedding.engine.plugins.FlutterPlugin
4 | import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
5 | import io.flutter.embedding.engine.plugins.activity.ActivityAware
6 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
7 |
8 | class CustomTabsPlugin : FlutterPlugin, ActivityAware {
9 | private var api: CustomTabsLauncher? = null
10 |
11 | override fun onAttachedToEngine(binding: FlutterPluginBinding) {
12 | api = CustomTabsLauncher()
13 | CustomTabsApi.setUp(binding.binaryMessenger, api)
14 | }
15 |
16 | override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
17 | if (api == null) {
18 | return
19 | }
20 |
21 | CustomTabsApi.setUp(binding.binaryMessenger, null)
22 | api = null
23 | }
24 |
25 | override fun onAttachedToActivity(binding: ActivityPluginBinding) {
26 | val api = this.api ?: return
27 | api.setActivity(binding.activity)
28 | }
29 |
30 | override fun onDetachedFromActivityForConfigChanges() {
31 | onDetachedFromActivity()
32 | }
33 |
34 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
35 | onAttachedToActivity(binding)
36 | }
37 |
38 | override fun onDetachedFromActivity() {
39 | val api = this.api ?: return
40 | api.setActivity(null)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/ExternalBrowserLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.provider.Browser.EXTRA_HEADERS
7 | import androidx.annotation.VisibleForTesting
8 | import com.github.droibit.flutter.plugins.customtabs.core.options.CustomTabsIntentOptions
9 | import com.github.droibit.flutter.plugins.customtabs.core.utils.bundleOf
10 |
11 | class ExternalBrowserLauncher {
12 | fun launch(context: Context, uri: Uri, options: CustomTabsIntentOptions?): Boolean {
13 | val externalBrowserIntent = createIntent(options) ?: return false
14 | externalBrowserIntent.setData(uri)
15 | context.startActivity(externalBrowserIntent)
16 | return true
17 | }
18 |
19 | @VisibleForTesting
20 | internal fun createIntent(options: CustomTabsIntentOptions?): Intent? {
21 | val intent = Intent(Intent.ACTION_VIEW)
22 | if (options == null) {
23 | return intent
24 | }
25 |
26 | val browserOptions = options.browser ?: return null
27 | val prefersExternalBrowser = browserOptions.prefersExternalBrowser
28 | if (prefersExternalBrowser == true) {
29 | browserOptions.headers?.let { intent.putExtra(EXTRA_HEADERS, bundleOf(it)) }
30 | return intent
31 | }
32 | return null
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/PartialCustomTabsLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core
2 |
3 | import android.app.Activity
4 | import android.net.Uri
5 | import androidx.browser.customtabs.CustomTabsIntent
6 | import androidx.browser.customtabs.CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX
7 | import androidx.browser.customtabs.CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_WIDTH_PX
8 |
9 | class PartialCustomTabsLauncher {
10 | fun launch(activity: Activity, uri: Uri, customTabsIntent: CustomTabsIntent): Boolean {
11 | val rawIntent = customTabsIntent.intent
12 | if (rawIntent.hasExtra(EXTRA_INITIAL_ACTIVITY_HEIGHT_PX) ||
13 | rawIntent.hasExtra(EXTRA_INITIAL_ACTIVITY_WIDTH_PX)
14 | ) {
15 | // ref. https://developer.chrome.com/docs/android/custom-tabs/guide-partial-custom-tabs
16 | rawIntent.setData(uri)
17 | activity.startActivityForResult(rawIntent, REQUEST_CODE_PARTIAL_CUSTOM_TABS)
18 | return true
19 | }
20 | return false
21 | }
22 |
23 | private companion object {
24 | const val REQUEST_CODE_PARTIAL_CUSTOM_TABS = 1001
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/ResourceFactory.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.Bitmap
6 | import android.os.Build
7 | import androidx.annotation.AnimRes
8 | import androidx.annotation.AnyRes
9 | import androidx.annotation.Px
10 | import androidx.core.content.ContextCompat
11 | import androidx.core.content.res.ResourcesCompat
12 | import androidx.core.graphics.drawable.DrawableCompat
13 | import androidx.core.graphics.drawable.toBitmap
14 |
15 | class ResourceFactory {
16 | fun getBitmap(context: Context, drawableIdentifier: String?): Bitmap? {
17 | val drawableResId = resolveIdentifier(context, "drawable", drawableIdentifier)
18 | if (drawableResId == ResourcesCompat.ID_NULL) {
19 | return null
20 | }
21 |
22 | var drawable = ContextCompat.getDrawable(context, drawableResId)
23 | ?: return null
24 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
25 | drawable = DrawableCompat.wrap(drawable).mutate()
26 | }
27 | return drawable.toBitmap()
28 | }
29 |
30 | @AnimRes
31 | fun getAnimationIdentifier(context: Context, identifier: String?): Int {
32 | return resolveIdentifier(context, "anim", identifier)
33 | }
34 |
35 | @SuppressLint("DiscouragedApi")
36 | @AnyRes
37 | private fun resolveIdentifier(context: Context, defType: String, name: String?): Int {
38 | val res = context.resources
39 | return when {
40 | name == null -> ResourcesCompat.ID_NULL
41 | fullIdentifierRegex.containsMatchIn(name) -> res.getIdentifier(name, null, null)
42 | else -> res.getIdentifier(name, defType, context.packageName)
43 | }
44 | }
45 |
46 | @Px
47 | fun convertToPx(context: Context, dp: Double): Int {
48 | val scale = context.resources.displayMetrics.density
49 | return (dp * scale + 0.5).toInt()
50 | }
51 |
52 | private companion object {
53 | // Note: The full resource qualifier is "package:type/entry".
54 | // https://developer.android.com/reference/android/content/res/Resources.html#getIdentifier(java.lang.String, java.lang.String, java.lang.String)
55 | private val fullIdentifierRegex = "^.+:.+/".toRegex()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/options/CustomTabsAnimations.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.options
2 |
3 | class CustomTabsAnimations internal constructor(
4 | val startEnter: String?,
5 | val startExit: String?,
6 | val endEnter: String?,
7 | val endExit: String?
8 | ) {
9 | class Builder {
10 | private var startEnter: String? = null
11 | private var startExit: String? = null
12 | private var endEnter: String? = null
13 | private var endExit: String? = null
14 |
15 | fun setOptions(options: Map?): Builder {
16 | if (options == null) {
17 | return this
18 | }
19 |
20 | startEnter = options[KEY_START_ENTER] as String?
21 | startExit = options[KEY_START_EXIT] as String?
22 | endEnter = options[KEY_END_ENTER] as String?
23 | endExit = options[KEY_END_EXIT] as String?
24 | return this
25 | }
26 |
27 | fun setStartEnter(startEnter: String?): Builder {
28 | this.startEnter = startEnter
29 | return this
30 | }
31 |
32 | fun setStartExit(startExit: String?): Builder {
33 | this.startExit = startExit
34 | return this
35 | }
36 |
37 | fun setEndEnter(endEnter: String?): Builder {
38 | this.endEnter = endEnter
39 | return this
40 | }
41 |
42 | fun setEndExit(endExit: String?): Builder {
43 | this.endExit = endExit
44 | return this
45 | }
46 |
47 | fun build() = CustomTabsAnimations(startEnter, startExit, endEnter, endExit)
48 |
49 | private companion object {
50 | private const val KEY_START_ENTER = "startEnter"
51 | private const val KEY_START_EXIT = "startExit"
52 | private const val KEY_END_ENTER = "endEnter"
53 | private const val KEY_END_EXIT = "endExit"
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/options/CustomTabsCloseButton.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.options
2 |
3 | import androidx.browser.customtabs.CustomTabsIntent.CloseButtonPosition
4 |
5 | class CustomTabsCloseButton internal constructor(
6 | val icon: String?,
7 | @CloseButtonPosition val position: Int?
8 | ) {
9 | class Builder {
10 | private var icon: String? = null
11 | private var position: Int? = null
12 |
13 | fun setOptions(options: Map?): Builder {
14 | if (options == null) {
15 | return this
16 | }
17 | icon = options[KEY_ICON] as String?
18 | position = (options[KEY_POSITION] as Long?)?.toInt()
19 | return this
20 | }
21 |
22 | fun setIcon(icon: String?): Builder {
23 | this.icon = icon
24 | return this
25 | }
26 |
27 | fun setPosition(position: Int?): Builder {
28 | this.position = position
29 | return this
30 | }
31 |
32 | fun build() = CustomTabsCloseButton(icon, position)
33 |
34 | private companion object {
35 | private const val KEY_ICON = "icon"
36 | private const val KEY_POSITION = "position"
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/options/CustomTabsSessionOptions.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.options
2 |
3 | import android.content.Context
4 | import com.droibit.android.customtabs.launcher.CustomTabsPackageProvider
5 |
6 | class CustomTabsSessionOptions internal constructor(
7 | private val browser: BrowserConfiguration
8 | ) {
9 | constructor(
10 | prefersDefaultBrowser: Boolean?,
11 | fallbackCustomTabPackages: Set?
12 | ) : this(
13 | BrowserConfiguration.Builder()
14 | .setPrefersDefaultBrowser(prefersDefaultBrowser)
15 | .setFallbackCustomTabs(fallbackCustomTabPackages)
16 | .build()
17 | )
18 |
19 | val prefersDefaultBrowser: Boolean?
20 | get() = browser.prefersDefaultBrowser
21 |
22 | val fallbackCustomTabPackages: Set?
23 | get() = browser.fallbackCustomTabPackages
24 |
25 | fun getAdditionalCustomTabs(context: Context): CustomTabsPackageProvider {
26 | return browser.getAdditionalCustomTabs(context)
27 | }
28 |
29 | class Builder {
30 | private var prefersDefaultBrowser: Boolean? = null
31 | private var fallbackCustomTabs: Set? = null
32 |
33 | fun setOptions(options: Map?): Builder {
34 | if (options == null) {
35 | return this
36 | }
37 |
38 | prefersDefaultBrowser = options[KEY_PREFERS_DEFAULT_BROWSER] as Boolean?
39 | @Suppress("UNCHECKED_CAST")
40 | fallbackCustomTabs = (options[KEY_FALLBACK_CUSTOM_TABS] as List?)?.toSet()
41 | return this
42 | }
43 |
44 | fun setPrefersDefaultBrowser(prefersExternalBrowser: Boolean?): Builder {
45 | this.prefersDefaultBrowser = prefersExternalBrowser
46 | return this
47 | }
48 |
49 | fun setFallbackCustomTabs(fallbackCustomTabs: Set?): Builder {
50 | this.fallbackCustomTabs = fallbackCustomTabs
51 | return this
52 | }
53 |
54 | fun build() = CustomTabsSessionOptions(
55 | prefersDefaultBrowser,
56 | fallbackCustomTabs
57 | )
58 |
59 | private companion object {
60 | private const val KEY_PREFERS_DEFAULT_BROWSER = "prefersDefaultBrowser"
61 | private const val KEY_FALLBACK_CUSTOM_TABS = "fallbackCustomTabs"
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/session/CustomTabsSessionManager.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.session
2 |
3 | import android.content.Context
4 | import androidx.annotation.VisibleForTesting
5 | import androidx.browser.customtabs.CustomTabsSession
6 | import com.droibit.android.customtabs.launcher.getCustomTabsPackage
7 | import com.github.droibit.flutter.plugins.customtabs.core.options.CustomTabsSessionOptions
8 |
9 | class CustomTabsSessionManager @VisibleForTesting internal constructor(
10 | private val cachedSessions: MutableMap
11 | ) : CustomTabsSessionProvider {
12 | constructor() : this(mutableMapOf())
13 |
14 | fun createSessionOptions(options: Map?): CustomTabsSessionOptions {
15 | return CustomTabsSessionOptions.Builder()
16 | .setOptions(options)
17 | .build()
18 | }
19 |
20 | fun createSessionController(
21 | context: Context,
22 | options: CustomTabsSessionOptions
23 | ): CustomTabsSessionController? {
24 | val prefersDefaultBrowser = options.prefersDefaultBrowser
25 | val customTabsPackage = getCustomTabsPackage(
26 | context,
27 | ignoreDefault = prefersDefaultBrowser != true,
28 | additionalCustomTabs = options.getAdditionalCustomTabs(context)
29 | ) ?: return null
30 |
31 | return cachedSessions[customTabsPackage]
32 | ?: CustomTabsSessionController(customTabsPackage).also {
33 | cachedSessions[customTabsPackage] = it
34 | }
35 | }
36 |
37 | fun getSessionController(packageName: String): CustomTabsSessionController? {
38 | return cachedSessions[packageName]
39 | }
40 |
41 | override fun getSession(packageName: String?): CustomTabsSession? {
42 | return packageName?.let { cachedSessions[it]?.session }
43 | }
44 |
45 | fun invalidateSession(packageName: String) {
46 | val controller = cachedSessions[packageName] ?: return
47 | controller.unbindCustomTabsService()
48 | cachedSessions.remove(packageName)
49 | }
50 |
51 | fun handleActivityChange(activity: Context?) {
52 | for (controller in cachedSessions.values) {
53 | if (activity == null) {
54 | controller.unbindCustomTabsService()
55 | } else {
56 | controller.bindCustomTabsService(activity)
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/session/CustomTabsSessionProvider.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.session
2 |
3 | import androidx.browser.customtabs.CustomTabsSession
4 |
5 | fun interface CustomTabsSessionProvider {
6 | fun getSession(packageName: String?): CustomTabsSession?
7 | }
8 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/core/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.utils
2 |
3 | import android.os.Bundle
4 |
5 | internal fun bundleOf(headers: Map): Bundle {
6 | return Bundle(headers.size).apply {
7 | for ((key, value) in headers) {
8 | putString(key, value)
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/res/drawable/fct_ic_arrow_back.xml:
--------------------------------------------------------------------------------
1 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/main/res/raw/keep.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/test/kotlin/com/github/droibit/flutter/plugins/customtabs/core/PartialCustomTabsLauncherTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core
2 |
3 | import android.app.Activity
4 | import androidx.browser.customtabs.CustomTabsIntent
5 | import androidx.core.net.toUri
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import androidx.test.ext.truth.content.IntentSubject.assertThat
8 | import com.google.common.truth.Truth.assertThat
9 | import io.mockk.impl.annotations.InjectMockKs
10 | import io.mockk.impl.annotations.MockK
11 | import io.mockk.junit4.MockKRule
12 | import io.mockk.verify
13 | import org.junit.Rule
14 | import org.junit.Test
15 | import org.junit.runner.RunWith
16 | import org.robolectric.annotation.Config
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | @Config(manifest = Config.NONE)
20 | class PartialCustomTabsLauncherTest {
21 | @get:Rule
22 | val mockkRule = MockKRule(this)
23 |
24 | @MockK(relaxed = true)
25 | private lateinit var activity: Activity
26 |
27 | @InjectMockKs
28 | private lateinit var launcher: PartialCustomTabsLauncher
29 |
30 | @Test
31 | fun launch_withValidCustomTabsIntent_returnsTrue() {
32 | val customTabsIntent = CustomTabsIntent.Builder()
33 | .setInitialActivityHeightPx(100)
34 | .build()
35 |
36 | val uri = "https://example.com".toUri()
37 | val result = launcher.launch(activity, uri, customTabsIntent)
38 | assertThat(result).isTrue()
39 | assertThat(customTabsIntent.intent).hasData(uri)
40 |
41 | verify { activity.startActivityForResult(refEq(customTabsIntent.intent), eq(1001)) }
42 | }
43 |
44 | @Test
45 | fun launch_withoutRequiredExtras_returnsFalse() {
46 | val customTabsIntent = CustomTabsIntent.Builder().build()
47 |
48 | val uri = "https://example.com".toUri()
49 | val result = launcher.launch(activity, uri, customTabsIntent)
50 | assertThat(result).isFalse()
51 |
52 | verify(exactly = 0) { activity.startActivityForResult(any(), any()) }
53 | }
54 | }
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/test/kotlin/com/github/droibit/flutter/plugins/customtabs/core/options/CustomTabsCloseButtonBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.options
2 |
3 | import androidx.browser.customtabs.CustomTabsIntent.CLOSE_BUTTON_POSITION_START
4 | import com.google.common.truth.Truth.assertThat
5 | import com.google.testing.junit.testparameterinjector.TestParameter
6 | import com.google.testing.junit.testparameterinjector.TestParameterInjector
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 |
10 | @RunWith(TestParameterInjector::class)
11 | class CustomTabsCloseButtonBuilderTest {
12 | @Test
13 | fun build_withChainedMethods() {
14 | val closeButton = CustomTabsCloseButton.Builder()
15 | .setIcon("ic_arrow_back")
16 | .setPosition(CLOSE_BUTTON_POSITION_START)
17 | .build()
18 |
19 | assertThat(closeButton.icon).isEqualTo("ic_arrow_back")
20 | assertThat(closeButton.position).isEqualTo(CLOSE_BUTTON_POSITION_START)
21 | }
22 |
23 | @Test
24 | fun setOptions_withAllOptions() {
25 | val options = mapOf(
26 | "icon" to "ic_arrow_back",
27 | "position" to CLOSE_BUTTON_POSITION_START.toLong(),
28 | )
29 |
30 | val closeButton = CustomTabsCloseButton.Builder()
31 | .setOptions(options)
32 | .build()
33 |
34 | assertThat(closeButton.icon).isEqualTo("ic_arrow_back")
35 | assertThat(closeButton.position).isEqualTo(CLOSE_BUTTON_POSITION_START)
36 | }
37 |
38 | @Test
39 | fun setOptions_withNullOptions() {
40 | val closeButton = CustomTabsCloseButton.Builder()
41 | .setOptions(null)
42 | .build()
43 |
44 | assertThat(closeButton.icon).isNull()
45 | assertThat(closeButton.position).isNull()
46 | }
47 |
48 | @Test
49 | fun setIcon_parameterized(
50 | @TestParameter("ic_arrow_back", "null") input: String?
51 | ) {
52 | val closeButton = CustomTabsCloseButton.Builder()
53 | .setIcon(input)
54 | .build()
55 |
56 | assertThat(closeButton.icon).isEqualTo(input)
57 | }
58 |
59 | @Test
60 | fun setPosition_parameterized(
61 | @TestParameter("1", "null") input: Int?
62 | ) {
63 | val closeButton = CustomTabsCloseButton.Builder()
64 | .setPosition(input)
65 | .build()
66 |
67 | assertThat(closeButton.position).isEqualTo(input)
68 | }
69 | }
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/test/kotlin/com/github/droibit/flutter/plugins/customtabs/core/options/CustomTabsSessionOptionsBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.options
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import com.google.testing.junit.testparameterinjector.TestParameter
5 | import com.google.testing.junit.testparameterinjector.TestParameterInjector
6 | import com.google.testing.junit.testparameterinjector.TestParameters
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 |
10 | @RunWith(TestParameterInjector::class)
11 | class CustomTabsSessionOptionsBuilderTest {
12 | @Test
13 | fun setOptions_withAllOptions() {
14 | val options = mapOf(
15 | "prefersDefaultBrowser" to true,
16 | "fallbackCustomTabs" to listOf(
17 | "com.example.browser1",
18 | "com.example.browser2",
19 | )
20 | )
21 |
22 | val sessionOptions = CustomTabsSessionOptions.Builder()
23 | .setOptions(options)
24 | .build()
25 |
26 | assertThat(sessionOptions.prefersDefaultBrowser).isTrue()
27 | assertThat(sessionOptions.fallbackCustomTabPackages).containsExactly(
28 | "com.example.browser1",
29 | "com.example.browser2",
30 | )
31 | }
32 |
33 | @Test
34 | fun setOptions_withNullOptions() {
35 | val sessionOptions = CustomTabsSessionOptions.Builder()
36 | .setOptions(null)
37 | .build()
38 |
39 | assertThat(sessionOptions.prefersDefaultBrowser).isNull()
40 | assertThat(sessionOptions.fallbackCustomTabPackages).isNull()
41 | }
42 |
43 | @Test
44 | fun setPrefersDefaultBrowser_parameterized(
45 | @TestParameter("true", "false", "null") input: Boolean?
46 | ) {
47 | val sessionOptions = CustomTabsSessionOptions.Builder()
48 | .setPrefersDefaultBrowser(input)
49 | .build()
50 |
51 | assertThat(sessionOptions.prefersDefaultBrowser).isEqualTo(input)
52 | assertThat(sessionOptions.fallbackCustomTabPackages).isNull()
53 | }
54 |
55 | @Test
56 | @TestParameters("{input: ['com.example.browser1']}", customName = "Multiple packages")
57 | @TestParameters("{input: []}", customName = "Empty packages")
58 | @TestParameters("{input: null}", customName = "Null packages")
59 | fun setFallbackCustomTabs_parameterized(input: List?) {
60 | val inputSet = input?.toSet()
61 | val sessionOptions = CustomTabsSessionOptions.Builder()
62 | .setFallbackCustomTabs(inputSet)
63 | .build()
64 |
65 | assertThat(sessionOptions.fallbackCustomTabPackages).isEqualTo(inputSet)
66 | }
67 |
68 | @Test
69 | fun build_withChainedMethods() {
70 | val animations = CustomTabsAnimations.Builder()
71 | .setStartEnter("fade_in")
72 | .setStartExit("fade_out")
73 | .setEndEnter("slide_in")
74 | .setEndExit("slide_out")
75 | .build()
76 |
77 | assertThat(animations.startEnter).isEqualTo("fade_in")
78 | assertThat(animations.startExit).isEqualTo("fade_out")
79 | assertThat(animations.endEnter).isEqualTo("slide_in")
80 | assertThat(animations.endExit).isEqualTo("slide_out")
81 | }
82 | }
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/test/kotlin/com/github/droibit/flutter/plugins/customtabs/core/options/CustomTabsSessionOptionsTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.flutter.plugins.customtabs.core.options
2 |
3 | import android.content.Context
4 | import com.droibit.android.customtabs.launcher.CustomTabsPackageProvider
5 | import com.google.common.truth.Truth.assertThat
6 | import io.mockk.every
7 | import io.mockk.mockk
8 | import org.junit.Test
9 |
10 | class CustomTabsSessionOptionsTest {
11 | @Test
12 | fun constructor_withPrefersDefaultBrowserAndFallbackPackages() {
13 | val prefersDefaultBrowser = true
14 | val fallbackPackages = setOf("com.example.browser1", "com.example.browser2")
15 | val sessionOptions = CustomTabsSessionOptions(prefersDefaultBrowser, fallbackPackages)
16 |
17 | assertThat(sessionOptions.prefersDefaultBrowser).isEqualTo(prefersDefaultBrowser)
18 | assertThat(sessionOptions.fallbackCustomTabPackages).isEqualTo(fallbackPackages)
19 | }
20 |
21 | @Test
22 | fun constructor_withNullParameters() {
23 | val sessionOptions = CustomTabsSessionOptions(null, null)
24 |
25 | assertThat(sessionOptions.prefersDefaultBrowser).isNull()
26 | assertThat(sessionOptions.fallbackCustomTabPackages).isNull()
27 | }
28 |
29 | @Test
30 | fun prefersDefaultBrowser_delegatesToBrowserConfiguration() {
31 | val prefersDefaultBrowser = true
32 | val sessionOptions = CustomTabsSessionOptions(prefersDefaultBrowser, null)
33 |
34 | assertThat(sessionOptions.prefersDefaultBrowser).isEqualTo(prefersDefaultBrowser)
35 | }
36 |
37 | @Test
38 | fun fallbackCustomTabPackages_delegatesToBrowserConfiguration() {
39 | val fallbackPackages = setOf("com.example.browser1", "com.example.browser2")
40 | val sessionOptions = CustomTabsSessionOptions(null, fallbackPackages)
41 |
42 | assertThat(sessionOptions.fallbackCustomTabPackages).isEqualTo(fallbackPackages)
43 | }
44 |
45 | @Test
46 | fun getAdditionalCustomTabs_delegatesToBrowserConfiguration() {
47 | val additionalCustomTabs = mockk()
48 | val browser = mockk {
49 | every { getAdditionalCustomTabs(any()) } returns additionalCustomTabs
50 | }
51 | val sessionOptions = CustomTabsSessionOptions(browser)
52 |
53 | val context = mockk()
54 | val provider = sessionOptions.getAdditionalCustomTabs(context)
55 |
56 | assertThat(provider).isSameInstanceAs(additionalCustomTabs)
57 | }
58 | }
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/android/src/test/resources/robolectric.properties:
--------------------------------------------------------------------------------
1 | sdk=19
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 | # Remove the following pattern if you wish to check in your lock file
35 | pubspec.lock
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/.pluginToolsConfig.yaml:
--------------------------------------------------------------------------------
1 | buildFlags:
2 | _pluginToolsConfigGlobalKey:
3 | - "--no-tree-shake-icons"
4 | - "--dart-define=buildmode=testing"
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_custom_tabs_android_example
2 |
3 | Demonstrates how to use the flutter_custom_tabs_android plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
8 |
9 | linter:
10 | rules:
11 | depend_on_referenced_packages: false
12 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{kt,kts}]
12 | ij_kotlin_imports_layout = *
13 | ij_kotlin_allow_trailing_comma = true
14 | ij_kotlin_allow_trailing_comma_on_call_site = true
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | # Local configuration file (sdk path, etc)
2 | local.properties
3 |
4 | # Intellij project files
5 | *.iml
6 | *.ipr
7 | *.iws
8 | .idea/*
9 |
10 | # Android Studio files
11 | /captures
12 | .externalNativeBuild/
13 | projectFilesBackup/
14 | output.json
15 |
16 | # Gradle files
17 | /.gradle
18 | /build
19 | /gradle
20 | /gradlew
21 | /gradlew.bat
22 |
23 | .DS_Store
24 |
25 | # Flutter files
26 | GeneratedPluginRegistrant.java
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | android {
8 | namespace "com.github.droibit.plugins.flutter.customtabs.android.example"
9 | compileSdk flutter.compileSdkVersion
10 | ndkVersion flutter.ndkVersion
11 |
12 | defaultConfig {
13 | applicationId = "com.github.droibit.plugins.flutter.customtabs.flutter_custom_tabs_android_example"
14 | // Enable multidex support.
15 | minSdk = flutter.minSdkVersion
16 | targetSdk = flutter.targetSdkVersion
17 | versionCode = 1
18 | versionName = "1.0"
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled true
24 | shrinkResources true
25 | // Signing with the debug keys for now, so `flutter run --release` works.
26 | signingConfig signingConfigs.debug
27 | }
28 | }
29 |
30 | kotlinOptions {
31 | jvmTarget = "1.8"
32 | }
33 |
34 | compileOptions {
35 | sourceCompatibility JavaVersion.VERSION_1_8
36 | targetCompatibility JavaVersion.VERSION_1_8
37 | }
38 | }
39 |
40 | flutter {
41 | source "../.."
42 | }
43 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
17 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/kotlin/com/github/droibit/plugins/flutter/customtabs/android/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.droibit.plugins.flutter.customtabs.android.example
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import io.flutter.embedding.android.FlutterActivity
6 |
7 | class MainActivity : FlutterActivity() {
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | Log.d("DEBUG", "#onCreate()")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/drawable/ic_round_close.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 |
7 | gradle.projectsEvaluated {
8 | tasks.withType(JavaCompile).configureEach {
9 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
10 | }
11 | }
12 | }
13 |
14 | rootProject.buildDir = "../build"
15 | subprojects {
16 | project.buildDir = "${rootProject.buildDir}/${project.name}"
17 | }
18 | subprojects {
19 | project.evaluationDependsOn(":app")
20 | }
21 |
22 | tasks.register("clean", Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.1.4" apply false
22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs_android_example
2 | description: Demonstrates how to use the flutter_custom_tabs_android plugin.
3 | publish_to: 'none'
4 |
5 | environment:
6 | sdk: ^3.3.0
7 | flutter: ">=3.19.0"
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 | flutter_custom_tabs_android:
13 | path: ../
14 |
15 | dev_dependencies:
16 | integration_test:
17 | sdk: flutter
18 | flutter_test:
19 | sdk: flutter
20 | flutter_lints: ^4.0.0
21 |
22 | flutter:
23 | uses-material-design: true
24 |
25 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/lib/flutter_custom_tabs_android.dart:
--------------------------------------------------------------------------------
1 | export 'src/messages/messages.dart' show CustomTabsOptionsConverter;
2 | export 'src/types/types.dart';
3 | export 'src/custom_tabs_plugin_android.dart';
4 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/lib/src/messages/messages.dart:
--------------------------------------------------------------------------------
1 | export 'messages.g.dart';
2 | export 'type_conversion.dart';
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/lib/src/types/custom_tabs_animations.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | /// The enter and exit animations for the Custom Tab.
4 | ///
5 | /// Specify the Resource ID according to the specifications for the Android platform.
6 | /// - For resources within the Android app, use the resource name.
7 | /// - e.g. `slide_up`
8 | /// - For other cases, provide the complete Resource ID with the type 'anim'.
9 | /// - e.g. `android:anim/fade_in`
10 | ///
11 | /// See also:
12 | /// - [View animation](https://developer.android.com/guide/topics/resources/animation-resource.html#View)
13 | /// - [getIdentifier](https://developer.android.com/reference/android/content/res/Resources.html#getIdentifier(java.lang.String,%20java.lang.String,%20java.lang.String))
14 | @immutable
15 | class CustomTabsAnimations {
16 | const CustomTabsAnimations({
17 | this.startEnter,
18 | this.startExit,
19 | this.endEnter,
20 | this.endExit,
21 | });
22 |
23 | /// Resource ID of the start "enter" animation for the Custom Tab.
24 | final String? startEnter;
25 |
26 | /// Resource ID of the start "exit" animation for the application.
27 | final String? startExit;
28 |
29 | /// Resource ID of the exit "enter" animation for the application.
30 | final String? endEnter;
31 |
32 | /// Resource ID of the exit "exit" animation for the Custom Tab.
33 | final String? endExit;
34 | }
35 |
36 | /// Build-in enter and exit animations for Custom Tabs.
37 | class CustomTabsSystemAnimations {
38 | /// Creates a built-in slide in animation.
39 | static CustomTabsAnimations slideIn() {
40 | _slideIn ??= const CustomTabsAnimations(
41 | startEnter: 'android:anim/slide_in_right',
42 | startExit: 'android:anim/slide_out_left',
43 | endEnter: 'android:anim/slide_in_left',
44 | endExit: 'android:anim/slide_out_right',
45 | );
46 | return _slideIn!;
47 | }
48 |
49 | /// Creates a built-in fade animation.
50 | static CustomTabsAnimations fade() {
51 | _fade ??= const CustomTabsAnimations(
52 | startEnter: 'android:anim/fade_in',
53 | startExit: 'android:anim/fade_out',
54 | endEnter: 'android:anim/fade_in',
55 | endExit: 'android:anim/fade_out',
56 | );
57 | return _fade!;
58 | }
59 |
60 | static CustomTabsAnimations? _slideIn;
61 |
62 | static CustomTabsAnimations? _fade;
63 | }
64 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/lib/src/types/custom_tabs_close_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | /// The configuration for close button on the Custom Tab.
4 | @immutable
5 | class CustomTabsCloseButton {
6 | const CustomTabsCloseButton({
7 | this.icon,
8 | this.position,
9 | });
10 |
11 | /// Resource identifier of the close button icon for the Custom Tab.
12 | final String? icon;
13 |
14 | /// The position of the close button on the Custom Tab.
15 | final CustomTabsCloseButtonPosition? position;
16 | }
17 |
18 | /// The position of the close button on the Custom Tab.
19 | enum CustomTabsCloseButtonPosition {
20 | /// Positions the close button at the start of the toolbar.
21 | start(1),
22 |
23 | /// Positions the close button at the end of the toolbar.
24 | end(2);
25 |
26 | @internal
27 | const CustomTabsCloseButtonPosition(this.rawValue);
28 |
29 | @internal
30 | final int rawValue;
31 | }
32 |
33 | /// Build-in close button icons for the Custom Tab.
34 | class CustomTabsCloseButtonIcons {
35 | /// The resource ID of build-in back arrow button icon.
36 | static String get back => "fct_ic_arrow_back";
37 | }
38 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/lib/src/types/custom_tabs_session.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
2 | import 'package:meta/meta.dart';
3 |
4 | /// Represents a session with a Custom Tabs application.
5 | ///
6 | /// A [CustomTabsSession] allows you to establish a connection to a Custom Tabs provider,
7 | /// enabling features like pre-warming the browser and pre-fetching content to improve
8 | /// performance when launching a URL.
9 | @immutable
10 | class CustomTabsSession implements PlatformSession {
11 | const CustomTabsSession(this.packageName);
12 |
13 | /// The package name of the Custom Tabs application corresponding to the session.
14 | final String? packageName;
15 |
16 | @override
17 | String toString() => 'CustomTabsSession: $packageName';
18 | }
19 |
20 | /// Options for creating a Custom Tabs session.
21 | ///
22 | /// [CustomTabsSessionOptions] allows you to customize the behavior of a Custom Tab session
23 | /// when establishing a connection to a Custom Tabs provider.
24 | @immutable
25 | class CustomTabsSessionOptions implements PlatformOptions {
26 | const CustomTabsSessionOptions({
27 | this.prefersDefaultBrowser,
28 | this.fallbackCustomTabs,
29 | });
30 |
31 | /// A Boolean value that determines whether to prioritize the default browser that supports Custom Tabs over Chrome.
32 | final bool? prefersDefaultBrowser;
33 |
34 | /// Package list of non-Chrome browsers supporting Custom Tabs. The top of the list is used with the highest priority.
35 | final List? fallbackCustomTabs;
36 | }
37 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/lib/src/types/custom_tabs_share_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | /// The share state that should be applied to the Custom Tab.
4 | enum CustomTabsShareState {
5 | /// Applies the default share settings depending on the browser.
6 | browserDefault(0),
7 |
8 | /// Explicitly does not show a share option in the tab.
9 | on(1),
10 |
11 | /// Shows a share option in the tab.
12 | off(2);
13 |
14 | @internal
15 | const CustomTabsShareState(this.rawValue);
16 |
17 | @internal
18 | final int rawValue;
19 | }
20 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/lib/src/types/types.dart:
--------------------------------------------------------------------------------
1 | export 'custom_tabs_animations.dart';
2 | export 'custom_tabs_browser.dart';
3 | export 'custom_tabs_close_button.dart';
4 | export 'custom_tabs_color_schemes.dart';
5 | export 'custom_tabs_options.dart';
6 | export 'custom_tabs_session.dart';
7 | export 'custom_tabs_share_state.dart';
8 | export 'partial_custom_tabs.dart';
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/pigeons/messages.dart:
--------------------------------------------------------------------------------
1 | import 'package:pigeon/pigeon.dart';
2 |
3 | @ConfigurePigeon(PigeonOptions(
4 | kotlinOut:
5 | 'android/src/main/kotlin/com/github/droibit/flutter/plugins/customtabs/Messages.kt',
6 | kotlinOptions: KotlinOptions(
7 | package: 'com.github.droibit.flutter.plugins.customtabs',
8 | ),
9 | dartOut: 'lib/src/messages/messages.g.dart',
10 | ))
11 | @HostApi()
12 | abstract class CustomTabsApi {
13 | void launch(
14 | String urlString, {
15 | required bool prefersDeepLink,
16 | Map? options,
17 | });
18 |
19 | void closeAllIfPossible();
20 |
21 | String? warmup(Map? options);
22 |
23 | void mayLaunch(List urls, String sessionPackageName);
24 |
25 | void invalidate(String sessionPackageName);
26 | }
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs_android
2 | description: Android platform implementation of flutter_custom_tabs.
3 | version: 2.3.1
4 | homepage: https://github.com/droibit/flutter_custom_tabs
5 | repository: https://github.com/droibit/flutter_custom_tabs/tree/main/flutter_custom_tabs_android
6 | issue_tracker: https://github.com/droibit/flutter_custom_tabs/issues
7 |
8 | environment:
9 | sdk: ^3.3.0
10 | flutter: ">=3.19.0"
11 |
12 | flutter:
13 | plugin:
14 | implements: flutter_custom_tabs
15 | platforms:
16 | android:
17 | package: com.github.droibit.flutter.plugins.customtabs
18 | pluginClass: CustomTabsPlugin
19 | dartPluginClass: CustomTabsPluginAndroid
20 |
21 | dependencies:
22 | flutter:
23 | sdk: flutter
24 | flutter_custom_tabs_platform_interface: ^2.3.0
25 | meta: ^1.10.0
26 |
27 | dev_dependencies:
28 | flutter_test:
29 | sdk: flutter
30 | flutter_lints: ^4.0.0
31 | pigeon: ^22.7.0
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/test/types/custom_tabs_animations_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_android/flutter_custom_tabs_android.dart';
2 | import 'package:flutter_custom_tabs_android/src/messages/messages.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('CustomTabsAnimations', () {
7 | test('toMessage() returns empty message when animation values are null',
8 | () {
9 | const animations = CustomTabsAnimations();
10 | final actual = animations.toMessage();
11 | expect(actual, isEmpty);
12 | });
13 |
14 | test('toMessage() returns a message with complete options', () {
15 | const animations = CustomTabsAnimations(
16 | startEnter: 'slide_up',
17 | startExit: 'android:anim/fade_out',
18 | endEnter: 'android:anim/fade_in',
19 | endExit: 'slide_down',
20 | );
21 | final actual = animations.toMessage();
22 | expect(actual, {
23 | 'startEnter': animations.startEnter!,
24 | 'startExit': animations.startExit!,
25 | 'endEnter': animations.endEnter!,
26 | 'endExit': animations.endExit!,
27 | });
28 | });
29 | });
30 |
31 | group('CustomTabsSystemAnimations', () {
32 | test('slideIn: creates the built-in slide in animation', () {
33 | final actual = CustomTabsSystemAnimations.slideIn().toMessage();
34 | expect(
35 | actual,
36 | {
37 | 'startEnter': 'android:anim/slide_in_right',
38 | 'startExit': 'android:anim/slide_out_left',
39 | 'endEnter': 'android:anim/slide_in_left',
40 | 'endExit': 'android:anim/slide_out_right',
41 | },
42 | );
43 | });
44 |
45 | test('fade: creates the built-in fade animation', () {
46 | final actual = CustomTabsSystemAnimations.fade().toMessage();
47 | expect(actual, {
48 | 'startEnter': 'android:anim/fade_in',
49 | 'startExit': 'android:anim/fade_out',
50 | 'endEnter': 'android:anim/fade_in',
51 | 'endExit': 'android:anim/fade_out',
52 | });
53 | });
54 | });
55 | }
56 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/test/types/custom_tabs_browser_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_android/flutter_custom_tabs_android.dart';
2 | import 'package:flutter_custom_tabs_android/src/messages/messages.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | test('toMessage() returns empty message when option values are null', () {
7 | const configuration = CustomTabsBrowserConfiguration();
8 | final actual = configuration.toMessage();
9 | expect(actual, isEmpty);
10 | });
11 |
12 | test('toMessage() returns a message with complete options', () {
13 | const configuration = CustomTabsBrowserConfiguration(
14 | prefersDefaultBrowser: true,
15 | fallbackCustomTabs: [
16 | 'org.mozilla.firefox',
17 | 'com.microsoft.emmx',
18 | ],
19 | headers: {'key': 'value'},
20 | );
21 | final actual = configuration.toMessage();
22 | expect(actual, {
23 | 'prefersDefaultBrowser': configuration.prefersDefaultBrowser!,
24 | 'fallbackCustomTabs': configuration.fallbackCustomTabs!,
25 | 'headers': configuration.headers!,
26 | });
27 | });
28 |
29 | test('toMessage() returns a message with external browser options', () {
30 | const configuration = CustomTabsBrowserConfiguration.externalBrowser(
31 | headers: {'key': 'value'},
32 | );
33 | final actual = configuration.toMessage();
34 | expect(actual, {
35 | 'headers': configuration.headers!,
36 | 'prefersExternalBrowser': true,
37 | });
38 | });
39 |
40 | test('toMessage() returns a message with session options', () {
41 | final configuration = CustomTabsBrowserConfiguration.session(
42 | const CustomTabsSession('com.example.browser'),
43 | headers: const {'key': 'value'},
44 | );
45 | final actual = configuration.toMessage();
46 | expect(actual, {
47 | 'sessionPackageName': configuration.sessionPackageName!,
48 | 'headers': configuration.headers!,
49 | });
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/test/types/custom_tabs_close_button_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_android/flutter_custom_tabs_android.dart';
2 | import 'package:flutter_custom_tabs_android/src/messages/messages.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('CustomTabsCloseButton', () {
7 | test('toMessage() returns empty message when option values are null', () {
8 | const button = CustomTabsCloseButton();
9 | final actual = button.toMessage();
10 | expect(actual, isEmpty);
11 | });
12 |
13 | test('toMessage() returns a message with complete options', () {
14 | const button = CustomTabsCloseButton(
15 | icon: 'close_icon',
16 | position: CustomTabsCloseButtonPosition.start,
17 | );
18 | final actual = button.toMessage();
19 | expect(actual, {
20 | 'icon': button.icon,
21 | 'position': button.position!.rawValue,
22 | });
23 | });
24 | });
25 |
26 | group('CustomTabsCloseButtonPosition', () {
27 | test('returns associated value', () {
28 | expect(CustomTabsCloseButtonPosition.start.rawValue, 1);
29 | expect(CustomTabsCloseButtonPosition.end.rawValue, 2);
30 | });
31 | });
32 |
33 | group('CustomTabsCloseButtonIcons', () {
34 | test('back: gets the resource ID of build-in back arrow button icon', () {
35 | expect(CustomTabsCloseButtonIcons.back, 'fct_ic_arrow_back');
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/test/types/custom_tabs_session_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_android/flutter_custom_tabs_android.dart';
2 | import 'package:flutter_custom_tabs_android/src/messages/messages.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | test('toMessage() returns empty message when option values are null', () {
7 | const options = CustomTabsSessionOptions();
8 | final actual = options.toMessage();
9 | expect(actual, isEmpty);
10 | });
11 |
12 | test('toMessage() returns a message with complete options', () {
13 | const options = CustomTabsSessionOptions(
14 | prefersDefaultBrowser: true,
15 | fallbackCustomTabs: [
16 | 'org.mozilla.firefox',
17 | 'com.microsoft.emmx',
18 | ],
19 | );
20 | final actual = options.toMessage();
21 | expect(actual, {
22 | 'prefersDefaultBrowser': options.prefersDefaultBrowser!,
23 | 'fallbackCustomTabs': options.fallbackCustomTabs!,
24 | });
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_android/test/types/custom_tabs_share_state_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_android/flutter_custom_tabs_android.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | void main() {
5 | test('returns associated value', () {
6 | expect(CustomTabsShareState.browserDefault.rawValue, 0);
7 | expect(CustomTabsShareState.on.rawValue, 1);
8 | expect(CustomTabsShareState.off.rawValue, 2);
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | .packages
30 | build/
31 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "e1e47221e86272429674bec4f1bd36acc4fc7b77"
8 | channel: "stable"
9 |
10 | project_type: plugin
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
17 | base_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
18 | - platform: ios
19 | create_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
20 | base_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/.swiftformat:
--------------------------------------------------------------------------------
1 | # file options
2 |
3 | --exclude Carthage,Pods,.swiftpm,ios/**/messages.g.swift
4 |
5 | # format options
6 |
7 | --allman false
8 | --commas always
9 | --elseposition same-line
10 | --header ignore
11 | --ifdef indent
12 | --indent 2
13 | --importgrouping testable-bottom
14 | --linebreaks lf
15 | --maxwidth 120
16 | --operatorfunc spaced
17 | --patternlet hoist
18 | --ranges spaced
19 | --self remove
20 | --semicolons inline
21 | --stripunusedargs closure-only
22 | --swiftversion 5.9
23 | --trimwhitespace always
24 | --wraparguments preserve
25 | --wrapcollections preserve
26 | --typeblanklines remove
27 | --hexgrouping 10
28 |
29 | # rules
30 |
31 | --disable strongOutlets, docComments
32 | --enable isEmpty,blockComments
33 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.4.0
2 |
3 | - Adds Swift Package Manager(SPM) support.
4 | - Removes unnecessary bridge code and cleanup for SPM support.
5 | - Adds .editorconfig for consistent code style.
6 |
7 | ## 2.3.1
8 |
9 | - No changes except for version bump.
10 |
11 | ## 2.3.0
12 |
13 | - Improves documentation for the `SheetPresentationControllerConfiguration` class.
14 | - Updates minimum iOS version to 12.0
15 | - Updates minimum supported SDK version to Flutter 3.19.0/Dart 3.3.
16 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.2.0.
17 | - Updates minimum required `pigeon` version to 22.7.0.
18 |
19 | ## 2.2.0
20 |
21 | - Adds temporary comments to suppress `Color.value` deprecation warnings.
22 | - Updates Swift code to use modern syntax.
23 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.2.0.
24 |
25 | ## 2.2.0-dev.1
26 |
27 | - Implements `warmup`, `mayLaunch`, and `invalidate` methods in `CustomTabsPluginIOS` for performance optimization.
28 | - Updates minimum supported SDK version to Flutter 3.16.0/Dart 3.2.
29 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.2.0-dev.1.
30 | - Updates minimum required `pigeon` version to 21.1.0.
31 |
32 | ## 2.1.0
33 |
34 | - Updates minimum supported SDK version to Flutter 3.10/Dart 3.
35 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.1.0.
36 | - Updates minimum required `pigeon` version to 17.0.0.
37 | - Adds privacy manifest.
38 | - Updates the CocoaPods dependency to version 1.15.2.
39 | - Refactors `SFSafariViewController` dismissal handling.
40 |
41 | ## 2.0.0
42 |
43 | - Fixes the LICENSE file.
44 |
45 | ## 2.0.0-beta.1
46 |
47 | - Supports launching a URL in an external browser.
48 | - Adopts the [Pigeon](https://pub.dev/packages/pigeon) code generation tool.
49 | - Moves `SafariViewControllerOptions` from `flutter_custom_tabs_platform_interface` package.
50 | - Adds unit tests for iOS platform.
51 | - Properly callback URL launch results.
52 |
53 | ## 2.0.0-beta
54 |
55 | - Initial release of the `flutter_custom_tabs` iOS implementation.
56 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: bootstrap
2 | bootstrap:
3 | @mint bootstrap
4 |
5 | .PHONY: format
6 | format:
7 | @xcrun --sdk macosx mint run swiftformat swiftformat .
8 |
9 | .PHONY: lint
10 | lint:
11 | @xcrun --sdk macosx mint run swiftformat swiftformat --lint .
12 | @xcrun --sdk macosx mint run swiftlint swiftlint lint
13 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/Mintfile:
--------------------------------------------------------------------------------
1 | realm/SwiftLint@0.58.2
2 | nicklockwood/SwiftFormat@0.55.5
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/README.md:
--------------------------------------------------------------------------------
1 | # flutter_custom_tabs_ios
2 |
3 | iOS platform implementation of [flutter_custom_tabs][1].
4 |
5 | ## Usage
6 |
7 | ### Import the package
8 |
9 | This package has been the endorsed implementation of `flutter_custom_tabs` for the iOS platform since version `2.0.0`. This means it will be automatically included in your app when you add it, so you do not need to include it in your `pubspec.yaml`.
10 |
11 | [1]: https://pub.dev/packages/flutter_custom_tabs
12 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | **/ios/Flutter/.last_build_id
29 | .dart_tool/
30 | .flutter-plugins
31 | .flutter-plugins-dependencies
32 | .packages
33 | .pub-cache/
34 | .pub/
35 | /build/
36 | # Remove the following pattern if you wish to check in your lock file
37 | pubspec.lock
38 |
39 | # Symbolication related
40 | app.*.symbols
41 |
42 | # Obfuscation related
43 | app.*.map.json
44 |
45 | # Android Studio will place build artifacts here
46 | /android/app/debug
47 | /android/app/profile
48 | /android/app/release
49 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/Makefile:
--------------------------------------------------------------------------------
1 | TEST_SCHEME := Runner
2 | TEST_PROJECT := ios/Runner.xcworkspace
3 | TEST_SDK := iphonesimulator
4 | TESET_DESTINATION := 'platform=iOS Simulator,OS=latest,name=iPhone 16'
5 |
6 | .PHONY: test
7 | test:
8 | set -o pipefail && \
9 | xcodebuild test \
10 | -workspace $(TEST_PROJECT) \
11 | -configuration Debug \
12 | -scheme $(TEST_SCHEME) \
13 | -sdk $(TEST_SDK) \
14 | -destination $(TESET_DESTINATION) \
15 | CODE_SIGNING_ALLOWED='NO' \
16 | | xcbeautify
17 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_custom_tabs_ios_example
2 |
3 | Demonstrates how to use the flutter_custom_tabs_ios plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | exclude:
3 | # Ignore generated files
4 | - '**/*.g.dart'
5 | - '**/*.mocks.dart' # Mockito @GenerateMocks
6 |
7 | linter:
8 | rules:
9 | depend_on_referenced_packages: false
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{swift,h,m}]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | max_line_length = 120
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 |
36 | target 'RunnerTests' do
37 | inherit! :search_paths
38 | end
39 | end
40 |
41 | post_install do |installer|
42 | installer.pods_project.targets.each do |target|
43 | flutter_additional_ios_build_settings(target)
44 |
45 | target.build_configurations.each do |config|
46 | pod_ios_deployment_target = Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
47 | if pod_ios_deployment_target < Gem::Version.new('12.0')
48 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_custom_tabs_ios (2.4.0):
4 | - Flutter
5 | - integration_test (0.0.1):
6 | - Flutter
7 |
8 | DEPENDENCIES:
9 | - Flutter (from `Flutter`)
10 | - flutter_custom_tabs_ios (from `.symlinks/plugins/flutter_custom_tabs_ios/ios`)
11 | - integration_test (from `.symlinks/plugins/integration_test/ios`)
12 |
13 | EXTERNAL SOURCES:
14 | Flutter:
15 | :path: Flutter
16 | flutter_custom_tabs_ios:
17 | :path: ".symlinks/plugins/flutter_custom_tabs_ios/ios"
18 | integration_test:
19 | :path: ".symlinks/plugins/integration_test/ios"
20 |
21 | SPEC CHECKSUMS:
22 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
23 | flutter_custom_tabs_ios: 89e60122b553c69a79bfd45eb8eb99d911c1a9c0
24 | integration_test: 13825b8a9334a850581300559b8839134b124670
25 |
26 | PODFILE CHECKSUM: 2f40fced8ccaa4f568954e339e48cea7e274cbdb
27 |
28 | COCOAPODS: 1.15.2
29 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Flutter Custom Tabs iOS Example
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | flutter_custom_tabs_ios_example
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UIApplicationSupportsIndirectInputEvents
30 |
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | UIMainStoryboardFile
34 | Main
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | UIViewControllerBasedStatusBarAppearance
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-App-1024x1024@1x.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_ios/example/ios/Runner/Resources/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Resources/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/ios/RunnerTests/MockLauncher.swift:
--------------------------------------------------------------------------------
1 | // swiftlint:disable legacy_objc_type
2 | import Foundation
3 | @testable import flutter_custom_tabs_ios
4 |
5 | class MockLauncher: Launcher {
6 | private var openCompletionHandlerResults = [Bool]()
7 | private var presentCompletionHandlerResults = [Bool]()
8 | private var prewarmConnectionsResults = [String?]()
9 | private(set) var openArguments = [OpenArgument]()
10 | private(set) var presentArguments = [PresentArgument]()
11 | private(set) var prewarmConnectionsArguments = [PrewarmConnectionsArgument]()
12 | private(set) var invalidatePrewarmingSessionArguments = [InvalidatePrewarmingSessionArgument]()
13 |
14 | override init() {}
15 |
16 | func setOpenCompletionHandlerResults(_ values: Bool...) {
17 | openCompletionHandlerResults.append(contentsOf: values)
18 | }
19 |
20 | func setPresentCompletionHandlerResults(_ values: Bool...) {
21 | presentCompletionHandlerResults.append(contentsOf: values)
22 | }
23 |
24 | func setPrewarmConnectionsResults(_ values: String?...) {
25 | prewarmConnectionsResults.append(contentsOf: values)
26 | }
27 |
28 | override func open(
29 | _ url: URL,
30 | options: [UIApplication.OpenExternalURLOptionsKey: Any] = [:],
31 | completionHandler completion: ((Bool) -> Void)? = nil
32 | ) {
33 | openArguments.append(.init(url: url, options: options))
34 |
35 | let opened = openCompletionHandlerResults.removeFirst()
36 | completion?(opened)
37 | }
38 |
39 | override func present(_ viewControllerToPresent: UIViewController, completion: ((Bool) -> Void)? = nil) {
40 | presentArguments.append(
41 | .init(viewControllerToPresent: viewControllerToPresent)
42 | )
43 |
44 | let presented = presentCompletionHandlerResults.removeFirst()
45 | completion?(presented)
46 | }
47 |
48 | override func prewarmConnections(to urls: [URL]) -> String? {
49 | prewarmConnectionsArguments.append(.init(urls: urls))
50 | return prewarmConnectionsResults.removeFirst()
51 | }
52 |
53 | override func invalidatePrewarmingSession(withId sessionId: String) {
54 | invalidatePrewarmingSessionArguments.append(.init(sessionId: sessionId))
55 | }
56 | }
57 |
58 | extension MockLauncher {
59 | struct OpenArgument {
60 | let url: URL?
61 | let options: [UIApplication.OpenExternalURLOptionsKey: Any]
62 | }
63 |
64 | struct PresentArgument {
65 | let viewControllerToPresent: UIViewController?
66 | }
67 |
68 | struct PrewarmConnectionsArgument {
69 | let urls: [URL]
70 | }
71 |
72 | struct InvalidatePrewarmingSessionArgument {
73 | let sessionId: String
74 | }
75 | }
76 |
77 | extension MockLauncher.OpenArgument: Equatable {
78 | static func == (lhs: MockLauncher.OpenArgument, rhs: MockLauncher.OpenArgument) -> Bool {
79 | lhs.url == rhs.url && NSDictionary(dictionary: lhs.options).isEqual(to: rhs.options)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs_ios_example
2 | description: Demonstrates how to use the flutter_custom_tabs_ios plugin.
3 | publish_to: 'none'
4 |
5 | environment:
6 | sdk: ^3.3.0
7 | flutter: ">=3.19.0"
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 | flutter_custom_tabs_ios:
13 | path: ../
14 | cupertino_icons: ^1.0.2
15 |
16 | dev_dependencies:
17 | integration_test:
18 | sdk: flutter
19 | flutter_test:
20 | sdk: flutter
21 | flutter_lints: ^4.0.0
22 |
23 | flutter:
24 | uses-material-design: true
25 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/Generated.xcconfig
37 | /Flutter/ephemeral/
38 | /Flutter/flutter_export_environment.sh
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_ios/ios/Assets/.gitkeep
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/ios/flutter_custom_tabs_ios.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
3 | # Run `pod lib lint flutter_custom_tabs_ios.podspec` to validate before publishing.
4 | #
5 | Pod::Spec.new do |s|
6 | s.name = 'flutter_custom_tabs_ios'
7 | s.version = '2.4.0'
8 | s.summary = 'iOS platform implementation of flutter_custom_tabs.'
9 | s.description = <<-DESC
10 | iOS platform implementation of flutter_custom_tabs.
11 | DESC
12 | s.homepage = 'https://github.com/droibit/flutter_custom_tabs/'
13 | s.license = { :type => 'Apache-2.0', :file => '../LICENSE' }
14 | s.author = 'Shinya Kumagai'
15 | s.source = { :http => 'https://github.com/droibit/flutter_custom_tabs/tree/develop/flutter_custom_tabs_ios' }
16 | s.documentation_url = 'https://pub.dev/packages/flutter_custom_tabs'
17 | s.source_files = 'flutter_custom_tabs_ios/Sources/**/*.swift'
18 | s.dependency 'Flutter'
19 | s.platform = :ios, '12.0'
20 | s.swift_version = '5.0'
21 | s.resource_bundles = {'flutter_custom_tabs_ios_privacy' => ['flutter_custom_tabs_ios/Sources/flutter_custom_tabs_ios/Resources/PrivacyInfo.xcprivacy']}
22 | # Flutter.framework does not contain a i386 slice.
23 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
24 | end
25 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/ios/flutter_custom_tabs_ios/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "flutter_custom_tabs_ios",
8 | platforms: [
9 | .iOS("12.0"),
10 | ],
11 | products: [
12 | .library(name: "flutter-custom-tabs-ios", targets: ["flutter_custom_tabs_ios"]),
13 | ],
14 | dependencies: [],
15 | targets: [
16 | .target(
17 | name: "flutter_custom_tabs_ios",
18 | dependencies: [],
19 | resources: [
20 | .process("Resources"),
21 | ]
22 | ),
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/ios/flutter_custom_tabs_ios/Sources/flutter_custom_tabs_ios/CustomTabsPlugin.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import SafariServices
3 |
4 | public class CustomTabsPlugin: NSObject, FlutterPlugin, CustomTabsApi {
5 | public static func register(with registrar: FlutterPluginRegistrar) {
6 | let plugin = CustomTabsPlugin()
7 | CustomTabsApiSetup.setUp(binaryMessenger: registrar.messenger(), api: plugin)
8 | registrar.publish(plugin)
9 | }
10 |
11 | private let launcher: Launcher
12 |
13 | init(launcher: Launcher = Launcher()) {
14 | self.launcher = launcher
15 | }
16 |
17 | func launchURL(
18 | _ urlString: String,
19 | prefersDeepLink: Bool,
20 | options: SFSafariViewControllerOptions?,
21 | completion: @escaping (Result) -> Void
22 | ) {
23 | let url = URL(string: urlString)!
24 | if prefersDeepLink {
25 | launcher.open(url, options: [.universalLinksOnly: true]) { [weak self] opened in
26 | if opened {
27 | completion(.success(()))
28 | } else {
29 | self?.launchURL(url, options: options, completion: completion)
30 | }
31 | }
32 | } else {
33 | launchURL(url, options: options, completion: completion)
34 | }
35 | }
36 |
37 | func closeAllIfPossible(completion: @escaping (Result) -> Void) {
38 | launcher.dismissAll {
39 | completion(.success(()))
40 | }
41 | }
42 |
43 | func mayLaunchURLs(_ urlStrings: [String]) throws -> String? {
44 | let urls = urlStrings.map { URL(string: $0)! }
45 | guard let sessionId = launcher.prewarmConnections(to: urls) else {
46 | return nil
47 | }
48 | return sessionId
49 | }
50 |
51 | func invalidateSession(_ sessionId: String) throws {
52 | launcher.invalidatePrewarmingSession(withId: sessionId)
53 | }
54 |
55 | // MARK: - Private
56 |
57 | private func launchURL(
58 | _ url: URL,
59 | options: SFSafariViewControllerOptions?,
60 | completion: @escaping (Result) -> Void
61 | ) {
62 | guard let options else {
63 | launcher.open(url) { opened in
64 | completion(
65 | opened ? .success(()) : .failure(
66 | PigeonError(message: "Failed to launch external browser.")
67 | )
68 | )
69 | }
70 | return
71 | }
72 |
73 | let safariViewController = SFSafariViewController.make(url: url, options: options)
74 | launcher.present(safariViewController) { presented in
75 | completion(
76 | presented ? .success(()) : .failure(
77 | PigeonError(message: "Failed to launch SFSafariViewController.")
78 | )
79 | )
80 | }
81 | }
82 | }
83 |
84 | extension PigeonError {
85 | convenience init(message: String) {
86 | self.init(code: Self.errorCode, message: message, details: nil)
87 | }
88 |
89 | static let errorCode = "LAUNCH_ERROR"
90 | }
91 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/ios/flutter_custom_tabs_ios/Sources/flutter_custom_tabs_ios/Resources/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTrackingDomains
6 |
7 | NSPrivacyAccessedAPITypes
8 |
9 | NSPrivacyCollectedDataTypes
10 |
11 | NSPrivacyTracking
12 |
13 |
14 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/lib/flutter_custom_tabs_ios.dart:
--------------------------------------------------------------------------------
1 | export 'src/types/types.dart';
2 | export 'src/custom_tabs_plugin_ios.dart';
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/lib/src/custom_tabs_plugin_ios.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
2 |
3 | import 'messages/messages.dart';
4 | import 'types/types.dart';
5 |
6 | /// The iOS implementation of [CustomTabsPlatform].
7 | ///
8 | /// This class implements the `package:flutter_custom_tabs` functionality for iOS.
9 | class CustomTabsPluginIOS extends CustomTabsPlatform {
10 | /// Creates a new plugin implementation instance.
11 | CustomTabsPluginIOS({
12 | CustomTabsApi? api,
13 | }) : _hostApi = api ?? CustomTabsApi();
14 |
15 | final CustomTabsApi _hostApi;
16 |
17 | /// Registers this class as the default instance of [CustomTabsPlatform].
18 | static void registerWith() {
19 | CustomTabsPlatform.instance = CustomTabsPluginIOS();
20 | }
21 |
22 | @override
23 | Future launch(
24 | String urlString, {
25 | bool prefersDeepLink = false,
26 | PlatformOptions? customTabsOptions,
27 | PlatformOptions? safariVCOptions,
28 | }) {
29 | final SFSafariViewControllerOptions? message;
30 | if (safariVCOptions == null) {
31 | message = null;
32 | } else {
33 | message = (safariVCOptions is SafariViewControllerOptions)
34 | ? safariVCOptions.toMessage()
35 | : SFSafariViewControllerOptions();
36 | }
37 |
38 | return _hostApi.launch(
39 | urlString,
40 | prefersDeepLink: prefersDeepLink,
41 | options: message,
42 | );
43 | }
44 |
45 | @override
46 | Future closeAllIfPossible() async {
47 | return _hostApi.closeAllIfPossible();
48 | }
49 |
50 | @override
51 | Future warmup([PlatformOptions? options]) async {
52 | // No-op on iOS.
53 | return null;
54 | }
55 |
56 | @override
57 | Future mayLaunch(
58 | List urls, {
59 | PlatformSession? session,
60 | }) async {
61 | final sessionId = await _hostApi.mayLaunch(urls);
62 | return SafariViewPrewarmingSession(sessionId);
63 | }
64 |
65 | @override
66 | Future invalidate(PlatformSession session) async {
67 | if (session is! SafariViewPrewarmingSession) {
68 | throw ArgumentError.value(
69 | session,
70 | 'session',
71 | 'must be an instance of SafariViewPrewarmingSession.',
72 | );
73 | }
74 |
75 | final sessionId = session.id;
76 | if (sessionId == null) {
77 | return;
78 | }
79 | return _hostApi.invalidate(sessionId);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/lib/src/messages/messages.dart:
--------------------------------------------------------------------------------
1 | export 'messages.g.dart';
2 | export 'type_conversion.dart';
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/lib/src/messages/type_conversion.dart:
--------------------------------------------------------------------------------
1 | import 'messages.g.dart';
2 | import '../types/types.dart';
3 |
4 | extension SafariViewControllerOptionsConverter on SafariViewControllerOptions {
5 | SFSafariViewControllerOptions toMessage() {
6 | // Temporarily suppress deprecation warnings until migration to `Color.toARGB32`.
7 | // See: https://github.com/flutter/flutter/issues/160184#issuecomment-2560184639
8 | return SFSafariViewControllerOptions(
9 | // ignore: deprecated_member_use
10 | preferredBarTintColor: preferredBarTintColor?.value,
11 | // ignore: deprecated_member_use
12 | preferredControlTintColor: preferredControlTintColor?.value,
13 | barCollapsingEnabled: barCollapsingEnabled,
14 | entersReaderIfAvailable: entersReaderIfAvailable,
15 | dismissButtonStyle: dismissButtonStyle?.rawValue,
16 | modalPresentationStyle: modalPresentationStyle?.rawValue,
17 | pageSheet: pageSheet?.toMessage(),
18 | );
19 | }
20 | }
21 |
22 | extension SheetPresentationControllerConfigurationConverter
23 | on SheetPresentationControllerConfiguration {
24 | UISheetPresentationControllerConfiguration toMessage() {
25 | return UISheetPresentationControllerConfiguration(
26 | detents: detents.map((e) => e.rawValue).toList(),
27 | largestUndimmedDetentIdentifier:
28 | largestUndimmedDetentIdentifier?.rawValue,
29 | prefersScrollingExpandsWhenScrolledToEdge:
30 | prefersScrollingExpandsWhenScrolledToEdge,
31 | prefersGrabberVisible: prefersGrabberVisible,
32 | prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight,
33 | preferredCornerRadius: preferredCornerRadius,
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/lib/src/types/safari_view_prewarming_session.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
2 | import 'package:meta/meta.dart';
3 |
4 | /// The session for the prewarming of [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller).
5 | @immutable
6 | class SafariViewPrewarmingSession implements PlatformSession {
7 | const SafariViewPrewarmingSession(this.id);
8 |
9 | final String? id;
10 |
11 | @override
12 | String toString() => 'SafariViewPrewarmingSession: $id';
13 | }
14 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/lib/src/types/sheet_presentation_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | /// Configuration for presenting [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) as a customizable sheet.
4 | ///
5 | /// This class provides control over iOS 15's `UISheetPresentationController`, enabling rich sheet-based
6 | /// presentations with the following capabilities:
7 | ///
8 | /// - **Customizable Height**: Configure the sheet to appear at different heights using [detents]
9 | /// (medium: approximately half-screen, large: full-screen)
10 | /// - **Interactive Resizing**: Users can resize the sheet by dragging when multiple detents are specified
11 | /// - **Visual Indicators**: Control the visibility of the grabber handle with [prefersGrabberVisible]
12 | /// - **Background Interaction**: Allow interaction with content behind the sheet when using
13 | /// [largestUndimmedDetentIdentifier]
14 | /// - **Appearance Control**: Customize corner radius and edge attachment behavior in different orientations
15 | ///
16 | /// ### See also
17 | /// - [UISheetPresentationController](https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller)
18 | @immutable
19 | class SheetPresentationControllerConfiguration {
20 | const SheetPresentationControllerConfiguration({
21 | required this.detents,
22 | this.largestUndimmedDetentIdentifier,
23 | this.prefersScrollingExpandsWhenScrolledToEdge,
24 | this.prefersGrabberVisible,
25 | this.prefersEdgeAttachedInCompactHeight,
26 | this.preferredCornerRadius,
27 | });
28 |
29 | /// The set of heights where a sheet can rest.
30 | final Set detents;
31 |
32 | /// The largest detent that doesn’t dim the view underneath the sheet.
33 | final SheetPresentationControllerDetent? largestUndimmedDetentIdentifier;
34 |
35 | /// A Boolean value that determines whether scrolling expands the sheet to a larger detent.
36 | final bool? prefersScrollingExpandsWhenScrolledToEdge;
37 |
38 | /// A Boolean value that determines whether the sheet shows a grabber at the top.
39 | final bool? prefersGrabberVisible;
40 |
41 | /// A Boolean value that determines whether the sheet attaches to the bottom edge of the screen in a compact-height size class.
42 | final bool? prefersEdgeAttachedInCompactHeight;
43 |
44 | /// The corner radius in logical pixels that the sheet attempts to present with.
45 | final double? preferredCornerRadius;
46 | }
47 |
48 | /// An object that represents a height where a sheet naturally rests.
49 | enum SheetPresentationControllerDetent {
50 | /// A system detent for a sheet at full height.
51 | large("large"),
52 |
53 | /// A system detent for a sheet that’s approximately half the height of the screen, and is inactive in compact height.
54 | medium("medium");
55 |
56 | @internal
57 | const SheetPresentationControllerDetent(this.rawValue);
58 |
59 | @internal
60 | final String rawValue;
61 | }
62 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/lib/src/types/types.dart:
--------------------------------------------------------------------------------
1 | export 'safari_view_controller_options.dart';
2 | export 'safari_view_prewarming_session.dart';
3 | export 'sheet_presentation_controller.dart';
4 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/pigeons/messages.dart:
--------------------------------------------------------------------------------
1 | import 'package:pigeon/pigeon.dart';
2 |
3 | @ConfigurePigeon(PigeonOptions(
4 | swiftOut: 'ios/flutter_custom_tabs_ios/Sources/flutter_custom_tabs_ios/messages.g.swift',
5 | dartOut: 'lib/src/messages/messages.g.dart',
6 | ))
7 | @HostApi()
8 | abstract class CustomTabsApi {
9 | @async
10 | @SwiftFunction('launchURL(_:prefersDeepLink:options:)')
11 | void launch(
12 | String urlString, {
13 | required bool prefersDeepLink,
14 | SFSafariViewControllerOptions? options,
15 | });
16 |
17 | @async
18 | void closeAllIfPossible();
19 |
20 | @SwiftFunction('mayLaunchURLs(_:)')
21 | String? mayLaunch(List urlStrings);
22 |
23 | @SwiftFunction('invalidateSession(_:)')
24 | void invalidate(String sessionId);
25 | }
26 |
27 | class SFSafariViewControllerOptions {
28 | const SFSafariViewControllerOptions({
29 | this.preferredBarTintColor,
30 | this.preferredControlTintColor,
31 | this.barCollapsingEnabled,
32 | this.entersReaderIfAvailable,
33 | this.dismissButtonStyle,
34 | this.modalPresentationStyle,
35 | this.pageSheet,
36 | });
37 |
38 | final int? preferredBarTintColor;
39 | final int? preferredControlTintColor;
40 | final bool? barCollapsingEnabled;
41 | final bool? entersReaderIfAvailable;
42 | final int? dismissButtonStyle;
43 | final int? modalPresentationStyle;
44 | final UISheetPresentationControllerConfiguration? pageSheet;
45 | }
46 |
47 | class UISheetPresentationControllerConfiguration {
48 | const UISheetPresentationControllerConfiguration({
49 | required this.detents,
50 | this.largestUndimmedDetentIdentifier,
51 | this.prefersScrollingExpandsWhenScrolledToEdge,
52 | this.prefersGrabberVisible,
53 | this.prefersEdgeAttachedInCompactHeight,
54 | this.preferredCornerRadius,
55 | });
56 |
57 | final List detents;
58 | final String? largestUndimmedDetentIdentifier;
59 | final bool? prefersScrollingExpandsWhenScrolledToEdge;
60 | final bool? prefersGrabberVisible;
61 | final bool? prefersEdgeAttachedInCompactHeight;
62 | final double? preferredCornerRadius;
63 | }
64 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs_ios
2 | description: iOS platform implementation of flutter_custom_tabs.
3 | version: 2.4.0
4 | homepage: https://github.com/droibit/flutter_custom_tabs
5 | repository: https://github.com/droibit/flutter_custom_tabs/tree/main/flutter_custom_tabs_ios
6 | issue_tracker: https://github.com/droibit/flutter_custom_tabs/issues
7 |
8 | environment:
9 | sdk: ^3.3.0
10 | flutter: ">=3.19.0"
11 |
12 | dependencies:
13 | flutter:
14 | sdk: flutter
15 | flutter_custom_tabs_platform_interface: ^2.3.0
16 | meta: ^1.10.0
17 |
18 | dev_dependencies:
19 | flutter_test:
20 | sdk: flutter
21 | flutter_lints: ^4.0.0
22 | pigeon: ^22.7.0
23 |
24 | flutter:
25 | plugin:
26 | implements: flutter_custom_tabs
27 | platforms:
28 | ios:
29 | pluginClass: CustomTabsPlugin
30 | dartPluginClass: CustomTabsPluginIOS
31 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_ios/test/types/sheet_presentation_controller_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_ios/flutter_custom_tabs_ios.dart';
2 | import 'package:flutter_custom_tabs_ios/src/messages/messages.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('SheetPresentationControllerConfiguration', () {
7 | test('toMessage() returns expected message with default values', () {
8 | const configuration = SheetPresentationControllerConfiguration(
9 | detents: {SheetPresentationControllerDetent.large},
10 | );
11 | final actual = configuration.toMessage();
12 | expect(
13 | actual.detents,
14 | containsAll([configuration.detents.first.rawValue]),
15 | );
16 | expect(actual.largestUndimmedDetentIdentifier, isNull);
17 | expect(actual.prefersScrollingExpandsWhenScrolledToEdge, isNull);
18 | expect(actual.prefersGrabberVisible, isNull);
19 | expect(actual.prefersEdgeAttachedInCompactHeight, isNull);
20 | expect(actual.preferredCornerRadius, isNull);
21 | });
22 |
23 | test('toMessage() returns a message with complete options', () {
24 | const configuration = SheetPresentationControllerConfiguration(
25 | detents: {SheetPresentationControllerDetent.large},
26 | largestUndimmedDetentIdentifier:
27 | SheetPresentationControllerDetent.medium,
28 | prefersScrollingExpandsWhenScrolledToEdge: true,
29 | prefersGrabberVisible: false,
30 | prefersEdgeAttachedInCompactHeight: true,
31 | preferredCornerRadius: 8.0,
32 | );
33 |
34 | final actual = configuration.toMessage();
35 | expect(
36 | actual.detents,
37 | containsAll([configuration.detents.first.rawValue]),
38 | );
39 | expect(
40 | actual.largestUndimmedDetentIdentifier,
41 | configuration.largestUndimmedDetentIdentifier!.rawValue,
42 | );
43 | expect(
44 | actual.prefersScrollingExpandsWhenScrolledToEdge,
45 | configuration.prefersScrollingExpandsWhenScrolledToEdge,
46 | );
47 | expect(actual.prefersGrabberVisible, configuration.prefersGrabberVisible);
48 | expect(
49 | actual.prefersEdgeAttachedInCompactHeight,
50 | configuration.prefersEdgeAttachedInCompactHeight,
51 | );
52 | expect(actual.preferredCornerRadius, configuration.preferredCornerRadius);
53 | });
54 | });
55 |
56 | group('SheetPresentationControllerDetent', () {
57 | test('rawValue returns associated value', () {
58 | expect(SheetPresentationControllerDetent.large.rawValue, 'large');
59 | expect(SheetPresentationControllerDetent.medium.rawValue, 'medium');
60 | });
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/.gitignore:
--------------------------------------------------------------------------------
1 | # Intellij project files
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .idea/
6 | !.idea/codeStyleSettings.xml
7 | !.idea/runConfigurations/*
8 |
9 | # Files and directories created by pub
10 | .dart_tool/
11 | .packages
12 | .pub/
13 | build/
14 | # Remove the following pattern if you wish to check in your lock file
15 | pubspec.lock
16 |
17 | # Directory created by dartdoc
18 | doc/api/
19 |
20 | # Covarage
21 | /coverage/
22 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: b22742018b3edf16c6cadd7b76d9db5e7f9064b5
8 | channel: stable
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.3.0
2 |
3 | - Updates minimum supported SDK version to Flutter 3.19.0/Dart 3.3.
4 |
5 | ## 2.2.0
6 |
7 | - No changes except for version bump.
8 |
9 | ## 2.2.0-dev.1
10 |
11 | - Adds `warmup`, `mayLaunch`, and `invalidate` methods to `CustomTabsPlatform` for performance optimization.
12 | - Adds `PlatformSession` as a base session for platform packages.
13 | - Updates minimum supported SDK version to Flutter 3.16.0/Dart 3.2.
14 |
15 | ## 2.1.0
16 |
17 | - Updates minimum supported SDK version to Flutter 3.10/Dart 3.
18 |
19 | ## 2.0.0
20 |
21 | - No changes except for version bump.
22 |
23 | ## 2.0.0-beta.2
24 |
25 | - Adds `PlatformOptions` as a base option for platform packages.
26 | - Moves `CustomTabsOptions` to `flutter_custom_tabs_android` package plugin.
27 | - Moves `SafariViewControllerOptions` to `flutter_custom_tabs_ios` package plugin.
28 |
29 | ## 2.0.0-beta.1
30 |
31 | - Refactors Custom Tabs browser configurations to `CustomTabsBrowserConfiguration`.
32 | - Adds an option to `CustomTabsBrowserConfiguration` to prioritize the default browser that supports Custom Tabs over Chrome.
33 |
34 | ## 2.0.0-beta
35 |
36 | - Supports the launch of a deep link URL.
37 | - Renames the `CustomTabsOption` class to `CustomTabsOptions`.
38 | - Updates `CustomTabsOptions` to improve compatibility with Custom Tabs (`androidx.browser`) v1.5.0.
39 | - Some options have been renamed.
40 | - Changes the method of specifying colors to `CustomTabsColorSchemes`.
41 | - Changes the method of specifying share state to `CustomTabsShareState`.
42 | - Changes the method of specifying the close button to `CustomTabsCloseButton`.
43 | - Adds options for Partial Custom Tabs to `CustomTabsOptions`.
44 | - Renames the `CustomTabsAnimation` class to `CustomTabsAnimations`.
45 | - Renames the `SafariViewControllerOption` class to `SafariViewControllerOptions`.
46 | - Removes the `statusBarBrightness` property from `SafariViewControllerOptions`.
47 | - Updates the minimum supported SDK version to Flutter 3.0.0.
48 |
49 | ## 1.2.0
50 |
51 | - Added `modalPresentationStyle` to `SafariViewControllerOption` to customize the presentation style.
52 |
53 | ## 1.1.0
54 |
55 | - Add interface to manually close a Custom Tab.
56 |
57 | ## 1.0.1
58 |
59 | - Add an option(`statusBarBrightness`) to specify the brightness of the application status bar for iOS.
60 |
61 | ## 1.0.0
62 |
63 | - Initial release.
64 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/README.md:
--------------------------------------------------------------------------------
1 | # flutter_custom_tabs_platform_interface
2 |
3 | A common platform interface for the [`flutter_custom_tabs`][1] plugin.
4 |
5 | This interface allows platform-specific implementations of the `flutter_custom_tabs`
6 | plugin, as well as the plugin itself, to ensure they are supporting the
7 | same interface.
8 |
9 | # Usage
10 |
11 | To implement a new platform-specific implementation of `flutter_custom_tabs`, extend
12 | [`CustomTabsPlatform`][2] with an implementation that performs the
13 | platform-specific behavior, and when you register your plugin, set the default
14 | `CustomTabsPlatform` by calling
15 | `CustomTabsPlatform.instance = MyPlatformCustomTabs()`.
16 |
17 | # Note on breaking changes
18 |
19 | Strongly prefer non-breaking changes (such as adding a method to the interface)
20 | over breaking changes for this package.
21 |
22 | See for a discussion
23 | on why a less-clean interface is preferable to a breaking change.
24 |
25 | [1]: ../flutter_custom_tabs
26 | [2]: lib/flutter_custom_tabs_platform_interface.dart
27 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/lib/flutter_custom_tabs_platform_interface.dart:
--------------------------------------------------------------------------------
1 | export 'src/custom_tabs_platform.dart';
2 | export 'src/types.dart';
3 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/lib/src/custom_tabs_platform.dart:
--------------------------------------------------------------------------------
1 | import 'package:plugin_platform_interface/plugin_platform_interface.dart';
2 |
3 | import 'method_channel_custom_tabs.dart';
4 | import 'types.dart';
5 |
6 | /// The interface that implementations of flutter_custom_tabs must implement.
7 | ///
8 | /// Platform implementations should extend this class rather than implement it as `flutter_custom_tabs`
9 | /// does not consider newly added methods to be breaking changes. Extending this class
10 | /// (using `extends`) ensures that the subclass will get the default implementation, while
11 | /// platform implementations that `implements` this interface will be broken by newly added
12 | abstract class CustomTabsPlatform extends PlatformInterface {
13 | /// Constructs a CustomTabsPlatform.
14 | CustomTabsPlatform() : super(token: _token);
15 |
16 | static final Object _token = Object();
17 |
18 | static CustomTabsPlatform _instance = MethodChannelCustomTabs();
19 |
20 | /// The default instance of [CustomTabsPlatform] to use.
21 | ///
22 | /// Defaults to [MethodChannelCustomTabs].
23 | static CustomTabsPlatform get instance => _instance;
24 |
25 | /// Platform-specific plugins should set this with their own platform-specific
26 | /// class that extends [CustomTabsPlatform] when they register themselves.
27 | static set instance(CustomTabsPlatform instance) {
28 | PlatformInterface.verifyToken(instance, _token);
29 | _instance = instance;
30 | }
31 |
32 | /// Passes [url] with options to the underlying platform for launching a Custom Tab.
33 | Future launch(
34 | String urlString, {
35 | bool prefersDeepLink = false,
36 | PlatformOptions? customTabsOptions,
37 | PlatformOptions? safariVCOptions,
38 | }) {
39 | throw UnimplementedError('launch() has not been implemented.');
40 | }
41 |
42 | /// Closes all Custom Tabs that were opened earlier by [launch].
43 | Future closeAllIfPossible() {
44 | throw UnimplementedError('closeAllIfPossible() has not been implemented.');
45 | }
46 |
47 | /// On Android, Warm up the browser process.
48 | ///
49 | /// Allows the browser application to pre-initialize itself in the background.
50 | /// Significantly speeds up URL opening in the browser.
51 | Future warmup([PlatformOptions? options]) {
52 | throw UnimplementedError('warmup() has not been implemented.');
53 | }
54 |
55 | /// Tells the browser of potential URLs that might be launched later,
56 | /// improving performance when the URL is actually launched.
57 | Future mayLaunch(
58 | List urls, {
59 | PlatformSession? session,
60 | }) {
61 | throw UnimplementedError('mayLaunchUrl() has not been implemented.');
62 | }
63 |
64 | /// Invalidates a session to release resources and properly dispose of it.
65 | Future invalidate(PlatformSession session) {
66 | throw UnimplementedError('invalidate() has not been implemented.');
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/lib/src/method_channel_custom_tabs.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:meta/meta.dart';
3 |
4 | import '../flutter_custom_tabs_platform_interface.dart';
5 |
6 | /// An implementation of [CustomTabsPlatform] that uses method channels.
7 | @internal
8 | class MethodChannelCustomTabs extends CustomTabsPlatform {
9 | static const MethodChannel _channel =
10 | MethodChannel('plugins.flutter.droibit.github.io/custom_tabs');
11 |
12 | @override
13 | Future launch(
14 | String urlString, {
15 | bool prefersDeepLink = false,
16 | PlatformOptions? customTabsOptions,
17 | PlatformOptions? safariVCOptions,
18 | }) {
19 | final args = {
20 | 'url': urlString,
21 | 'prefersDeepLink': prefersDeepLink,
22 | 'customTabsOptions': {},
23 | 'safariVCOptions': {},
24 | };
25 | return _channel.invokeMethod('launch', args);
26 | }
27 |
28 | @override
29 | Future closeAllIfPossible() {
30 | return _channel.invokeMethod('closeAllIfPossible');
31 | }
32 |
33 | @override
34 | Future warmup([PlatformOptions? options]) {
35 | return _channel.invokeMethod('warmup', {});
36 | }
37 |
38 | @override
39 | Future mayLaunch(
40 | List urls, {
41 | PlatformSession? session,
42 | }) {
43 | return _channel.invokeMethod('mayLaunch', {
44 | 'urls': urls,
45 | 'session': null,
46 | });
47 | }
48 |
49 | @override
50 | Future invalidate(PlatformSession session) {
51 | return _channel.invokeMethod('invalidate', {});
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/lib/src/types.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | /// The base options for Custom Tabs implementation.
4 | ///
5 | /// Platform specific implementations can add additional fields by extending
6 | /// this class.
7 | ///
8 | @immutable
9 | interface class PlatformOptions {}
10 |
11 | /// The base session for Custom Tabs implementation.
12 | ///
13 | /// Platform specific implementations can add additional fields by extending
14 | /// this class.
15 | ///
16 | @immutable
17 | interface class PlatformSession {}
18 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs_platform_interface
2 | description: A common platform interface for the flutter_custom_tabs plugin.
3 | version: 2.3.0
4 | homepage: https://github.com/droibit/flutter_custom_tabs
5 | repository: https://github.com/droibit/flutter_custom_tabs/tree/main/flutter_custom_tabs_platform_interface
6 | issue_tracker: https://github.com/droibit/flutter_custom_tabs/issues
7 |
8 | environment:
9 | sdk: ^3.3.0
10 | flutter: ">=3.19.0"
11 |
12 | dependencies:
13 | flutter:
14 | sdk: flutter
15 | meta: ^1.10.0
16 | plugin_platform_interface: ^2.1.8
17 |
18 | dev_dependencies:
19 | flutter_test:
20 | sdk: flutter
21 | mockito: ^5.4.2
22 | flutter_lints: ^4.0.0
--------------------------------------------------------------------------------
/flutter_custom_tabs_platform_interface/test/custom_tabs_platform_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
2 | import 'package:flutter_custom_tabs_platform_interface/src/method_channel_custom_tabs.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 | import 'package:mockito/mockito.dart';
5 | import 'package:plugin_platform_interface/plugin_platform_interface.dart';
6 |
7 | void main() {
8 | TestWidgetsFlutterBinding.ensureInitialized();
9 |
10 | test('$CustomTabsPlatform() is the default instance', () {
11 | expect(CustomTabsPlatform.instance, isA());
12 | });
13 |
14 | test('Cannot be implemented with `implements`', () {
15 | expect(() {
16 | CustomTabsPlatform.instance = _ImplementsCustomTabsPlatform();
17 | }, throwsA(isA()));
18 | });
19 |
20 | test('Can be mocked with `implements`', () {
21 | final mock = _CustomTabsPlatformMock();
22 | CustomTabsPlatform.instance = mock;
23 | });
24 |
25 | test('Can be extended', () {
26 | CustomTabsPlatform.instance = _CustomTabsPlatformMock();
27 | });
28 | }
29 |
30 | class _CustomTabsPlatformMock extends Mock
31 | with MockPlatformInterfaceMixin
32 | implements CustomTabsPlatform {}
33 |
34 | class _ImplementsCustomTabsPlatform extends Mock
35 | implements CustomTabsPlatform {}
36 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/.gitignore:
--------------------------------------------------------------------------------
1 | # Intellij project files
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .idea/*
6 | !.idea/codeStyleSettings.xml
7 | !.idea/runConfigurations/*
8 |
9 | # Files and directories created by pub
10 | .dart_tool/
11 | .packages
12 | .pub/
13 | build/
14 | # Remove the following pattern if you wish to check in your lock file
15 | pubspec.lock
16 |
17 | # Directory created by dartdoc
18 | doc/api/
19 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
8 | channel: stable
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.3.0
2 |
3 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.3.0.
4 | - Updates minimum supported SDK version to Flutter 3.16.0/Dart 3.2.
5 |
6 | ## 2.2.0
7 |
8 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.2.0.
9 |
10 | ## 2.2.0-dev.1
11 |
12 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.2.0-dev.1.
13 | - Updates minimum required `url_launcher_web` version to 2.2.3.
14 | - Updates minimum supported SDK version to Flutter 3.16.0/Dart 3.2.
15 |
16 | ## 2.1.0
17 |
18 | - Updates minimum supported SDK version to Flutter 3.10/Dart 3.
19 | - Updates minimum required `flutter_custom_tabs_platform_interface` version to 2.1.0.
20 | - Updates minimum required `url_launcher_web` version to 2.0.19.
21 |
22 | ## 2.0.0
23 |
24 | - Fixes the LICENSE file.
25 |
26 | ## 2.0.0-beta.1
27 |
28 | - Updates `flutter_custom_tabs_platform_interface` to v2.0.0-beta.2.
29 |
30 | ## 2.0.0-beta+1
31 |
32 | - Tweaks the description of "Import the package" in the README.
33 |
34 | ## 2.0.0-beta
35 |
36 | - Renames the `CustomTabsPlugin` class to `CustomTabsPluginWeb` to avoid conflicts with other platform packages.
37 | - Updates the package `flutter_custom_tabs_platform_interface` to v2.0.0-beta.
38 | - Updates the package `url_launcher_web` to v2.0.16.
39 | - Updates the minimum supported SDK version to Flutter 3.0.0/Dart 2.17.
40 |
41 | ## 1.1.0
42 |
43 | - Update `flutter_custom_tabs_platform_interface` to v1.1.0.
44 |
45 | ## 1.0.0
46 |
47 | - Initial release of the `flutter_custom_tabs` web implementation.
48 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/README.md:
--------------------------------------------------------------------------------
1 | # flutter_custom_tabs_web
2 |
3 | Web platform implementation of [flutter_custom_tabs][1].
4 |
5 | ## Usage
6 |
7 | ### Import the package
8 |
9 | This package has been the endorsed implementation of `flutter_custom_tabs` for the web platform since version `1.0.0`. This means it will be automatically included in your app when you add it, so you do not need to include it in your `pubspec.yaml`.
10 |
11 | [1]: https://pub.dev/packages/flutter_custom_tabs
12 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 | .flutter-plugins-dependencies
34 | # Remove the following pattern if you wish to check in your lock file
35 | pubspec.lock
36 |
37 | # Web related
38 | lib/generated_plugin_registrant.dart
39 |
40 | # Symbolication related
41 | app.*.symbols
42 |
43 | # Obfuscation related
44 | app.*.map.json
45 |
46 | # Android Studio will place build artifacts here
47 | /android/app/debug
48 | /android/app/profile
49 | /android/app/release
50 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/README.md:
--------------------------------------------------------------------------------
1 | # Integration tests
2 |
3 | This package utilizes the `integration_test` package to run its tests in a web browser.
4 |
5 | See [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info.
6 |
7 | ## Running the tests
8 |
9 | Make sure you have updated to the latest Flutter master.
10 |
11 | 1. Check what version of Chrome is running on the machine you're running tests on.
12 | 1. Download and install driver for that version from here:
13 | -
14 | 1. Start the driver using `chromedriver --port=4444`
15 | 1. Run tests: `flutter drive -d web-server --browser-name=chrome --driver=test_driver/integration_test_driver.dart --target=integration_test/TEST_NAME.dart`, or (in Linux):
16 | - Single: `./run_test.sh integration_test/TEST_NAME.dart`
17 | - All: `./run_test.sh`
18 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | exclude:
5 | # Ignore generated files
6 | - '**/*.g.dart'
7 | - '**/*.mocks.dart' # Mockito @GenerateMocks
8 |
9 | linter:
10 | rules:
11 | depend_on_referenced_packages: false
12 | avoid_web_libraries_in_flutter: false
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/integration_test/flutter_custom_tabs_web_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
2 | import 'package:flutter_custom_tabs_web/flutter_custom_tabs_web.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 | import 'package:integration_test/integration_test.dart';
5 | import 'package:mockito/mockito.dart';
6 | import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
7 |
8 | import 'mock_url_launcher_plugin.dart';
9 |
10 | void main() {
11 | IntegrationTestWidgetsFlutterBinding.ensureInitialized();
12 |
13 | late MockUrlLauncherPlugin mock;
14 | late CustomTabsPluginWeb plugin;
15 | setUp(() {
16 | mock = MockUrlLauncherPlugin();
17 | UrlLauncherPlatform.instance = mock;
18 |
19 | plugin = CustomTabsPluginWeb();
20 | });
21 |
22 | testWidgets('launch() delegate to "url_launcher_web"',
23 | (WidgetTester _) async {
24 | when(mock.launch(
25 | any,
26 | useSafariVC: anyNamed('useSafariVC'),
27 | useWebView: anyNamed('useWebView'),
28 | enableJavaScript: anyNamed('enableJavaScript'),
29 | enableDomStorage: anyNamed('enableDomStorage'),
30 | universalLinksOnly: anyNamed('universalLinksOnly'),
31 | headers: anyNamed('headers'),
32 | webOnlyWindowName: anyNamed('webOnlyWindowName'),
33 | )).thenAnswer((_) async => true);
34 |
35 | const url = 'https://example.com';
36 | await plugin.launch(
37 | url,
38 | customTabsOptions: const _Options(),
39 | safariVCOptions: const _Options(),
40 | );
41 | verify(mock.launch(url));
42 | });
43 | }
44 |
45 | class _Options implements PlatformOptions {
46 | const _Options();
47 | }
48 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/integration_test/mock_url_launcher_plugin.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:mockito/mockito.dart';
4 | import 'package:url_launcher_web/url_launcher_web.dart';
5 | import 'package:url_launcher_platform_interface/link.dart';
6 | import 'package:plugin_platform_interface/plugin_platform_interface.dart';
7 |
8 | class MockUrlLauncherPlugin extends Mock
9 | with MockPlatformInterfaceMixin
10 | implements UrlLauncherPlugin {
11 | @override
12 | Future canLaunch(String url) {
13 | throw UnimplementedError();
14 | }
15 |
16 | @override
17 | Future closeWebView() {
18 | throw UnimplementedError();
19 | }
20 |
21 | @override
22 | Future launch(
23 | String? url, {
24 | bool? useSafariVC = false,
25 | bool? useWebView = false,
26 | bool? enableJavaScript = false,
27 | bool? enableDomStorage = false,
28 | bool? universalLinksOnly = false,
29 | Map? headers = const {},
30 | String? webOnlyWindowName,
31 | }) async {
32 | return super.noSuchMethod(
33 | Invocation.method(#launch, [
34 | url
35 | ], {
36 | #useSafariVC: useSafariVC,
37 | #useWebView: useWebView,
38 | #enableJavaScript: enableJavaScript,
39 | #enableDomStorage: enableDomStorage,
40 | #universalLinksOnly: universalLinksOnly,
41 | #headers: headers,
42 | #webOnlyWindowName: webOnlyWindowName
43 | }),
44 | returnValue: Future.value(false),
45 | );
46 | }
47 |
48 | @override
49 | LinkDelegate get linkDelegate => throw UnimplementedError();
50 |
51 | @override
52 | bool openNewWindow(String url, {String? webOnlyWindowName}) {
53 | throw UnimplementedError();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | void main() => runApp(const TestApp());
4 |
5 | class TestApp extends StatelessWidget {
6 | const TestApp({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return const Directionality(
11 | textDirection: TextDirection.ltr,
12 | child: Text('Now testing...'),
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: regular_integration_tests
2 | publish_to: none
3 |
4 | environment:
5 | sdk: ^3.3.0
6 | flutter: ">=3.19.0"
7 |
8 | dependencies:
9 | flutter:
10 | sdk: flutter
11 | flutter_custom_tabs_web:
12 | path: ../
13 |
14 | dev_dependencies:
15 | flutter_driver:
16 | sdk: flutter
17 | flutter_test:
18 | sdk: flutter
19 | integration_test:
20 | sdk: flutter
21 | mockito: ^5.4.4
22 | plugin_platform_interface: ^2.1.8
23 | flutter_lints: ^4.0.0
24 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/run_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if pgrep -lf chromedriver > /dev/null; then
4 | echo "chromedriver is running."
5 |
6 | if [ $# -eq 0 ]; then
7 | echo "No target specified, running all tests..."
8 | find ./integration_test -iname *_test.dart | xargs -n1 -I '{}' -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}'
9 | else
10 | echo "Running test target: $1..."
11 | set -x
12 | flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1
13 | fi
14 |
15 | else
16 | echo "chromedriver is not running."
17 | echo "Please, check the README.md for instructions on how to use run_test.sh"
18 | fi
19 |
20 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/test_driver/integration_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:integration_test/integration_test_driver.dart';
2 |
3 | Future main() => integrationDriver();
4 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_web/example/web/favicon.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_web/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droibit/flutter_custom_tabs/8a1efc4b776e91f14250371d09bcf508b5f58297/flutter_custom_tabs_web/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "Demonstrates how to use the example plugin.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/lib/flutter_custom_tabs_web.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart';
4 | import 'package:flutter_web_plugins/flutter_web_plugins.dart';
5 | import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
6 | import 'package:url_launcher_web/url_launcher_web.dart';
7 |
8 | /// The web implementation of [CustomTabsPlatform].
9 | ///
10 | /// This class implements the `package:flutter_custom_tabs` functionality for the web.
11 | class CustomTabsPluginWeb extends CustomTabsPlatform {
12 | /// Registers this class as the default instance of [CustomTabsPlatform].
13 | static void registerWith(Registrar registrar) {
14 | CustomTabsPlatform.instance = CustomTabsPluginWeb();
15 | }
16 |
17 | @override
18 | Future launch(
19 | String urlString, {
20 | bool prefersDeepLink = false,
21 | PlatformOptions? customTabsOptions,
22 | PlatformOptions? safariVCOptions,
23 | }) {
24 | final plugin = UrlLauncherPlatform.instance as UrlLauncherPlugin;
25 | return plugin.launch(urlString).then((_) => null);
26 | }
27 |
28 | @override
29 | Future closeAllIfPossible() async {
30 | // No-op on web.
31 | }
32 |
33 | @override
34 | Future warmup([PlatformOptions? options]) async {
35 | // No-op on web.
36 | return null;
37 | }
38 |
39 | @override
40 | Future mayLaunch(
41 | List urls, {
42 | PlatformSession? session,
43 | }) async {
44 | // No-op on web.
45 | return null;
46 | }
47 |
48 | @override
49 | Future invalidate(PlatformSession session) async {
50 | // No-op on web.
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/flutter_custom_tabs_web/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_custom_tabs_web
2 | description: Web platform implementation of flutter_custom_tabs.
3 | version: 2.3.0
4 | homepage: https://github.com/droibit/flutter_custom_tabs
5 | repository: https://github.com/droibit/flutter_custom_tabs/tree/main/flutter_custom_tabs_web
6 | issue_tracker: https://github.com/droibit/flutter_custom_tabs/issues
7 |
8 | environment:
9 | sdk: ^3.3.0
10 | flutter: ">=3.19.0"
11 |
12 | flutter:
13 | plugin:
14 | implements: flutter_custom_tabs
15 | platforms:
16 | web:
17 | pluginClass: CustomTabsPluginWeb
18 | fileName: flutter_custom_tabs_web.dart
19 |
20 | dependencies:
21 | flutter:
22 | sdk: flutter
23 | flutter_web_plugins:
24 | sdk: flutter
25 | flutter_custom_tabs_platform_interface: ^2.3.0
26 | meta: ^1.10.0
27 | url_launcher_web: ^2.3.3
28 | url_launcher_platform_interface: ^2.2.0
29 |
30 | dev_dependencies:
31 | flutter_test:
32 | sdk: flutter
33 | flutter_lints: ^4.0.0
34 |
--------------------------------------------------------------------------------