exec method

  1. @override
Future<void> exec(
  1. ExecContext context
)
override

Run command.

The contents of katana.yaml and the arguments of the command are passed to context.

コマンドを実行します。

contextkatana.yamlの内容やコマンドの引数が渡されます。

Implementation

@override
Future<void> exec(ExecContext context) async {
  final location = context.yaml.getAsMap("location");
  final enableBackground = location.get("enable_background", false);
  final googleMap = location.getAsMap("google_map");
  final enableGoogleMap = googleMap.get("enable", false);
  final googleMapApiKeys = googleMap.getAsMap("api_key");
  final googleMapApiKeyForAndroid = googleMapApiKeys.get("android", "");
  final googleMapApiKeyForIOS = googleMapApiKeys.get("ios", "");
  final googleMapApiKeyForWeb = googleMapApiKeys.get("web", "");
  final permission = location.getAsMap("permission");
  if (enableGoogleMap &&
      googleMapApiKeyForAndroid.isEmpty &&
      googleMapApiKeyForIOS.isEmpty &&
      googleMapApiKeyForWeb.isEmpty) {
    error(
      "[google_map]->[api_key] is missing. Please add the API key issued by Google.",
    );
    return;
  }
  if (permission.isEmpty) {
    error(
      "The item [app]->[picker]->[permission] is missing. Please include the language code and the message when authorization is granted here.",
    );
    return;
  }
  label("Edit AndroidManifest.xml.");
  await AndroidManifestPermissionType.accessFineLocation.enablePermission();
  if (enableBackground) {
    await AndroidManifestPermissionType.accessBackgroundLocation
        .enablePermission();
  }
  if (enableGoogleMap) {
    final file = File("android/app/src/main/AndroidManifest.xml");
    if (!file.existsSync()) {
      throw Exception(
        "AndroidManifest does not exist in `android/app/src/main/AndroidManifest.xml`. Do `katana create` to complete the initial setup of the project.",
      );
    }
    final document = XmlDocument.parse(await file.readAsString());
    final manifest = document.findAllElements("manifest");
    if (manifest.isEmpty) {
      throw Exception(
        "The structure of AndroidManifest.xml is broken. Do `katana create` to complete the initial setup of the project.",
      );
    }
    final application = document.findAllElements("application");
    if (!application.first.children.any(
      (p0) =>
          p0 is XmlElement &&
          p0.name.toString() == "meta-data" &&
          p0.attributes.any(
            (p1) =>
                p1.name.toString() == "android:name" &&
                p1.value == "com.google.android.geo.API_KEY",
          ),
    )) {
      application.first.children.add(
        XmlElement(
          XmlName("meta-data"),
          [
            XmlAttribute(
              XmlName("android:name"),
              "com.google.android.geo.API_KEY",
            ),
            XmlAttribute(
              XmlName("android:value"),
              googleMapApiKeyForAndroid.isNotEmpty
                  ? googleMapApiKeyForAndroid
                  : (googleMapApiKeyForIOS.isNotEmpty
                      ? googleMapApiKeyForIOS
                      : googleMapApiKeyForWeb),
            ),
          ],
          [],
        ),
      );
    }
    await file.writeAsString(
      document.toXmlString(pretty: true, indent: "    ", newLine: "\n"),
    );
  }
  if (enableGoogleMap) {
    label("Edit config.properties");
    final configPropertiesFile = File("android/config.properties");
    if (!configPropertiesFile.existsSync()) {
      await configPropertiesFile.writeAsString("");
    }
    final configProperties = await configPropertiesFile.readAsLines();
    if (!configProperties
        .any((element) => element.startsWith("flutter.minSdkVersion"))) {
      await configPropertiesFile.writeAsString([
        ...configProperties,
        "flutter.minSdkVersion=${Config.firebaseMinSdkVersion}",
      ].join("\n"));
    }
    if (!configProperties
        .any((element) => element.startsWith("flutter.compileSdkVersion"))) {
      await configPropertiesFile.writeAsString([
        ...configProperties,
        "flutter.compileSdkVersion=${Config.billingCompileSdkVersion}",
      ].join("\n"));
    }
    label("Edit build.gradle");
    final gradle = AppGradle();
    await gradle.load();
    if (!gradle.loadProperties.any((e) => e.name == "configProperties")) {
      gradle.loadProperties.add(
        GradleLoadProperties(
          path: "config.properties",
          name: "configProperties",
          file: "configPropertiesFile",
        ),
      );
    }
    gradle.android?.defaultConfig.minSdkVersion =
        "configProperties[\"flutter.minSdkVersion\"].toInteger()";
    gradle.android?.compileSdkVersion =
        "configProperties[\"flutter.compileSdkVersion\"].toInteger()";
    await gradle.save();
    label("Edit settings.gradle.");
    final settingsGradle = SettingsGradle();
    await settingsGradle.load();
    if (!settingsGradle.plugins.any((element) =>
        element.package.startsWith("com.google.gms.google-services"))) {
      settingsGradle.plugins.add(
        SettingsGradlePlugins(
          package: "com.google.gms.google-services",
          version: Config.googleServicesVersion,
          apply: false,
        ),
      );
    }
    await settingsGradle.save();
    label("Edit AppDelegate.swift.");
    final file = File("ios/Runner/AppDelegate.swift");
    if (!file.existsSync()) {
      throw Exception(
        "AppDelegate.swift does not exist in `ios/Runner/AppDelegate.swift`. Do `katana create` to complete the initial setup of the project.",
      );
    }
    var swift = await file.readAsString();
    if (!RegExp("import GoogleMaps").hasMatch(swift)) {
      swift = swift.replaceFirst(
        "import Flutter",
        "import Flutter\nimport GoogleMaps",
      );
    }
    if (!RegExp(r"GMSServices.provideAPIKey").hasMatch(swift)) {
      swift = swift.replaceFirst(
        "    GeneratedPluginRegistrant.register",
        "    GMSServices.provideAPIKey(\"${googleMapApiKeyForIOS.isNotEmpty ? googleMapApiKeyForIOS : (googleMapApiKeyForAndroid.isNotEmpty ? googleMapApiKeyForAndroid : googleMapApiKeyForWeb)}\")\n    GeneratedPluginRegistrant.register",
      );
    }
    await file.writeAsString(swift);
    label("Edit index.html");
    final indexHtmlFile = File("web/index.html");
    if (!indexHtmlFile.existsSync()) {
      throw Exception(
        "index.html does not exist in `web/index.html`. Do `katana create` to complete the initial setup of the project.",
      );
    }
    final htmlDocument = parse(await indexHtmlFile.readAsString());
    final head = htmlDocument.head;
    if (head == null) {
      throw Exception(
        "The structure of index.html is broken. Do `katana create` to complete the initial setup of the project.",
      );
    }
    if (!head.children.any((e0) =>
        e0.localName == "script" &&
        e0.attributes["src"] != null &&
        e0.attributes["src"]!
            .startsWith("https://maps.googleapis.com/maps/api/js"))) {
      head.children.add(
        Element.tag("script")
          ..attributes["src"] =
              "https://maps.googleapis.com/maps/api/js?key=${googleMapApiKeyForWeb.isNotEmpty ? googleMapApiKeyForWeb : (googleMapApiKeyForAndroid.isNotEmpty ? googleMapApiKeyForAndroid : googleMapApiKeyForIOS)}",
      );
    }
    await indexHtmlFile.writeAsString(htmlDocument.outerHtml);
  }
  if (enableBackground) {
    label("Edit Info.plist.");
    final plist = File("ios/Runner/Info.plist");
    final document = XmlDocument.parse(await plist.readAsString());
    final dict = document.findAllElements("dict").firstOrNull;
    if (dict == null) {
      throw Exception(
        "Could not find `dict` element in `ios/Runner/Info.plist`. File is corrupt.",
      );
    }
    final node = dict.children.firstWhereOrNull((p0) {
      return p0 is XmlElement &&
          p0.name.toString() == "key" &&
          p0.innerText == "UIBackgroundModes";
    });
    if (node == null) {
      dict.children.addAll(
        [
          XmlElement(
            XmlName("key"),
            [],
            [XmlText("UIBackgroundModes")],
          ),
          XmlElement(
            XmlName("array"),
            [],
            [
              XmlElement(
                XmlName("string"),
                [],
                [XmlText("location")],
              )
            ],
          ),
        ],
      );
    } else {
      final next = node.nextElementSibling;
      if (next is XmlElement && next.name.toString() == "array") {
        if (!next.children.any(
          (p1) =>
              p1 is XmlElement &&
              p1.name.toString() == "string" &&
              p1.innerText == "location",
        )) {
          next.children.add(
            XmlElement(
              XmlName("string"),
              [],
              [XmlText("location")],
            ),
          );
        }
      } else {
        throw Exception(
          "The `ios/Runner/Info.plist` configuration is broken.",
        );
      }
    }
    await plist.writeAsString(
      document.toXmlString(pretty: true, indent: "\t", newLine: "\n"),
    );
  }
  label("Addition of permission messages.");
  await XCodePermissionType.locationWhenInUseUsage.setMessageToXCode(
    permission
        .map((key, value) => MapEntry(key, value.toString()))
        .where((key, value) => value.isNotEmpty),
  );
  await XCodePermissionType.locationAlwaysUsage.setMessageToXCode(
    permission
        .map((key, value) => MapEntry(key, value.toString()))
        .where((key, value) => value.isNotEmpty),
  );
  await XCodePermissionType.locationAlwaysAndWhenInUseUsage.setMessageToXCode(
    permission
        .map((key, value) => MapEntry(key, value.toString()))
        .where((key, value) => value.isNotEmpty),
  );
  await PodfilePermissionType.locationUsage.enablePermissionToPodfile();
  await addFlutterImport(
    [
      "masamune_location",
    ],
  );
}