From 2d8f16d4d7a7d79bec3011590c7d2770c5a1ff07 Mon Sep 17 00:00:00 2001 From: PSPDFKit Date: Fri, 12 Jan 2024 13:36:24 +0300 Subject: [PATCH] Release 3.7.2 --- CHANGELOG.md | 11 +- .../flutter/pspdfkit/EventDispatcher.java | 4 +- .../pspdfkit/FlutterPdfUiFragmentCallbacks.kt | 18 ++ .../pspdfkit/flutter/pspdfkit/PSPDFKitView.kt | 1 + .../android/FlutterAppCompatActivity.java | 263 ++++++++++++++---- example/ios/Podfile | 6 + example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/lib/main.dart | 59 ++-- example/pubspec.yaml | 2 +- ios/Classes/PspdfPlatformView.m | 19 +- ios/pspdfkit_flutter.podspec | 4 +- lib/pspdfkit.dart | 14 +- lib/src/main.dart | 8 + pubspec.yaml | 2 +- 15 files changed, 318 insertions(+), 97 deletions(-) create mode 100644 android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 585a78f3..a55cdf5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,17 @@ ## Newest Release -### 3.7.1 - 18 Oct 2023 +### 3.7.2 - 12 Jan 2024 -- Fixed issue where iOS Appstore upload fails due to PSPDFKit Flutter missing "CFBundleShortVersionString" key. (#42166) -- Fixed issue where Plugin returned "Document is missing or invalid" during pdfViewControllerWillDismiss events. (#42255) +- Adds `flutterPdfFragmentAdded` callback for Android. (#42631) +- Updates FlutterAppCompatActivity to Support Flutter 3.16.0. (#42767) ## Previous Releases +### 3.7.1 - 18 Oct 2023 + +- Fixes issue where iOS Appstore upload fails due to PSPDFKit Flutter missing "CFBundleShortVersionString" key. (#42166) +- Fixes issue where Plugin returned "Document is missing or invalid" during pdfViewControllerWillDismiss events. (#42255) + ### 3.7.0 - 07 Sep 2023 - Adds annotation preset customization. (#41669) diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/EventDispatcher.java b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/EventDispatcher.java index e35b50c6..c1006bb3 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/EventDispatcher.java +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/EventDispatcher.java @@ -52,7 +52,9 @@ public void notifyActivityOnPause() { sendEvent("flutterPdfActivityOnPause"); } - + public void notifyPdfFragmentAdded() { + sendEvent("flutterPdfFragmentAdded"); + } public void notifyInstantSyncStarted(String documentId) { sendEvent("pspdfkitInstantSyncStarted", documentId); diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt new file mode 100644 index 00000000..22b8406c --- /dev/null +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/FlutterPdfUiFragmentCallbacks.kt @@ -0,0 +1,18 @@ +package com.pspdfkit.flutter.pspdfkit + +import android.content.Context +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager + +class FlutterPdfUiFragmentCallbacks: FragmentManager.FragmentLifecycleCallbacks() { + + override fun onFragmentAttached( + fm: FragmentManager, + f: Fragment, + context: Context + ) { + if (f.tag?.contains("PSPDFKit.Fragment") == true) { + EventDispatcher.getInstance().notifyPdfFragmentAdded() + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt index b978b1d1..d53b3be6 100644 --- a/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt +++ b/android/src/main/java/com/pspdfkit/flutter/pspdfkit/PSPDFKitView.kt @@ -75,6 +75,7 @@ internal class PSPDFKitView( .build() } } + getFragmentActivity(context).supportFragmentManager.registerFragmentLifecycleCallbacks(FlutterPdfUiFragmentCallbacks(), true) fragmentContainerView?.let { it.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { diff --git a/android/src/main/java/io/flutter/embedding/android/FlutterAppCompatActivity.java b/android/src/main/java/io/flutter/embedding/android/FlutterAppCompatActivity.java index 4cb1dd88..e767ab85 100644 --- a/android/src/main/java/io/flutter/embedding/android/FlutterAppCompatActivity.java +++ b/android/src/main/java/io/flutter/embedding/android/FlutterAppCompatActivity.java @@ -5,27 +5,28 @@ package io.flutter.embedding.android; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_URI_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; -import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.view.View; @@ -33,40 +34,41 @@ import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.res.ResourcesCompat; import androidx.fragment.app.FragmentManager; - import io.flutter.Log; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; +import io.flutter.util.ViewUtils; +import java.util.ArrayList; +import java.util.List; /** * A Flutter {@code Activity} that is based upon {@link AppCompatActivity}. * - *

