Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new bottomSheet option to CustomTabsOptions #96

Merged
merged 2 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion flutter_custom_tabs/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,16 @@ android {
buildConfig false
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

dependencies {
// TODO: Delete Flutter's default compiles sdk version when it reaches 34.
api('androidx.browser:browser') {
version { strictly "1.5.0" }
}
api 'com.github.droibit:customtabslauncher:1.7.1'
api 'com.github.droibit:customtabslauncher:2.0.0-beta01'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.provider.Browser;
Expand All @@ -14,11 +13,15 @@

import com.droibit.android.customtabs.launcher.CustomTabsFallback;
import com.droibit.android.customtabs.launcher.CustomTabsLauncher;
import com.droibit.android.customtabs.launcher.CustomTabsPackageFallback;
import com.droibit.android.customtabs.launcher.NonChromeCustomTabs;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import static com.droibit.android.customtabs.launcher.CustomTabsIntentHelper.ensureCustomTabsPackage;

@SuppressWarnings({"ConstantConditions", "unchecked"})
@RestrictTo(RestrictTo.Scope.LIBRARY)
class CustomTabsFactory {
Expand All @@ -34,7 +37,11 @@ class CustomTabsFactory {
private static final String KEY_ANIMATION_START_EXIT = "startExit";
private static final String KEY_ANIMATION_END_ENTER = "endEnter";
private static final String KEY_ANIMATION_END_EXIT = "endExit";
private static final String KEY_EXTRA_CUSTOM_TABS = "extraCustomTabs";
private static final String KEY_OPTIONS_EXTRA_CUSTOM_TABS = "extraCustomTabs";
private static final String KEY_OPTIONS_BOTTOM_SHEET = "bottomSheet";
private static final String KEY_BOTTOM_SHEET_INITIAL_HEIGHT_DP = "initialHeightDp";
private static final String KEY_BOTTOM_SHEET_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR = "activityHeightResizeBehavior";
private static final String KEY_BOTTOM_SHEET_CORNER_RADIUS_DP = "cornerRadiusDp";

// Note: The full resource qualifier is "package:type/entry".
// https://developer.android.com/reference/android/content/res/Resources.html#getIdentifier(java.lang.String, java.lang.String, java.lang.String)
Expand All @@ -59,8 +66,8 @@ CustomTabsIntent createIntent(@NonNull Map<String, Object> options) {
}

if (options.containsKey(KEY_OPTIONS_SHARE_STATE)) {
final int shareState = ((int) options.get(KEY_OPTIONS_SHARE_STATE));
builder.setShareState(shareState);
final int shareState = ((int) options.get(KEY_OPTIONS_SHARE_STATE));
builder.setShareState(shareState);
}

if (options.containsKey(KEY_OPTIONS_SHOW_PAGE_TITLE)) {
Expand All @@ -80,24 +87,50 @@ CustomTabsIntent createIntent(@NonNull Map<String, Object> options) {
builder.setCloseButtonPosition(position);
}

if (options.containsKey(KEY_OPTIONS_BOTTOM_SHEET)) {
final Map<String, Object> bottomSheetConfig =
((Map<String, Object>) options.get(KEY_OPTIONS_BOTTOM_SHEET));
applyBottomSheetConfiguration(builder, bottomSheetConfig);
}

final CustomTabsIntent customTabsIntent = builder.build();
onPostBuild(customTabsIntent.intent, options);
onPostBuild(customTabsIntent, options);
return customTabsIntent;
}

private void onPostBuild(@NonNull Intent intent, @NonNull Map<String, Object> options) {
private void onPostBuild(
@NonNull CustomTabsIntent customTabsIntent,
@NonNull Map<String, Object> options
) {
if (options.containsKey(KEY_HEADERS)) {
Map<String, String> headers = (Map<String, String>) options.get(KEY_HEADERS);
Bundle bundleHeaders = new Bundle();
final Map<String, String> headers = (Map<String, String>) options.get(KEY_HEADERS);
final Bundle bundleHeaders = new Bundle();
for (Map.Entry<String, String> header : headers.entrySet()) {
bundleHeaders.putString(header.getKey(), header.getValue());
}
intent.putExtra(Browser.EXTRA_HEADERS, bundleHeaders);
customTabsIntent.intent.putExtra(Browser.EXTRA_HEADERS, bundleHeaders);
}

final List<String> extraCustomTabs;
if (options.containsKey(KEY_OPTIONS_EXTRA_CUSTOM_TABS)) {
extraCustomTabs = ((List<String>) options.get(KEY_OPTIONS_EXTRA_CUSTOM_TABS));
} else {
extraCustomTabs = null;
}

final CustomTabsPackageFallback fallback;
if (extraCustomTabs != null && !extraCustomTabs.isEmpty()) {
fallback = new NonChromeCustomTabs(extraCustomTabs);
} else {
fallback = new NonChromeCustomTabs(context);
}
ensureCustomTabsPackage(customTabsIntent, context, fallback);
}

private void applyAnimations(@NonNull CustomTabsIntent.Builder builder,
@NonNull Map<String, String> animations) {
private void applyAnimations(
@NonNull CustomTabsIntent.Builder builder,
@NonNull Map<String, String> animations
) {
final int startEnterAnimationId =
animations.containsKey(KEY_ANIMATION_START_ENTER) ? resolveAnimationIdentifierIfNeeded(
animations.get(KEY_ANIMATION_START_ENTER)) : 0;
Expand Down Expand Up @@ -130,11 +163,28 @@ private int resolveAnimationIdentifierIfNeeded(@NonNull String identifier) {
}
}

private void applyBottomSheetConfiguration(
@NonNull CustomTabsIntent.Builder builder,
@NonNull Map<String, Object> configuration
) {
final double initialHeightDp = (double) configuration.get(KEY_BOTTOM_SHEET_INITIAL_HEIGHT_DP);
final float scale = context.getResources().getDisplayMetrics().density;
final int resizeBehavior = (int) configuration.get(KEY_BOTTOM_SHEET_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR);
builder.setInitialActivityHeightPx(
(int) (initialHeightDp * scale + 0.5),
resizeBehavior
);
if (configuration.containsKey(KEY_BOTTOM_SHEET_CORNER_RADIUS_DP)) {
final int cornerRadius = ((int) configuration.get(KEY_BOTTOM_SHEET_CORNER_RADIUS_DP));
builder.setToolbarCornerRadiusDp(cornerRadius);
}
}

@NonNull
CustomTabsFallback createFallback(@NonNull Map<String, Object> options) {
final List<String> extraCustomTabs;
if (options.containsKey(KEY_EXTRA_CUSTOM_TABS)) {
extraCustomTabs = ((List<String>) options.get(KEY_EXTRA_CUSTOM_TABS));
if (options.containsKey(KEY_OPTIONS_EXTRA_CUSTOM_TABS)) {
extraCustomTabs = ((List<String>) options.get(KEY_OPTIONS_EXTRA_CUSTOM_TABS));
} else {
extraCustomTabs = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsService;
import androidx.core.content.ContextCompat;

import com.droibit.android.customtabs.launcher.CustomTabsFallback;
import com.droibit.android.customtabs.launcher.CustomTabsLauncher;

import java.util.Map;
import java.util.Objects;

Expand All @@ -29,12 +25,14 @@

import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX;
import static androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION;

public class CustomTabsPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler {
private static final String KEY_OPTIONS = "customTabsOptions";
private static final String KEY_URL = "url";
private static final String CODE_LAUNCH_ERROR = "LAUNCH_ERROR";
private static final int REQUEST_CODE_CUSTOM_TABS = 0;

@Nullable
private Activity activity;
Expand Down Expand Up @@ -100,8 +98,12 @@ private void launch(@NonNull Map<String, Object> args, @NonNull MethodChannel.Re
final Map<String, Object> options = (Map<String, Object>) args.get(KEY_OPTIONS);
final CustomTabsIntent customTabsIntent = factory.createIntent(options);
final Uri uri = Uri.parse(args.get(KEY_URL).toString());
final CustomTabsFallback fallback = factory.createFallback(options);
CustomTabsLauncher.launch(activity, customTabsIntent, uri, fallback);
if (customTabsIntent.intent.hasExtra(EXTRA_INITIAL_ACTIVITY_HEIGHT_PX)) {
customTabsIntent.intent.setData(uri);
activity.startActivityForResult(customTabsIntent.intent, REQUEST_CODE_CUSTOM_TABS);
} else {
customTabsIntent.launchUrl(activity, uri);
}
result.success(null);
} catch (ActivityNotFoundException e) {
result.error(CODE_LAUNCH_ERROR, e.getMessage(), null);
Expand Down
53 changes: 47 additions & 6 deletions flutter_custom_tabs/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';

// ignore:depend_on_referenced_packages
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';

Expand All @@ -25,12 +26,25 @@ class MyApp extends StatelessWidget {
title: const Text('Flutter Custom Tabs Example'),
),
body: Center(
child: TextButton(
onPressed: () => _launchURL(context),
child: const Text(
'Show Flutter homepage',
style: TextStyle(fontSize: 17),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton(
onPressed: () => _launchURL(context),
child: const Text(
'Show Flutter homepage',
style: TextStyle(fontSize: 17),
),
),
const SizedBox(height: 16),
TextButton(
onPressed: () => _launchURLInBottomSheet(context),
child: const Text(
'Show Flutter homepage(bottom Sheet)',
style: TextStyle(fontSize: 17),
),
),
],
),
),
),
Expand Down Expand Up @@ -69,4 +83,31 @@ class MyApp extends StatelessWidget {
debugPrint(e.toString());
}
}

Future<void> _launchURLInBottomSheet(BuildContext context) async {
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
try {
await launch(
'https://flutter.dev',
customTabsOptions: CustomTabsOptions.bottomSheet(
configuration: CustomTabsBottomSheetConfiguration(
initialHeight: mediaQuery.size.height * 0.7,
),
toolbarColor: theme.primaryColor,
showPageTitle: true,
),
safariVCOptions: SafariViewControllerOptions(
preferredBarTintColor: theme.primaryColor,
preferredControlTintColor: Colors.white,
barCollapsingEnabled: true,
entersReaderIfAvailable: false,
dismissButtonStyle: SafariViewControllerDismissButtonStyle.close,
),
);
} catch (e) {
// An exception is thrown if browser app is not installed on Android device.
debugPrint(e.toString());
}
}
}
2 changes: 2 additions & 0 deletions flutter_custom_tabs/lib/flutter_custom_tabs.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export 'package:flutter_custom_tabs_platform_interface/flutter_custom_tabs_platform_interface.dart'
show
CustomTabsActivityHeightResizeBehavior,
CustomTabsBottomSheetConfiguration,
CustomTabsOptions,
CustomTabsCloseButtonPosition,
CustomTabsShareState,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math';

import 'package:flutter/painting.dart';
import 'package:meta/meta.dart';

Expand All @@ -20,8 +22,27 @@ class CustomTabsOptions {
this.animation,
this.extraCustomTabs,
this.headers,
this.bottomSheetConfiguration,
});

const CustomTabsOptions.bottomSheet({
required CustomTabsBottomSheetConfiguration configuration,
Color? toolbarColor,
CustomTabsShareState? shareState,
bool? showPageTitle,
CustomTabsCloseButtonPosition? closeButtonPosition,
List<String>? extraCustomTabs,
Map<String, String>? headers,
}) : this(
toolbarColor: toolbarColor,
shareState: shareState,
showPageTitle: showPageTitle,
closeButtonPosition: closeButtonPosition,
extraCustomTabs: extraCustomTabs,
headers: headers,
bottomSheetConfiguration: configuration,
);

/// Custom tab toolbar color.
final Color? toolbarColor;

Expand All @@ -46,9 +67,12 @@ class CustomTabsOptions {
/// Package list of non-Chrome browsers supporting Custom Tabs. The top of the list is used with the highest priority.
final List<String>? extraCustomTabs;

/// Request Headers
/// Request Headers.
final Map<String, String>? headers;

/// The bottom sheet configuration.
final CustomTabsBottomSheetConfiguration? bottomSheetConfiguration;

@internal
Map<String, dynamic> toMap() {
final dest = <String, dynamic>{};
Expand Down Expand Up @@ -79,6 +103,9 @@ class CustomTabsOptions {
if (headers != null) {
dest['headers'] = headers;
}
if (bottomSheetConfiguration != null) {
dest['bottomSheet'] = bottomSheetConfiguration!.toMap();
}
return dest;
}
}
Expand Down Expand Up @@ -162,3 +189,56 @@ enum CustomTabsShareState {
@internal
final int rawValue;
}

/// The configuration to show custom tab as a bottom sheet.
@immutable
class CustomTabsBottomSheetConfiguration {
const CustomTabsBottomSheetConfiguration({
required this.initialHeight,
this.activityHeightResizeBehavior =
CustomTabsActivityHeightResizeBehavior.defaultBehavior,
this.cornerRadius,
});

/// The Custom Tab Activity's initial height.
///
/// *The minimum partial custom tab height is 50% of the screen height.
final double initialHeight;

/// The Custom Tab Activity's desired resize behavior.
final CustomTabsActivityHeightResizeBehavior activityHeightResizeBehavior;

/// The toolbar's top corner radius.
///
/// *The maximum corner radius is 16dp(lp).
final int? cornerRadius;

@internal
Map<String, dynamic> toMap() {
final dest = <String, dynamic>{
'initialHeightDp': initialHeight,
'activityHeightResizeBehavior': activityHeightResizeBehavior.rawValue,
};
if (cornerRadius != null) {
dest['cornerRadiusDp'] = min(cornerRadius!, 16);
}
return dest;
}
}

/// Desired height behavior for the custom tab.
enum CustomTabsActivityHeightResizeBehavior {
/// Applies the default height resize behavior for the Custom Tab Activity when it behaves as a bottom sheet.
defaultBehavior(0),

/// The Custom Tab Activity, when it behaves as a bottom sheet, can have its height manually resized by the user.
adjustable(1),

/// The Custom Tab Activity, when it behaves as a bottom sheet, cannot have its height manually resized by the user.
fixed(2);

const CustomTabsActivityHeightResizeBehavior(this.rawValue);

@internal
final int rawValue;
}
Loading