├── .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 | --------------------------------------------------------------------------------