{@code FlutterAppCompatActivity} exists because there are some Android APIs in the ecosystem + *

{@code FlutterFragmentActivity} exists because there are some Android APIs in the ecosystem * that only accept a {@link AppCompatActivity}. If a {@link AppCompatActivity} is not required, you - * should consider using a regular {@link FlutterFragmentActivity} instead, because {@link FlutterFragmentActivity} - * is considered to be the standard, canonical implementation of a Flutter {@code FragmentActivity}. + * should consider using a regular {@link FlutterActivity} instead, because {@link FlutterActivity} + * is considered to be the standard, canonical implementation of a Flutter {@code Activity}. */ // A number of methods in this class have the same implementation as FlutterActivity. These methods // are duplicated for readability purposes. Be sure to replicate any change in this class in // FlutterActivity, too. public class FlutterAppCompatActivity extends AppCompatActivity - implements SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator { - private static final String TAG = "FlutterAppCompat"; + implements FlutterEngineProvider, FlutterEngineConfigurator { + private static final String TAG = "FlutterAppCompatActivity"; // FlutterFragment management. private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment"; // TODO(mattcarroll): replace ID with R.id when build system supports R.java - private static final int FRAGMENT_CONTAINER_ID = 609893468; // random number + public static final int FRAGMENT_CONTAINER_ID = + ViewUtils.generateViewId(609893468); // random number /** * Creates an {@link Intent} that launches a {@code FlutterFragmentActivity}, which executes a @@ -96,6 +98,7 @@ public static class NewEngineIntentBuilder { private final Class activityClass; private String initialRoute = DEFAULT_INITIAL_ROUTE; private String backgroundMode = DEFAULT_BACKGROUND_MODE; + @Nullable private List dartEntrypointArgs; /** * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of @@ -144,16 +147,36 @@ public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundM return this; } + /** + * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint + * function. + * + *

A value of null means do not pass any arguments to Dart's entrypoint function. + * + * @param dartEntrypointArgs The Dart entrypoint arguments. + * @return The engine intent builder. + */ + @NonNull + public NewEngineIntentBuilder dartEntrypointArgs(@Nullable List dartEntrypointArgs) { + this.dartEntrypointArgs = dartEntrypointArgs; + return this; + } + /** * Creates and returns an {@link Intent} that will launch a {@code FlutterFragmentActivity} with * the desired configuration. */ @NonNull public Intent build(@NonNull Context context) { - return new Intent(context, activityClass) - .putExtra(EXTRA_INITIAL_ROUTE, initialRoute) - .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode) - .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true); + Intent intent = + new Intent(context, activityClass) + .putExtra(EXTRA_INITIAL_ROUTE, initialRoute) + .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode) + .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true); + if (dartEntrypointArgs != null) { + intent.putExtra(EXTRA_DART_ENTRYPOINT_ARGS, new ArrayList(dartEntrypointArgs)); + } + return intent; } } @@ -241,6 +264,112 @@ public Intent build(@NonNull Context context) { } } + /** + * Creates a {@link NewEngineInGroupIntentBuilder}, which can be used to configure an {@link + * Intent} to launch a {@code FlutterFragmentActivity} that internally uses an existing {@link + * io.flutter.embedding.engine.FlutterEngineGroup} that is cached in {@link + * io.flutter.embedding.engine.FlutterEngineGroupCache}. + * + * @param engineGroupId A cached engine group ID. + * @return The builder. + */ + public static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId) { + return new NewEngineInGroupIntentBuilder(FlutterFragmentActivity.class, engineGroupId); + } + + /** + * Builder to create an {@code Intent} that launches a {@code FlutterFragmentActivity} with a new + * {@link FlutterEngine} by FlutterEngineGroup#createAndRunEngine. + */ + public static class NewEngineInGroupIntentBuilder { + private final Class activityClass; + private final String cachedEngineGroupId; + private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT; + private String initialRoute = DEFAULT_INITIAL_ROUTE; + private String backgroundMode = DEFAULT_BACKGROUND_MODE; + + /** + * Constructor that allows this {@code NewEngineInGroupIntentBuilder} to be used by subclasses + * of {@code FlutterActivity}. + * + *

