updateContentOfStoryboard function

Future<void> updateContentOfStoryboard({
  1. String? imagePath,
  2. String? color,
  3. String? iosContentMode,
  4. String? backgroundImage,
  5. String? iosBackgroundContentMode,
  6. String? darkColor,
  7. String? darkBackgroundImage,
})

Update the default storyboard content with the provided details Image, Color and contentMode

Implementation

Future<void> updateContentOfStoryboard({
  String? imagePath,
  String? color,
  String? iosContentMode,
  String? backgroundImage,
  String? iosBackgroundContentMode,
  String? darkColor,
  String? darkBackgroundImage,
}) async {
  final file = File(CmdStrings.storyboardPath);
  final xmlDocument = XmlDocument.parse(file.readAsStringSync());
  final documentData = xmlDocument.getElement(
    IOSStrings.documentElement,
  );

  /// Find the default view in the storyboard
  final view = documentData?.descendants
      .whereType<XmlElement>()
      .firstWhereOrNull((element) {
    return element.name.qualified == IOSStrings.viewElement &&
        element.getAttribute(IOSStrings.viewIdAttr) == IOSStrings.defaultViewId;
  });
  if (view == null) {
    throw SplashMasterException(
      message:
          'Default Flutter view with ${IOSStrings.defaultViewId} ID not found.',
    );
  }

  final resolvedColor = color ?? IOSStrings.defaultColor;

  // Dark mode requires an Asset Catalog color set (.colorset);
  // an inline storyboard color cannot express appearance variants.
  // resolvedColor serves as the light variant.
  if (darkColor != null) {
    await createLaunchBackgroundColorSet(
      lightColor: resolvedColor,
      darkColor: darkColor,
    );
    _setNamedBackgroundColor(view);
  } else if (color != null) {
    await _removeLaunchBackgroundColorSet();

    /// Update or add a `color` element for the background color
    final colorElement = view.getElement(IOSStrings.colorElement);
    if (colorElement != null) {
      /// Update existing color with provided color code
      _updateColorAttributes(colorElement, color);
    } else {
      /// Add a new color element with provided color code
      view.children.add(XmlElement(
        XmlName(IOSStrings.colorElement),
        _buildColorAttributes(color),
      ));
    }
  } else {
    await _removeLaunchBackgroundColorSet();
    final colorElement = view.getElement(IOSStrings.colorElement);
    if (colorElement != null) {
      /// Update existing color to white background
      _updateColorAttributes(colorElement, IOSStrings.defaultColor);
    } else {
      /// Add a new color element with white background color as child in view element
      view.children.add(XmlElement(
        XmlName(IOSStrings.colorElement),
        _buildColorAttributes(IOSStrings.defaultColor),
      ));
    }
  }

  var shouldAddBackgroundImage = false;
  if (backgroundImage != null) {
    final backgroundImageFile = File(backgroundImage);
    final backgroundImageFileExists = await backgroundImageFile.exists();

    if (backgroundImageFileExists) {
      // Per Apple's Asset Catalog spec, a Dark appearance variant requires a base
      // "Any" appearance asset. Both are resolved here together before writing the image set.
      File? darkBackgroundImageFile;
      if (darkBackgroundImage != null) {
        final file = File(darkBackgroundImage);
        if (await file.exists()) {
          darkBackgroundImageFile = file;
        } else {
          throw SplashMasterException(
            message: 'Asset not found. $darkBackgroundImage',
          );
        }
      }
      await createBackgroundImage(
        const Image(
          scale: '3x',
          filename: '${IOSStrings.backgroundImageSnakeCase}.png',
          idiom: 'universal',
        ),
        backgroundImageFile,
        darkImageFile: darkBackgroundImageFile,
      );
      shouldAddBackgroundImage = true;
    } else {
      throw SplashMasterException(message: 'Asset not found. $backgroundImage');
    }
  } else {
    await _removeBackgroundImageSet();
  }

  final shouldRenderImageViews = imagePath != null || shouldAddBackgroundImage;

  if (shouldRenderImageViews) {
    /// Find (or create) subViews element in the storyboard only when needed.
    final subViews = _getOrCreateSubViews(view);

    /// Keep the storyboard idempotent by recreating the managed image nodes.
    _removeManagedImageViews(subViews);

    if (shouldAddBackgroundImage) {
      subViews.children.add(getImageXMLElement(
        elementId: IOSStrings.backgroundImageViewIdValue,
        imageName: IOSStrings.backgroundImage,
        contentMode: iosBackgroundContentMode ?? IOSStrings.contentModeValue,
      ));
    }

    if (imagePath != null) {
      subViews.children.add(getImageXMLElement(
        elementId: IOSStrings.defaultImageViewIdValue,
        imageName: IOSStrings.imageValue,
        contentMode: iosContentMode ?? IOSStrings.contentModeValue,
      ));
    }

    /// Remove all existing constraints elements to avoid duplicates
    view.children.removeWhere(
      (node) =>
          node is XmlElement &&
          node.name.qualified == IOSStrings.constraintsElement,
    );

    /// Add constraints in view element based on requested content modes.
    final constraintsElement = _buildConstraintsElement(
      includeSplashImage: imagePath != null,
      splashContentMode: iosContentMode ?? IOSStrings.contentModeValue,
      includeBackgroundImage: shouldAddBackgroundImage,
      backgroundContentMode:
          iosBackgroundContentMode ?? IOSStrings.contentModeValue,
    );
    if (constraintsElement != null) {
      view.children.add(constraintsElement);
    }
  } else {
    /// Remove all existing constraints elements
    view.children.removeWhere(
      (node) =>
          node is XmlElement &&
          node.name.qualified == IOSStrings.constraintsElement,
    );

    final subviewsTag = view.getElement(IOSStrings.subViewsElement);

    /// subview element
    subviewsTag?.remove();
  }

  _syncStoryboardImageResources(
    documentData,
    includeLaunchImage: imagePath != null,
    includeBackgroundImage: shouldAddBackgroundImage,
  );

  /// Write the updated storyboard content to the file.
  file.writeAsStringSync(
    '${xmlDocument.toXmlString(pretty: true, indent: '    ')}\n',
  );
}