Subclasses of {@code FlutterFragmentActivity} should provide their own static version of + * {@link #withNewEngineInGroup}, which returns an instance of {@code + * NewEngineInGroupIntentBuilder} constructed with a {@code Class} reference to the {@code + * FlutterFragmentActivity} subclass, e.g.: + * + *

{@code return new NewEngineInGroupIntentBuilder(FlutterFragmentActivity.class, + * cacheedEngineGroupId); } + * + * @param activityClass A subclass of {@code FlutterFragmentActivity}. + * @param engineGroupId The engine group id. + */ + public NewEngineInGroupIntentBuilder( + @NonNull Class activityClass, + @NonNull String engineGroupId) { + this.activityClass = activityClass; + this.cachedEngineGroupId = engineGroupId; + } + + /** + * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded, default to + * "main". + * + * @param dartEntrypoint The dart entrypoint's name + * @return The engine group intent builder + */ + @NonNull + public NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) { + this.dartEntrypoint = dartEntrypoint; + return this; + } + + /** + * The initial route that a Flutter app will render in this {@code FlutterFragmentActivity}, + * defaults to "/". + */ + @NonNull + public NewEngineInGroupIntentBuilder initialRoute(@NonNull String initialRoute) { + this.initialRoute = initialRoute; + return this; + } + + /** + * The mode of {@code FlutterFragmentActivity}'s background, either {@link + * BackgroundMode#opaque} or {@link BackgroundMode#transparent}. + * + *

The default background mode is {@link BackgroundMode#opaque}. + * + *

Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner + * {@link FlutterView} of this {@code FlutterFragmentActivity} to be configured with a {@link + * FlutterTextureView} to support transparency. This choice has a non-trivial performance + * impact. A transparent background should only be used if it is necessary for the app design + * being implemented. + * + *

A {@code FlutterFragmentActivity} that is configured with a background mode of {@link + * BackgroundMode#transparent} must have a theme applied to it that includes the following + * property: {@code true}. + */ + @NonNull + public NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) { + this.backgroundMode = backgroundMode.name(); + return this; + } + + /** + * Creates and returns an {@link Intent} that will launch a {@code FlutterFragmentActivity} with + * the desired configuration. + */ + @NonNull + public Intent build(@NonNull Context context) { + return new Intent(context, activityClass) + .putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint) + .putExtra(EXTRA_INITIAL_ROUTE, initialRoute) + .putExtra(EXTRA_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId) + .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode) + .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true); + } + } + @Nullable private FlutterFragment flutterFragment; @Override @@ -306,41 +435,6 @@ private void switchLaunchThemeForNormalTheme() { } } - @Nullable - @Override - public SplashScreen provideSplashScreen() { - Drawable manifestSplashDrawable = getSplashScreenFromManifest(); - if (manifestSplashDrawable != null) { - return new DrawableSplashScreen(manifestSplashDrawable); - } else { - return null; - } - } - - /** - * Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the - * {@code AndroidManifest.xml} file, or null if no such splash screen is requested. - * - *

See {@link FlutterActivityLaunchConfigs#SPLASH_SCREEN_META_DATA_KEY} for the meta-data key - * to be used in a manifest file. - */ - @Nullable - private Drawable getSplashScreenFromManifest() { - try { - Bundle metaData = getMetaData(); - int splashScreenId = metaData != null ? metaData.getInt(SPLASH_SCREEN_META_DATA_KEY) : 0; - return splashScreenId != 0 - ? ResourcesCompat.getDrawable(getResources(), splashScreenId, getTheme()) - : null; - } catch (Resources.NotFoundException e) { - Log.e(TAG, "Splash screen not found. Ensure the drawable exists and that it's valid."); - throw e; - } catch (PackageManager.NameNotFoundException e) { - // This is never expected to happen. - return null; - } - } - /** * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link @@ -456,12 +550,18 @@ protected FlutterFragment createFlutterFragment() { Log.v( TAG, "Creating FlutterFragment with new engine:\n" + + "Cached engine group ID: " + + getCachedEngineGroupId() + + "\n" + "Background transparency mode: " + backgroundMode + "\n" + "Dart entrypoint: " + getDartEntrypointFunctionName() + "\n" + + "Dart entrypoint library uri: " + + (getDartEntrypointLibraryUri() != null ? getDartEntrypointLibraryUri() : "\"\"") + + "\n" + "Initial route: " + getInitialRoute() + "\n" @@ -471,8 +571,23 @@ protected FlutterFragment createFlutterFragment() { + "Will attach FlutterEngine to Activity: " + shouldAttachEngineToActivity()); + if (getCachedEngineGroupId() != null) { + return flutterFragment + .withNewEngineInGroup(getCachedEngineGroupId()) + .dartEntrypoint(getDartEntrypointFunctionName()) + .initialRoute(getInitialRoute()) + .handleDeeplinking(shouldHandleDeeplinking()) + .renderMode(renderMode) + .transparencyMode(transparencyMode) + .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) + .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw) + .build(); + } + return FlutterFragment.withNewEngine() .dartEntrypoint(getDartEntrypointFunctionName()) + .dartLibraryUri(getDartEntrypointLibraryUri()) + .dartEntrypointArgs(getDartEntrypointArgs()) .initialRoute(getInitialRoute()) .appBundlePath(getAppBundlePath()) .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent())) @@ -508,6 +623,7 @@ protected void onNewIntent(@NonNull Intent intent) { } @Override + @SuppressWarnings("MissingSuperCall") public void onBackPressed() { flutterFragment.onBackPressed(); } @@ -695,6 +811,44 @@ public String getDartEntrypointFunctionName() { } } + /** + * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint function. + * + *

A value of null means do not pass any arguments to Dart's entrypoint function. + * + *

Subclasses may override this method to directly control the Dart entrypoint arguments. + */ + @Nullable + public List getDartEntrypointArgs() { + return (List) getIntent().getSerializableExtra(EXTRA_DART_ENTRYPOINT_ARGS); + } + + /** + * The Dart library URI for the entrypoint that will be executed as soon as the Dart snapshot is + * loaded. + * + *

Example value: "package:foo/bar.dart" + * + *

This preference can be controlled by setting a {@code } called {@link + * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_URI_META_DATA_KEY} within the Android manifest + * definition for this {@code FlutterFragmentActivity}. + * + *

A value of null means use the default root library. + * + *

Subclasses may override this method to directly control the Dart entrypoint uri. + */ + @Nullable + public String getDartEntrypointLibraryUri() { + try { + Bundle metaData = getMetaData(); + String desiredDartLibraryUri = + metaData != null ? metaData.getString(DART_ENTRYPOINT_URI_META_DATA_KEY) : null; + return desiredDartLibraryUri; + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + /** * The initial route that a Flutter app will render upon loading and executing its Dart code. * @@ -745,6 +899,11 @@ protected String getCachedEngineId() { return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID); } + @Nullable + protected String getCachedEngineGroupId() { + return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_GROUP_ID); + } + /** * The desired window background mode of this {@code Activity}, which defaults to {@link * BackgroundMode#opaque}. @@ -785,4 +944,4 @@ private boolean isDebuggable() { protected FrameLayout provideRootLayout(Context context) { return new FrameLayout(context); } -} +} \ No newline at end of file diff --git a/example/ios/Podfile b/example/ios/Podfile index 9f1ec1cf..873e4fbf 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -28,6 +28,7 @@ end require File.expand_path(File.join("packages", "flutter_tools", "bin", "podhelper"), flutter_root) flutter_ios_podfile_setup +host_cpu = RbConfig::CONFIG["host_cpu"] target "Runner" do flutter_install_all_ios_pods __dir__ @@ -39,5 +40,10 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |target_config| + if host_cpu.eql?("x86_64") + target_config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" + end + end end end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index f91605f8..a16fe05e 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -164,7 +164,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 50a8cfc9..9c585c83 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ with WidgetsBindingObserver { - static final ThemeData lightTheme = - ThemeData(primaryColor: Colors.black, dividerColor: Colors.grey[400]); - - static final ThemeData darkTheme = - ThemeData(primaryColor: Colors.white, dividerColor: Colors.grey[800]); String _frameworkVersion = ''; - ThemeData currentTheme = lightTheme; void showDocument() async { final extractedDocument = await extractAsset(context, _documentPath); @@ -576,18 +580,6 @@ class _HomePageState extends State with WidgetsBindingObserver { super.dispose(); } - @override - void didChangePlatformBrightness() { - currentTheme = - WidgetsBinding.instance.window.platformBrightness == Brightness.light - ? lightTheme - : darkTheme; - setState(() { - build(context); - }); - super.didChangePlatformBrightness(); - } - String frameworkVersion() { return '$_pspdfkitFor $_frameworkVersion\n'; } @@ -637,6 +629,14 @@ class _HomePageState extends State with WidgetsBindingObserver { print('pdfViewControllerDidDismissHandler'); } + void flutterPdfFragmentAdded() { + print('flutterPdfFragmentAdded'); + } + + void pspdfkitDocumentLoaded(String? documentId) { + print('pspdfkitDocumentLoaded: $documentId'); + } + @override Widget build(BuildContext context) { Pspdfkit.flutterPdfActivityOnPause = @@ -645,17 +645,16 @@ class _HomePageState extends State with WidgetsBindingObserver { () => pdfViewControllerWillDismissHandler(); Pspdfkit.pdfViewControllerDidDismiss = () => pdfViewControllerDidDismissHandler(); - - currentTheme = MediaQuery.of(context).platformBrightness == Brightness.light - ? lightTheme - : darkTheme; + Pspdfkit.flutterPdfFragmentAdded = () => flutterPdfFragmentAdded(); + Pspdfkit.pspdfkitDocumentLoaded = + (documentId) => pspdfkitDocumentLoaded(documentId); final listTiles = [ Container( color: Colors.grey[200], padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), child: Text(_pspdfkitWidgetExamples, - style: currentTheme.textTheme.headline4?.copyWith( + style: Theme.of(context).textTheme.headline4?.copyWith( fontSize: _fontSize, fontWeight: FontWeight.bold))), ListTile( title: const Text(_basicExample), @@ -738,7 +737,7 @@ class _HomePageState extends State with WidgetsBindingObserver { color: Colors.grey[200], padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), child: Text(_pspdfkitGlobalPluginExamples, - style: currentTheme.textTheme.headline4?.copyWith( + style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontSize: _fontSize, fontWeight: FontWeight.bold))), ListTile( title: const Text(_basicExampleGlobal), @@ -779,17 +778,15 @@ class _HomePageState extends State with WidgetsBindingObserver { ]; return Scaffold( appBar: AppBar(title: const Text(_pspdfkitFlutterPluginTitle)), - body: ExampleListView(currentTheme, frameworkVersion(), listTiles)); + body: ExampleListView(frameworkVersion(), listTiles)); } } class ExampleListView extends StatelessWidget { - final ThemeData themeData; final String frameworkVersion; final List widgets; - const ExampleListView(this.themeData, this.frameworkVersion, this.widgets, - {Key? key}) + const ExampleListView(this.frameworkVersion, this.widgets, {Key? key}) : super(key: key); @override @@ -800,10 +797,10 @@ class ExampleListView extends StatelessWidget { padding: const EdgeInsets.only(top: 24), child: Center( child: Text(frameworkVersion, - style: themeData.textTheme.headline4?.copyWith( + style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontSize: _fontSize, fontWeight: FontWeight.bold, - color: themeData.primaryColor)))), + color: Theme.of(context).primaryColor)))), Expanded( child: ListView.separated( itemCount: widgets.length, diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9ea87342..5051a60e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: pspdfkit_example description: Demonstrates how to use the pspdfkit plugin. -version: 3.7.1 +version: 3.7.2 homepage: https://pspdfkit.com/ environment: sdk: '>=2.17.0 <4.0.0' diff --git a/ios/Classes/PspdfPlatformView.m b/ios/Classes/PspdfPlatformView.m index 2297c5ee..3ed3bddf 100644 --- a/ios/Classes/PspdfPlatformView.m +++ b/ios/Classes/PspdfPlatformView.m @@ -17,6 +17,7 @@ @interface PspdfPlatformView() @property int64_t platformViewId; @property (nonatomic) FlutterMethodChannel *channel; +@property (nonatomic) FlutterMethodChannel *broadcastChannel; @property (nonatomic, weak) UIViewController *flutterViewController; @property (nonatomic) PSPDFViewController *pdfViewController; @property (nonatomic) PSPDFNavigationController *navigationController; @@ -29,10 +30,10 @@ - (nonnull UIView *)view { } - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args messenger:(NSObject *)messenger { + if ((self = [super init])) { - NSString *name = [NSString stringWithFormat:@"com.pspdfkit.widget.%lld", viewId]; - _platformViewId = viewId; - _channel = [FlutterMethodChannel methodChannelWithName:name binaryMessenger:messenger]; + _channel = [FlutterMethodChannel methodChannelWithName:[NSString stringWithFormat:@"com.pspdfkit.widget.%lld", viewId] binaryMessenger:messenger]; + _broadcastChannel = [FlutterMethodChannel methodChannelWithName:@"com.pspdfkit.global" binaryMessenger:messenger]; _navigationController = [PSPDFNavigationController new]; _navigationController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; @@ -64,6 +65,7 @@ - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId argum _pdfViewController.appearanceModeManager.appearanceMode = [PspdfkitFlutterConverter appearanceMode:configurationDictionary]; _pdfViewController.pageIndex = [PspdfkitFlutterConverter pageIndex:configurationDictionary]; _pdfViewController.delegate = self; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(documentDidFinishRendering) name:PSPDFDocumentViewControllerDidConfigureSpreadViewNotification object:nil]; if ((id)configurationDictionary != NSNull.null) { NSString *key = @"leftBarButtonItems"; @@ -100,6 +102,17 @@ - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId argum return self; } +- (void)documentDidFinishRendering { + // Remove observer after the initial notification + [NSNotificationCenter.defaultCenter removeObserver:self + name:PSPDFDocumentViewControllerDidConfigureSpreadViewNotification + object:nil]; + NSString *documentId = self.pdfViewController.document.UID; + if (documentId != nil) { + [_broadcastChannel invokeMethod:@"pspdfkitDocumentLoaded" arguments:documentId]; + } +} + - (void)dealloc { [self cleanup]; } diff --git a/ios/pspdfkit_flutter.podspec b/ios/pspdfkit_flutter.podspec index cbaeeb7f..4a9c732e 100644 --- a/ios/pspdfkit_flutter.podspec +++ b/ios/pspdfkit_flutter.podspec @@ -5,7 +5,7 @@ # Pod::Spec.new do |s| s.name = "pspdfkit_flutter" - s.version = "3.7.1" + s.version = "3.7.2" s.homepage = "https://PSPDFKit.com" s.documentation_url = "https://pspdfkit.com/guides/flutter" s.license = { type: "Commercial", file: "../LICENSE" } @@ -22,6 +22,6 @@ Pod::Spec.new do |s| s.dependency("Instant") s.swift_version = "5.0" s.platform = :ios, "15.0" - s.version = "3.7.1" + s.version = "3.7.2" s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES", "SWIFT_INSTALL_OBJC_HEADER" => "NO" } end diff --git a/lib/pspdfkit.dart b/lib/pspdfkit.dart index e434e2c7..5cd663c3 100644 --- a/lib/pspdfkit.dart +++ b/lib/pspdfkit.dart @@ -270,9 +270,12 @@ class Pspdfkit { return Directory(path); } - /// onPAuse callback for FlutterPdfActivity + /// onPause callback for FlutterPdfActivity static void Function()? flutterPdfActivityOnPause; + /// Added callback for FlutterPdfFragment + static void Function()? flutterPdfFragmentAdded; + /// ViewControllerWillDismiss callback for PDFViewController static void Function()? pdfViewControllerWillDismiss; @@ -305,12 +308,18 @@ class Pspdfkit { static void Function(String? documentId, String? error)? instantDownloadFailed; + /// Called with the document has been loaded + static void Function(String? documentId)? pspdfkitDocumentLoaded; + static Future _platformCallHandler(MethodCall call) { try { switch (call.method) { case 'flutterPdfActivityOnPause': flutterPdfActivityOnPause?.call(); break; + case 'flutterPdfFragmentAdded': + flutterPdfFragmentAdded?.call(); + break; case 'pdfViewControllerWillDismiss': pdfViewControllerWillDismiss?.call(); break; @@ -358,6 +367,9 @@ class Pspdfkit { arguments['error'] as String); break; } + case 'pspdfkitDocumentLoaded': + pspdfkitDocumentLoaded?.call(call.arguments as String); + break; default: if (kDebugMode) { print('Unknown method ${call.method} '); diff --git a/lib/src/main.dart b/lib/src/main.dart index a7a56f80..157b7500 100644 --- a/lib/src/main.dart +++ b/lib/src/main.dart @@ -190,6 +190,8 @@ class Pspdfkit { static late VoidCallback flutterPdfActivityOnPause; static late VoidCallback pdfViewControllerWillDismiss; static late VoidCallback pdfViewControllerDidDismiss; + static late VoidCallback flutterPdfFragmentAdded; + static late Function(String) pspdfkitDocumentLoaded; static Future _platformCallHandler(MethodCall call) { try { @@ -203,6 +205,12 @@ class Pspdfkit { case 'pdfViewControllerDidDismiss': pdfViewControllerDidDismiss(); break; + case 'flutterPdfFragmentAdded': + flutterPdfFragmentAdded(); + break; + case 'pspdfkitDocumentLoaded': + pspdfkitDocumentLoaded(call.arguments as String); + break; default: if (kDebugMode) { print('Unknown method ${call.method} '); diff --git a/pubspec.yaml b/pubspec.yaml index ed9044d3..646f2dc1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pspdfkit_flutter description: A Flutter plugin providing a feature-rich PDF viewing and editing experience to your users with the powerful PSPDFKit PDF SDK. -version: 3.7.1 +version: 3.7.2 homepage: https://pspdfkit.com/ repository: https://github.com/PSPDFKit/pspdfkit-flutter issue_tracker: https://support.pspdfkit.com/hc/en-us/requests/new