diff --git a/.github/workflows/flutter-acceptance.yml b/.github/workflows/flutter-acceptance.yml new file mode 100644 index 00000000000..e4a43c03203 --- /dev/null +++ b/.github/workflows/flutter-acceptance.yml @@ -0,0 +1,82 @@ +name: Flutter app acceptance + +# Imports, transpiles and compiles the "ordinary" Flutter apps under +# scripts/flutter-apps with no edits (scripts/flutter-apps/run-acceptance.sh). +# The unit and behavior tests (dart-flutter-tests.yml) check constructs one at a +# time; this checks that a whole app built from pub packages and plugins gets +# through the importer, the transpiler and javac together. +# +# `flutter pub get` is real: the importer transpiles pure-Dart dependencies +# (dio, ...) from the pub cache, so the Flutter SDK is needed for resolution, +# never to build anything. + +on: + pull_request: + paths: + - 'maven/dart-transpiler/**' + - 'maven/dart-runtime/**' + - 'maven/flutter-runtime/**' + - 'maven/build-engine/src/main/java/com/codename1/maven/FlutterProjectImporter.java' + - 'scripts/flutter-apps/**' + - '.github/workflows/flutter-acceptance.yml' + push: + branches: + - master + paths: + - 'maven/dart-transpiler/**' + - 'maven/dart-runtime/**' + - 'maven/flutter-runtime/**' + - 'maven/build-engine/src/main/java/com/codename1/maven/FlutterProjectImporter.java' + - 'scripts/flutter-apps/**' + - '.github/workflows/flutter-acceptance.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + acceptance: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + env: + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Cache Maven dependencies + uses: actions/cache@v5 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-flutter-acceptance-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2-flutter-acceptance- + ${{ runner.os }}-m2 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: '3.35.4' + + # The runtimes join the reactor only on JDK 17+ (flutter-modules). + - name: Build the transpiler and runtimes + env: + RETRY_ONLY_MATCHING: transient + run: | + cd maven + bash ../scripts/ci/retry.sh mvn -B install \ + -pl core,dart-runtime,flutter-runtime,dart-transpiler -am -DskipTests \ + -Dmaven.javadoc.skip=true -Dmaven.source.skip=true \ + -Dspotbugs.skip=true -Dpmd.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true + + - name: Import, transpile and compile each app + run: JAVA17_HOME="$JAVA_HOME" scripts/flutter-apps/run-acceptance.sh diff --git a/maven/build-engine/src/main/java/com/codename1/maven/FlutterProjectImporter.java b/maven/build-engine/src/main/java/com/codename1/maven/FlutterProjectImporter.java index 0615f69fc60..11a65b9b31b 100644 --- a/maven/build-engine/src/main/java/com/codename1/maven/FlutterProjectImporter.java +++ b/maven/build-engine/src/main/java/com/codename1/maven/FlutterProjectImporter.java @@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -77,6 +78,49 @@ public final class FlutterProjectImporter { COVERED.put("flutter_localized_locales", "LocaleNames/LocaleNamesLocalizationsDelegate stubs"); COVERED.put("adaptive_breakpoints", "AdaptiveBreakpoints.getWindowType only; " + "isDisplayDesktop/isDisplaySmallDesktop/isDisplayMobile are not stubbed"); + // Plugin and state-management packs (flutter-runtime com.codename1.flutter.plugins.*, + // META-INF/dart/plugin_*.dart). Stubbed rather than transpiled from pub: the + // plugins call platform channels, and bloc/go_router depend on reified generics + // and the Router page API, neither of which a transpile can reproduce. + COVERED.put("bloc", "Bloc/Cubit/Emitter/BlocObserver (plugins.bloc)"); + COVERED.put("flutter_bloc", "BlocProvider/BlocBuilder/BlocListener/BlocConsumer/BlocSelector/" + + "RepositoryProvider and their Multi* forms (plugins.bloc)"); + COVERED.put("equatable", "Equatable with deep props equality (plugins.equatable)"); + COVERED.put("json_annotation", "@JsonSerializable/@JsonKey/@JsonValue and $enumDecode " + + "for committed *.g.dart sources (plugins.json)"); + COVERED.put("shared_preferences", "SharedPreferences over Storage (plugins.sharedpreferences)"); + COVERED.put("path_provider", "application/support/temporary/cache directories (plugins.pathprovider)"); + COVERED.put("dio", "Dio/BaseOptions/Options/Response/DioException/interceptors/FormData " + + "over ConnectionRequest (plugins.dio)"); + COVERED.put("go_router", "GoRouter/GoRoute/ShellRoute/GoRouterState and the context.go/push/pop " + + "extension; MaterialApp.router (plugins.gorouter)"); + COVERED.put("cached_network_image", "CachedNetworkImage/CachedNetworkImageProvider over " + + "Image.network (plugins.cachednetworkimage)"); + COVERED.put("cross_file", "XFile over FileSystemStorage or in-memory bytes (plugins.crossfile)"); + COVERED.put("share_plus", "SharePlus.instance.share and the static Share API over Display.share " + + "(plugins.share)"); + COVERED.put("package_info_plus", "PackageInfo.fromPlatform from the build properties (plugins.packageinfo)"); + COVERED.put("device_info_plus", "DeviceInfoPlugin android/ios/deviceInfo from Display properties " + + "(plugins.deviceinfo)"); + COVERED.put("connectivity_plus", "Connectivity.checkConnectivity/onConnectivityChanged over " + + "NetworkManager (plugins.connectivity)"); + COVERED.put("geolocator", "Geolocator positions, permissions and distance/bearing over " + + "LocationManager (plugins.geolocator)"); + COVERED.put("image_picker", "ImagePicker pickImage/pickVideo/pickMedia/pickMultiImage over Capture and " + + "the gallery (plugins.imagepicker)"); + COVERED.put("app_links", "AppLinks initial link and link streams from the AppArg launch property " + + "(plugins.applinks)"); + COVERED.put("flutter_local_notifications", "show/periodicallyShow/zonedSchedule/cancel and tap " + + "callbacks over LocalNotification (plugins.notifications)"); + COVERED.put("firebase_core", "Firebase.initializeApp/FirebaseOptions (plugins.firebase)"); + COVERED.put("firebase_auth", "FirebaseAuth email/password and anonymous accounts over the Identity " + + "Toolkit REST API (plugins.firebase)"); + COVERED.put("firebase_analytics", "FirebaseAnalytics events logged, screen views to AnalyticsService " + + "(plugins.firebase)"); + COVERED.put("firebase_messaging", "FirebaseMessaging token and foreground onMessage over Codename One " + + "push (plugins.firebase)"); + COVERED.put("google_maps_flutter", "GoogleMap widget with markers, camera and taps over MapView " + + "(plugins.googlemaps)"); // NOT covered, checked and confirmed absent: "collection" (no ListEquality/MapEquality/ // SetEquality stub -- the one hit on that name is a comment, not an implementation), // "flutter_staggered_grid_view" and "nested" (provider's internal composition helper, @@ -121,6 +165,13 @@ public static final class Result { /// directory, so the two cannot both be there. The source is kept, every time, /// and the asset is not copied. public final List assetCollisions = new ArrayList(); + /// Pure-Dart pub packages whose sources were copied to be transpiled with the + /// app, each with the number of its files the app reaches. + public final Map transpiledPackages = new TreeMap(); + /// A package the app imports that cannot be transpiled and has no stub, with + /// the reason: a platform plugin, or a library that needs dart:ffi, dart:html + /// or dart:mirrors. These are what an import of this app cannot provide. + public final Map unsupportedPackages = new TreeMap(); public String appName; public String appVersion; public int copiedFiles; @@ -186,6 +237,13 @@ public Result importProject(File source, File commonDir, String mainPackage, Str imported); } } + // The pub packages the app imports: pure-Dart ones are transpiled from their + // own sources, stubbed ones come from the runtime, anything else is reported. + Map packageConfig = readPackageConfig(source); + Map lockVersions = readLockVersions(source); + List packageSources = new ArrayList(); + r.copiedFiles += importPackageSources(libDir, pubspec.name, packageConfig, lockVersions, target, + previous, imported, packageSources, r); // Everything copied so far came from lib/. An asset landing on one of those // paths silently replaced the source on a first import, and a later import's // kept-local-edit check could keep either -- the result depended on how many @@ -197,8 +255,6 @@ public Result importProject(File source, File commonDir, String mainPackage, Str // checkout, not this project: resolve it the same way `flutter pub get` // left it resolvable, via .dart_tool/package_config.json first and the // pub cache (by the version pubspec.lock locked) otherwise. - Map packageConfig = readPackageConfig(source); - Map lockVersions = readLockVersions(source); Map resolvedPackageLibDirs = new HashMap(); Set packagesWithCopiedAssets = new LinkedHashSet(); // Every declared key that resolved to real content, recorded for @@ -292,12 +348,13 @@ public Result importProject(File source, File commonDir, String mainPackage, Str r.assetCollisions.addAll(collisions); sourcePaths = null; writeImportRecord(target, imported); - writeLibSources(target, libDir); + writeLibSources(target, libDir, packageSources); writeDeclaredAssetKeys(target, declaredAssetKeys); writePackageName(target, pubspec.name); if (mainPackage != null && mainClass != null) { - writeEntryPoint(new File(commonDir, "src/main/java"), mainPackage, mainClass, entryPointPackage); + writeEntryPoint(new File(commonDir, "src/main/java"), mainPackage, mainClass, entryPointPackage, + pubspec.dependencies); } updateSettings(commonDir, pubspec); @@ -316,6 +373,7 @@ public Result importProject(File source, File commonDir, String mainPackage, Str } } + Set reachedPackages = new HashSet(lastReachedPackages); for (String dep : pubspec.dependencies) { boolean ignored = false; for (String i : IGNORED) { @@ -331,10 +389,24 @@ public Result importProject(File source, File commonDir, String mainPackage, Str if (note == null) { note = assetOnlyCovered.get(dep); } + if (note == null && r.transpiledPackages.containsKey(dep)) { + note = "transpiled from its sources, " + r.transpiledPackages.get(dep) + " file(s)"; + } + if (note == null && !r.unsupportedPackages.containsKey(dep) && !reachedPackages.contains(dep)) { + note = "not imported by lib/"; + } if (note != null) { r.covered.add(dep + " (" + note + ")"); } else { - r.uncovered.add(dep); + String why = r.unsupportedPackages.get(dep); + r.uncovered.add(why == null ? dep : dep + " (" + why + ")"); + } + } + // A package reached only through another one (a transitive dependency) is not + // in pubspec.yaml's list, but the app cannot build without it either. + for (Map.Entry u : r.unsupportedPackages.entrySet()) { + if (!pubspec.dependencies.contains(u.getKey())) { + r.uncovered.add(u.getKey() + " (" + u.getValue() + ", needed by a dependency)"); } } } catch (IOException e) { @@ -343,6 +415,346 @@ public Result importProject(File source, File commonDir, String mainPackage, Str return r; } + /// SDK packages the runtime itself provides; never copied, never reported. + static final Set SDK_PACKAGES = new HashSet(java.util.Arrays.asList( + "flutter", "flutter_test", "flutter_driver", "flutter_web_plugins", "flutter_localizations", + "sky_engine", "integration_test")); + + /// The `dart:` libraries the transpiler implements -- its configurable-import + /// test answers true for exactly these, and the walk below follows the same + /// alternative it will pick. + static final Set SUPPORTED_DART_LIBRARIES = new HashSet(java.util.Arrays.asList( + "core", "async", "collection", "convert", "io", "math", "typed_data", "ui")); + + private static final Pattern DIRECTIVE = Pattern.compile( + "(?m)^\\s*(import|export|part)\\s+(?!of\\b)(['\"])([^'\"]+)\\2([^;]*);"); + private static final Pattern CONDITIONAL = Pattern.compile( + "if\\s*\\(\\s*dart\\.library\\.([A-Za-z_]+)\\s*\\)\\s*(['\"])([^'\"]+)\\2"); + + /// The packages the last [#importPackageSources] reached, stubbed ones included. + private final Set lastReachedPackages = new HashSet(); + + /// Follows the app's imports from every file in `lib/`, file by file, into its pub + /// packages. A package the runtime stubs (COVERED) or the SDK provides is not + /// followed. A pure-Dart package has every file the app reaches copied to + /// `packages//` -- where Flutter's `packages//...` asset keys already + /// put that package's files -- for the transpiler to compile with the app. A + /// platform plugin, or a file that needs a `dart:` library with no counterpart, is + /// recorded in [Result#unsupportedPackages] and not followed further. + /// + /// Only reachable files are copied: a package's tests, tools and web-only + /// libraries never reach the transpiler. + private int importPackageSources(File libDir, String ownName, Map packageConfig, + Map lockVersions, File target, Map previous, + Map imported, List packageSources, Result r) throws IOException { + lastReachedPackages.clear(); + // (package, path inside its lib/) of every file visited; own package is null. + Set seen = new HashSet(); + java.util.ArrayDeque work = new java.util.ArrayDeque(); + List own = new ArrayList(); + collectDartSources(libDir, "", own); + // Seeded from EVERY lib/ file, not just the entry point's import graph: all of + // lib/ is copied and the transpiler compiles the whole program, so a file nothing + // imports is still compiled, and the packages it imports must resolve too. + // Reporting them here names the problem; skipping them would only move it to a + // compile error in the transpiler. + for (String f : own) { + work.add(new String[] {null, f}); + seen.add("\u0000" + f); + } + Map libDirs = new HashMap(); + Map counts = new TreeMap(); + Map> importers = new HashMap>(); + Map pluginCache = new HashMap(); + int copied = 0; + while (!work.isEmpty()) { + String[] item = work.poll(); + String pkg = item[0]; + String rel = item[1]; + File base = pkg == null ? libDir : libDirs.get(pkg); + File file = new File(base, rel.replace('/', File.separatorChar)); + if (!file.isFile()) { + continue; + } + String text = new String(Files.readAllBytes(file.toPath()), Charset.forName("UTF-8")); + if (pkg != null) { + String dest = "packages/" + pkg + "/" + rel; + copied += copy(file, new File(target, dest.replace('/', File.separatorChar)), dest, previous, + imported); + packageSources.add(dest); + Integer c = counts.get(pkg); + counts.put(pkg, Integer.valueOf(c == null ? 1 : c.intValue() + 1)); + } + Matcher m = DIRECTIVE.matcher(stripComments(text)); + while (m.find()) { + String uri = selectUri(m.group(3), m.group(4)); + if (uri.startsWith("dart:")) { + String lib = uri.substring(5); + // Anything the transpiler does not implement is unsupported, not only + // the libraries listed as such: dart:developer copied through would + // only fail later, in the transpiler, instead of in this report. + if (pkg != null && !SUPPORTED_DART_LIBRARIES.contains(lib) + && !r.unsupportedPackages.containsKey(pkg)) { + r.unsupportedPackages.put(pkg, "needs dart:" + lib + ", which has no Codename One " + + "counterpart (" + rel + ")"); + } + continue; + } + String nextPkg; + String nextRel; + if (uri.startsWith("package:")) { + int slash = uri.indexOf('/'); + if (slash < 0) { + continue; + } + nextPkg = uri.substring("package:".length(), slash); + nextRel = uri.substring(slash + 1); + if (nextPkg.equals(ownName)) { + nextPkg = null; + } + } else if (uri.indexOf(':') >= 0) { + continue; + } else { + nextPkg = pkg; + int cut = rel.lastIndexOf('/'); + nextRel = normalize((cut < 0 ? "" : rel.substring(0, cut + 1)) + uri); + } + if (nextPkg != null) { + lastReachedPackages.add(nextPkg); + if (pkg != null && !pkg.equals(nextPkg)) { + Set by = importers.get(nextPkg); + if (by == null) { + by = new TreeSet(); + importers.put(nextPkg, by); + } + by.add(pkg); + } + if (SDK_PACKAGES.contains(nextPkg) || COVERED.containsKey(nextPkg) + || r.unsupportedPackages.containsKey(nextPkg)) { + continue; + } + if (!libDirs.containsKey(nextPkg)) { + File dir = resolvePackageLibDir(nextPkg, packageConfig, lockVersions); + if (dir == null) { + if (!r.unresolvedPackages.contains(nextPkg)) { + r.unresolvedPackages.add(nextPkg); + log.warn("Flutter package '" + nextPkg + "' is imported but not resolvable from " + + ".dart_tool/package_config.json or the pub cache; run `flutter pub get` " + + "once in the Flutter project"); + } + r.unsupportedPackages.put(nextPkg, "sources not found; run `flutter pub get`"); + continue; + } + Boolean plugin = pluginCache.get(nextPkg); + if (plugin == null) { + plugin = Boolean.valueOf(isPlatformPlugin(dir)); + pluginCache.put(nextPkg, plugin); + } + if (plugin.booleanValue()) { + r.unsupportedPackages.put(nextPkg, "a platform plugin with no Codename One stub"); + continue; + } + libDirs.put(nextPkg, dir); + } + } + if (seen.add((nextPkg == null ? "\u0000" : nextPkg + "\u0000") + nextRel)) { + work.add(new String[] {nextPkg, nextRel}); + } + } + } + // A package that imports an unsupported one cannot compile either (its symbols + // were never imported), so the verdict travels up to every importer -- else the + // build failed in the importer's sources while this report blamed only the leaf. + java.util.ArrayDeque verdicts = new java.util.ArrayDeque(r.unsupportedPackages.keySet()); + while (!verdicts.isEmpty()) { + String dep = verdicts.poll(); + Set by = importers.get(dep); + if (by == null) { + continue; + } + for (String p : by) { + if (!r.unsupportedPackages.containsKey(p)) { + r.unsupportedPackages.put(p, "depends on " + dep + ", which is unsupported"); + verdicts.add(p); + } + } + } + // A package found unsupported after some of its files were copied is not + // transpiled: drop what was copied, so the build does not compile half of it. + for (String bad : r.unsupportedPackages.keySet()) { + if (counts.remove(bad) != null) { + String prefix = "packages/" + bad + "/"; + for (java.util.Iterator it = packageSources.iterator(); it.hasNext();) { + String ps = it.next(); + if (ps.startsWith(prefix)) { + it.remove(); + String written = imported.remove(ps); + File f = new File(target, ps.replace('/', File.separatorChar)); + // Only a file that is still exactly the importer's copy goes. One + // edited locally (copy() kept it, recording the earlier hash) is + // left for removeStaleImports, which keeps it with a warning. + if (f.isFile() && (written == null || !written.equals(sha256(f)))) { + continue; + } + if (f.isFile() && !f.delete()) { + log.warn("Could not remove " + f); + } + } + } + } + } + r.transpiledPackages.putAll(counts); + return copied; + } + + /// The uri a configurable import names here: the first `if (dart.library.x)` + /// alternative whose library the transpiler implements, else the default. + static String selectUri(String defaultUri, String conditions) { + Matcher c = CONDITIONAL.matcher(conditions); + while (c.find()) { + if (SUPPORTED_DART_LIBRARIES.contains(c.group(1))) { + return c.group(3); + } + } + return defaultUri; + } + + /// Whether a package's pubspec declares `flutter: plugin:` -- a federated or + /// classic platform plugin, whose Dart side calls into native code. + static boolean isPlatformPlugin(File packageLibDir) { + File pubspec = new File(packageLibDir.getParentFile(), "pubspec.yaml"); + if (!pubspec.isFile()) { + return false; + } + try { + boolean inFlutter = false; + for (String line : Files.readAllLines(pubspec.toPath(), Charset.forName("UTF-8"))) { + if (line.trim().isEmpty() || line.trim().startsWith("#")) { + continue; + } + if (!line.startsWith(" ")) { + inFlutter = line.startsWith("flutter:"); + continue; + } + if (inFlutter && line.trim().startsWith("plugin:")) { + return true; + } + } + } catch (IOException e) { + return false; + } + return false; + } + + /// Removes `//` and `/* */` comments, keeping string literals intact enough for + /// directive matching (a directive is never inside a string). A triple-quoted + /// string's contents are blanked, newlines kept: it spans lines, and a line of it + /// that reads like `import 'package:x/x.dart';` is text, not a dependency. A raw + /// string (`r'...'`) has no escapes. + static String stripComments(String s) { + StringBuilder out = new StringBuilder(s.length()); + int i = 0; + int n = s.length(); + char quote = 0; + boolean rawQuote = false; + while (i < n) { + char ch = s.charAt(i); + if (quote == 0 && (ch == '\'' || ch == '"') && i + 2 < n + && s.charAt(i + 1) == ch && s.charAt(i + 2) == ch) { + boolean raw = i > 0 && (s.charAt(i - 1) == 'r' || s.charAt(i - 1) == 'R'); + String close = new String(new char[] {ch, ch, ch}); + out.append(close); + i += 3; + while (i < n && !s.startsWith(close, i)) { + char c = s.charAt(i); + if (c == '\\' && !raw && i + 1 < n) { + out.append(s.charAt(i + 1) == '\n' ? "\n " : " "); + i += 2; + continue; + } + out.append(c == '\n' ? '\n' : ' '); + i++; + } + if (i < n) { + out.append(close); + i += 3; + } + continue; + } + if (quote != 0) { + out.append(ch); + if (ch == '\\' && i + 1 < n && !rawQuote) { + out.append(s.charAt(i + 1)); + i += 2; + continue; + } + if (ch == quote || ch == '\n') { + quote = 0; + } + i++; + continue; + } + if (ch == '/' && i + 1 < n && s.charAt(i + 1) == '/') { + while (i < n && s.charAt(i) != '\n') { + i++; + } + continue; + } + if (ch == '/' && i + 1 < n && s.charAt(i + 1) == '*') { + int depth = 0; + while (i < n) { + if (s.startsWith("/*", i)) { + depth++; + i += 2; + } else if (s.startsWith("*/", i)) { + depth--; + i += 2; + if (depth == 0) { + break; + } + } else { + if (s.charAt(i) == '\n') { + out.append('\n'); + } + i++; + } + } + continue; + } + if (ch == '\'' || ch == '"') { + quote = ch; + rawQuote = i > 0 && (s.charAt(i - 1) == 'r' || s.charAt(i - 1) == 'R'); + } + out.append(ch); + i++; + } + return out.toString(); + } + + private static String normalize(String path) { + List out = new ArrayList(); + for (String p : path.split("/")) { + if (p.isEmpty() || p.equals(".")) { + continue; + } + if (p.equals("..")) { + if (!out.isEmpty()) { + out.remove(out.size() - 1); + } + } else { + out.add(p); + } + } + StringBuilder b = new StringBuilder(); + for (String p : out) { + if (b.length() > 0) { + b.append('/'); + } + b.append(p); + } + return b.toString(); + } + /// Rewrites `codename1.version`/`codename1.displayName` in /// `commonDir/codenameone_settings.properties` from the pubspec, but only /// when the project already declares that key -- this never introduces a @@ -365,6 +777,9 @@ public Result importProject(File source, File commonDir, String mainPackage, Str /// (`CodenameOne/src/.../buildhints/Ios.java`, which backs /// `CFBundleVersion`) -- added rather than updated-if-present, since the /// archetype's settings file never declares either by default. + /// Must match com.codename1.flutter.plugins.packageinfo.PackageInfo. + static final String BUILD_NUMBER_RESOURCE = "cn1-flutter-build-number.txt"; + private void updateSettings(File commonDir, Pubspec pubspec) throws IOException { File settings = new File(commonDir, "codenameone_settings.properties"); if (!settings.isFile()) { @@ -380,6 +795,23 @@ private void updateSettings(File commonDir, Pubspec pubspec) throws IOException String buildNumber = pubspec.version.substring(plus + 1); updated = setOrAppendProperty(updated, "codename1.arg.android.versionCode", buildNumber); updated = setOrAppendProperty(updated, "codename1.arg.ios.bundleVersion", buildNumber); + // No device property reports the native build number, so package_info_plus + // reads it from here (PackageInfo.BUILD_NUMBER_RESOURCE). + File res = new File(commonDir, "src/main/resources/" + BUILD_NUMBER_RESOURCE); + res.getParentFile().mkdirs(); + Files.write(res.toPath(), buildNumber.getBytes(Charset.forName("UTF-8"))); + } else { + // A reimport after the pubspec dropped its +build part: the resource an + // earlier import wrote would keep reporting the old build number, and the + // native hints it appended would keep building with it. A hint whose value + // is not the one this importer wrote was set by hand and is kept. + File res = new File(commonDir, "src/main/resources/" + BUILD_NUMBER_RESOURCE); + if (res.isFile()) { + String old = new String(Files.readAllBytes(res.toPath()), Charset.forName("UTF-8")).trim(); + updated = removePropertyIfValue(updated, "codename1.arg.android.versionCode", old); + updated = removePropertyIfValue(updated, "codename1.arg.ios.bundleVersion", old); + Files.delete(res.toPath()); + } } } if (pubspec.name != null) { @@ -391,6 +823,13 @@ private void updateSettings(File commonDir, Pubspec pubspec) throws IOException } } + /// Removes the `key=value` line whose value is exactly `value`; any other + /// value (or no line) leaves the file untouched. + static String removePropertyIfValue(String properties, String key, String value) { + return properties.replaceAll("(?m)^" + Pattern.quote(key) + "\\s*=\\s*" + Pattern.quote(value) + + "[ \\t]*(\\r?\\n|$)", ""); + } + /// Replaces the value of a `key=value` line in a `.properties` file, /// leaving every other line untouched; a `key` the file does not already /// declare is left alone rather than appended. @@ -417,17 +856,11 @@ static String setOrAppendProperty(String properties, String key, String value) { return properties + separator + key + "=" + value + "\n"; } - private void writeEntryPoint(File javaDir, String pkg, String cls, String flutterPackage) throws IOException { + private void writeEntryPoint(File javaDir, String pkg, String cls, String flutterPackage, + java.util.Collection dependencies) throws IOException { File f = new File(javaDir, pkg.replace('.', File.separatorChar) + File.separator + cls + ".java"); f.getParentFile().mkdirs(); - String src = "package " + pkg + ";\n\n" - + "/// The Codename One entry point: starts the imported Flutter application's Dart `main()`.\n" - + "public class " + cls + " extends com.codename1.system.Lifecycle {\n" - + " @Override\n" - + " public void runApp() {\n" - + " " + flutterPackage + ".FlutterRegistry.invokeMain();\n" - + " }\n" - + "}\n"; + String src = entryPointSource(pkg, cls, flutterPackage, dependencies); byte[] bytes = src.getBytes(Charset.forName("UTF-8")); if (f.isFile()) { File backup = new File(f.getPath() + ".pre-flutter-import"); @@ -448,6 +881,87 @@ private void writeEntryPoint(File javaDir, String pkg, String cls, String flutte Files.write(f.toPath(), bytes); } + /// The entry point's source. The plugin packs that need the platform to call the + /// app -- a deep link (app_links), a notification tap (flutter_local_notifications), a + /// push (firebase_messaging) -- get their callbacks forwarded here, and only when the + /// app depends on them: implementing PushCallback is what turns push on in a build. + static String entryPointSource(String pkg, String cls, String flutterPackage, + java.util.Collection dependencies) { + boolean links = dependencies.contains("app_links"); + boolean notifications = dependencies.contains("flutter_local_notifications"); + boolean push = dependencies.contains("firebase_messaging"); + StringBuilder implementsList = new StringBuilder(); + if (notifications) { + implementsList.append("com.codename1.notifications.LocalNotificationCallback"); + } + if (push) { + implementsList.append(implementsList.length() > 0 ? ", " : "").append("com.codename1.push.PushCallback"); + } + StringBuilder b = new StringBuilder(); + b.append("package ").append(pkg).append(";\n\n") + .append("/// The Codename One entry point: starts the imported Flutter application's Dart `main()`.\n") + .append("public class ").append(cls).append(" extends com.codename1.system.Lifecycle"); + if (implementsList.length() > 0) { + b.append("\n implements ").append(implementsList); + } + b.append(" {\n"); + if (links) { + b.append(" @Override\n") + .append(" public void start() {\n") + .append(" com.codename1.flutter.plugins.applinks.AppLinks.appStarted();\n") + .append(" super.start();\n") + .append(" }\n\n"); + } + b.append(" @Override\n") + .append(" public void runApp() {\n"); + if (push) { + // the runtime reads the key through this, so only an app using push names Push + b.append(" com.codename1.flutter.plugins.firebase.FirebaseMessaging.tokenSource(\n") + .append(" new dart.runtime.Funcs.Func0() {\n") + .append(" @Override\n") + .append(" public String call() {\n") + .append(" return com.codename1.push.Push.getPushKey();\n") + .append(" }\n") + .append(" });\n"); + b.append(" com.codename1.flutter.plugins.firebase.FirebaseMessaging.topicSink(\n") + .append(" new dart.runtime.Funcs.VoidFunc2() {\n") + .append(" @Override\n") + .append(" public void call(String topic, Boolean subscribe) {\n") + .append(" if (subscribe.booleanValue()) {\n") + .append(" com.codename1.push.Push.subscribeToTopic(topic);\n") + .append(" } else {\n") + .append(" com.codename1.push.Push.unsubscribeFromTopic(topic);\n") + .append(" }\n") + .append(" }\n") + .append(" });\n"); + } + b.append(" ").append(flutterPackage).append(".FlutterRegistry.invokeMain();\n") + .append(" }\n"); + if (notifications) { + b.append("\n @Override\n") + .append(" public void localNotificationReceived(String notificationId) {\n") + .append(" com.codename1.flutter.plugins.notifications.FlutterLocalNotificationsPlugin") + .append(".notificationTapped(notificationId);\n") + .append(" }\n"); + } + if (push) { + b.append("\n @Override\n") + .append(" public void push(String value) {\n") + .append(" com.codename1.flutter.plugins.firebase.FirebaseMessaging.pushReceived(value);\n") + .append(" }\n\n") + .append(" @Override\n") + .append(" public void registeredForPush(String deviceId) {\n") + .append(" com.codename1.flutter.plugins.firebase.FirebaseMessaging.tokenRegistered(deviceId);\n") + .append(" }\n\n") + .append(" @Override\n") + .append(" public void pushRegistrationError(String error, int errorCode) {\n") + .append(" com.codename1.io.Log.p(\"Push registration failed: \" + error);\n") + .append(" com.codename1.flutter.plugins.firebase.FirebaseMessaging.registrationFailed(error);\n") + .append(" }\n"); + } + return b.append("}\n").toString(); + } + /// The manifest-free minimal record: one ` ` line per file /// this import copied, `path` relative to `src/main/flutter`, `/` /// separated. Same semantics as [AndroidProjectImporter#IMPORT_RECORD]: @@ -509,9 +1023,10 @@ private static void writeImportRecord(File target, Map imported) /// as source there. Read by `com.codename1.dart.transpiler.api.DartTranspiler`. static final String LIB_SOURCES_RECORD = ".flutter-lib-sources"; - private static void writeLibSources(File target, File libDir) throws IOException { + private static void writeLibSources(File target, File libDir, List packageSources) throws IOException { List sources = new ArrayList(); collectDartSources(libDir, "", sources); + sources.addAll(packageSources); java.util.Collections.sort(sources); StringBuilder b = new StringBuilder(); for (String s : sources) { diff --git a/maven/build-engine/src/test/java/com/codename1/maven/FlutterProjectImporterTest.java b/maven/build-engine/src/test/java/com/codename1/maven/FlutterProjectImporterTest.java index 2cdd7347bce..e254ac47223 100644 --- a/maven/build-engine/src/test/java/com/codename1/maven/FlutterProjectImporterTest.java +++ b/maven/build-engine/src/test/java/com/codename1/maven/FlutterProjectImporterTest.java @@ -88,7 +88,8 @@ public void importsLibTreeAssetsAndFontsAndReportsDependencies() throws Exceptio File root = Files.createTempDirectory("flutter-import").toFile(); File flutterProject = new File(root, "demo"); write(new File(flutterProject, "pubspec.yaml"), PUBSPEC); - write(new File(flutterProject, "lib/main.dart"), "void main() {}\n"); + // imported, so it is a real dependency: one lib/ never imports is not reported + write(new File(flutterProject, "lib/main.dart"), "import 'package:some_uncovered_pkg/a.dart';\nvoid main() {}\n"); write(new File(flutterProject, "lib/pages/home.dart"), "class Home {}\n"); write(new File(flutterProject, "assets/images/logo.png"), "PNGDATA"); write(new File(flutterProject, "assets/icons/a.png"), "ICONA"); @@ -103,7 +104,8 @@ public void importsLibTreeAssetsAndFontsAndReportsDependencies() throws Exceptio assertEquals("1.2.3+4", r.appVersion); assertTrue(r.covered.toString(), r.covered.get(0).startsWith("flutter (")); assertTrue(r.covered.toString(), r.covered.get(1).startsWith("cupertino_icons (")); - assertEquals(java.util.Collections.singletonList("some_uncovered_pkg"), r.uncovered); + assertEquals(r.uncovered.toString(), 1, r.uncovered.size()); + assertTrue(r.uncovered.toString(), r.uncovered.get(0).startsWith("some_uncovered_pkg (sources not found")); assertTrue(r.missingAssets.toString(), r.missingAssets.isEmpty()); File flutterDir = new File(common, "src/main/flutter"); @@ -368,6 +370,37 @@ public void anAssetNeverOverwritesALibSourceAtTheSamePath() throws Exception { /// lib/ is copied flat, so lib/assets/x.dart shares assets/ with the bundled /// assets; the lib-sources record lists exactly the Dart files that came from lib/. + /// A pure-Dart package importing an unsupported one cannot compile either: the + /// verdict travels to it, and none of its files reach the build. + @Test + public void aPackageImportingAnUnsupportedOneIsUnsupportedToo() throws Exception { + File root = Files.createTempDirectory("flutter-transitive").toFile(); + File flutterProject = new File(root, "demo"); + write(new File(flutterProject, "pubspec.yaml"), "name: demo\nversion: 1.0.0\n" + + "dependencies:\n flutter:\n sdk: flutter\n outer: 1.0.0\n"); + write(new File(flutterProject, "lib/main.dart"), "import 'package:outer/outer.dart';\nvoid main() {}\n"); + File outer = new File(root, "cache/outer-1.0.0"); + write(new File(outer, "lib/outer.dart"), "import 'package:inner/inner.dart';\nint o() => 1;\n"); + File inner = new File(root, "cache/inner-1.0.0"); + write(new File(inner, "lib/inner.dart"), "import 'dart:mirrors';\nint i() => 1;\n"); + write(new File(flutterProject, ".dart_tool/package_config.json"), "{\n \"configVersion\": 2,\n" + + " \"packages\": [\n" + + " {\n \"name\": \"outer\",\n \"rootUri\": \"" + outer.toURI() + + "\",\n \"packageUri\": \"lib/\"\n },\n" + + " {\n \"name\": \"inner\",\n \"rootUri\": \"" + inner.toURI() + + "\",\n \"packageUri\": \"lib/\"\n }\n" + + " ]\n}\n"); + + File common = new File(root, "common"); + FlutterProjectImporter.Result r = new FlutterProjectImporter(new com.codename1.build.SystemStreamLog()) + .importProject(flutterProject, common, null, null); + + assertTrue(r.unsupportedPackages.toString(), r.unsupportedPackages.containsKey("inner")); + assertTrue(r.unsupportedPackages.toString(), + String.valueOf(r.unsupportedPackages.get("outer")).startsWith("depends on inner")); + assertFalse(new File(common, "src/main/flutter/packages/outer/outer.dart").exists()); + } + @Test public void recordsTheDartSourcesCopiedFromLib() throws Exception { File root = Files.createTempDirectory("flutter-lib-sources").toFile(); @@ -661,4 +694,45 @@ private static void write(File f, String content) throws IOException { private static String read(File f) throws IOException { return new String(Files.readAllBytes(f.toPath()), "UTF-8"); } + + @Test + public void entryPointForwardsPluginCallbacksOnlyWhenUsed() { + String plain = FlutterProjectImporter.entryPointSource("com.example", "Main", "com.example.flutter", + java.util.Arrays.asList("flutter", "dio")); + assertFalse(plain.contains("implements")); + assertFalse(plain.contains("AppLinks")); + assertTrue(plain.contains("com.example.flutter.FlutterRegistry.invokeMain();")); + // naming com.codename1.push makes the Android build require google-services.json + assertFalse(plain.contains("com.codename1.push")); + + String all = FlutterProjectImporter.entryPointSource("com.example", "Main", "com.example.flutter", + java.util.Arrays.asList("app_links", "flutter_local_notifications", "firebase_messaging")); + assertTrue(all.contains("implements com.codename1.notifications.LocalNotificationCallback, " + + "com.codename1.push.PushCallback")); + assertTrue(all.contains("AppLinks.appStarted();\n super.start();")); + assertTrue(all.contains("FlutterLocalNotificationsPlugin.notificationTapped(notificationId);")); + assertTrue(all.contains("FirebaseMessaging.pushReceived(value);")); + assertTrue(all.contains("return com.codename1.push.Push.getPushKey();")); + assertTrue(all.contains("com.codename1.push.Push.subscribeToTopic(topic);")); + } + + @Test + public void pluginPacksAreCovered() { + for (String p : new String[] {"share_plus", "geolocator", "image_picker", "firebase_auth", + "firebase_messaging", "google_maps_flutter", "flutter_local_notifications", "app_links"}) { + assertTrue(p, FlutterProjectImporter.COVERED.containsKey(p)); + } + } + + @Test + public void importLookingTextInTripleQuotedStringsIsNotADirective() { + String src = "import 'package:real/real.dart';\n" + + "const doc = \"\"\"\nimport 'package:fake/fake.dart';\n\"\"\";\n" + + "const raw = r'''\nimport 'package:fake2/x.dart';\\\n''';\n" + + "const re = r'\\d';\nimport 'package:after/after.dart';\n"; + String out = FlutterProjectImporter.stripComments(src); + assertTrue(out, out.contains("package:real/real.dart")); + assertTrue(out, out.contains("package:after/after.dart")); + assertFalse(out, out.contains("fake")); + } } diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeFlutterMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeFlutterMojo.java index d127c695651..c72370e40d3 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeFlutterMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeFlutterMojo.java @@ -97,6 +97,7 @@ public class TranscodeFlutterMojo extends AbstractCN1Mojo { protected void executeImpl() throws MojoExecutionException, MojoFailureException { if (flutterSourceDir == null || !flutterSourceDir.isDirectory()) { sweepStaleOutput(); + removeDeselectedPackClasses(); // Flutter was removed from the project: its assets go with it. removeStaleAssets(new HashSet()); return; @@ -116,6 +117,15 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException req.stubClasspathEntry(a.getFile()); } } + // The app's own Java can name a plugin pack too (the import-generated entry point + // installs the firebase_messaging and app_links hooks): the transpiler copies + // exactly the packs that are named, so it must see that Java as well. + for (Object root : project.getCompileSourceRoots()) { + File dir = new File(String.valueOf(root)); + if (dir.isDirectory() && !dir.getAbsoluteFile().equals(flutterOutputDir.getAbsoluteFile())) { + req.javaSourceRoot(dir); + } + } TranspileResult result = new DartTranspiler().transpile(req); for (Diagnostic d : result.getDiagnostics()) { String msg = "src/main/flutter/" + d.file + ":[" + d.line + "," + d.col + "] " @@ -139,10 +149,32 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException } else { getLog().info("Transpiled Flutter sources to " + flutterOutputDir); } + removeDeselectedPackClasses(); copyAssets(); registerSourceRoot(); } + /** + * The transpiler deletes the source of a plugin pack the app stopped naming, but javac + * does not reliably delete the class compiled from it on an earlier build. Left in + * target/classes, those classes would still ship -- and still imply the permissions + * (location, push, ...) the builders derive from the classes they find. + */ + private void removeDeselectedPackClasses() { + String packs = "com/codename1/flutter/plugins".replace('/', File.separatorChar); + File[] compiled = new File(project.getBuild().getOutputDirectory(), packs).listFiles(); + if (compiled == null) { + return; + } + File selected = new File(flutterOutputDir, packs); + for (File pack : compiled) { + if (pack.isDirectory() && !new File(selected, pack.getName()).isDirectory()) { + deleteRecursive(pack); + getLog().debug("Removed classes of unused Flutter plugin pack " + pack.getName()); + } + } + } + private void checkJdk() throws MojoFailureException { String spec = System.getProperty("java.specification.version", "1.8"); int major; diff --git a/maven/dart-runtime/src/main/java/dart/async/AsyncStar.java b/maven/dart-runtime/src/main/java/dart/async/AsyncStar.java new file mode 100644 index 00000000000..dff947c0843 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/async/AsyncStar.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.async; + +import dart.runtime.DartRuntime; +import dart.runtime.Funcs; + +/** + * Runtime support for {@code async*} generator bodies. + * + *

The body is lowered to a method that takes the generator's {@link Sink}: + * {@code yield v} is {@code sink.yield$(v)} and {@code yield* s} is + * {@code sink.yieldAll(s)}. As in Dart, the body starts only when the stream is + * listened to, and it runs on the event loop, so an {@code await} inside it parks + * through {@link Await} like any other async body. When the listener cancels, the + * next {@code yield} ends the body the way Dart's does: {@code finally} blocks run + * and nothing more is delivered.

+ * + *

Pause is not modelled: a paused subscription still receives events, which its + * subscription buffers.

+ */ +public final class AsyncStar { + + private AsyncStar() { + } + + /** Thrown out of a yield after the listener cancelled; ends the body silently. */ + static final class Cancelled extends RuntimeException { + private static final long serialVersionUID = 1L; + + Cancelled() { + super(null, null, false, false); + } + } + + /** The yield target an async* body writes to. */ + public static final class Sink { + final StreamController controller = new StreamController(); + volatile boolean cancelled; + private final java.util.List cancelHooks = new java.util.ArrayList(); + + /// Runs {@code hook} when the listener cancels: a body blocked on an upstream + /// source releases it then, not at the source's next event. + public void onCancel(Funcs.VoidFunc0 hook) { + // One lock with the cancel handler: a cancel between the check and the add + // left the hook unrun, and the body blocked on a quiet source forever. + boolean now; + synchronized (cancelHooks) { + now = cancelled; + if (!now) { + cancelHooks.add(hook); + } + } + if (now) { + hook.call(); + } + } + + /// An error event that does not end the stream, as a caught error in an + /// async* body re-raised to the listener would not. + public void addError(Object error) { + if (cancelled) { + throw new Cancelled(); + } + controller.addError(error instanceof Throwable ? DartRuntime.caught((Throwable) error) : error); + } + + public void yield$(T value) { + if (cancelled) { + throw new Cancelled(); + } + controller.add(value); + } + + public void yieldAll(Stream source) { + if (source == null) { + return; + } + // yield* forwards the source's errors and keeps delegating until it is done, + // as Dart does; a plain (await for) iterator would end the generator at the + // first error. Cancelling the generator cancels the source and wakes a body + // waiting on it, so the finally below (and the body's own cleanup) runs. + @SuppressWarnings("unchecked") + final StreamIterator it = new StreamIterator((Stream) source, new Funcs.VoidFunc1() { + @Override + public void call(Object e) { + if (!cancelled) { + addError(e); + } + } + }); + onCancel(new Funcs.VoidFunc0() { + @Override + public void call() { + it.cancel(); + } + }); + try { + while (it.next()) { + yield$(it.current()); + } + } finally { + it.cancel(); + } + if (cancelled) { + throw new Cancelled(); + } + } + } + + public static Stream asyncStar(final Funcs.VoidFunc1> body) { + return asyncStar(body, null); + } + + /// A stream transformation: {@code body} reads {@code source} through an iterator + /// and yields. The iterator subscribes inside the downstream listen(), so an event + /// the source emits right after it is buffered rather than lost to a broadcast + /// stream, and source errors are re-emitted in order while the body keeps reading, + /// as Dart's transformers (take, LineSplitter, utf8.decoder...) do. + public static Stream over(final Stream source, + final Funcs.VoidFunc2, Sink> body) { + final Object[] sinkRef = new Object[1]; + final StreamIterator it = new StreamIterator(source, new Funcs.VoidFunc1() { + @Override + @SuppressWarnings("unchecked") + public void call(Object e) { + Object sk = sinkRef[0]; + if (sk instanceof Sink) { + ((Sink) sk).addError(e); + } + } + }); + return asyncStar(new Funcs.VoidFunc1>() { + @Override + public void call(final Sink s) { + sinkRef[0] = s; + s.onCancel(new Funcs.VoidFunc0() { + @Override + public void call() { + it.cancel(); + } + }); + try { + body.call(it, s); + } finally { + it.cancel(); + } + } + }, new Funcs.VoidFunc0() { + @Override + public void call() { + it.start(); + } + }); + } + + /// {@code onListen} runs synchronously inside listen(), before the body's timer: a + /// derived stream subscribes to its source there, so an event the source emits right + /// after the downstream listen() is buffered rather than lost to a broadcast stream. + static Stream asyncStar(final Funcs.VoidFunc1> body, final Funcs.VoidFunc0 onListen) { + final Sink sink = new Sink(); + sink.controller.onListen(new Funcs.VoidFunc0() { + @Override + public void call() { + if (onListen != null) { + onListen.call(); + } + // Started from a timer, never inside listen() itself, as Dart does. + new Timer(dart.core.Duration.zero, new Funcs.VoidFunc0() { + @Override + public void call() { + run(body, sink); + } + }); + } + }); + sink.controller.onCancel(new Funcs.VoidFunc0() { + @Override + public void call() { + Funcs.VoidFunc0[] hooks; + synchronized (sink.cancelHooks) { + sink.cancelled = true; + hooks = sink.cancelHooks.toArray(new Funcs.VoidFunc0[sink.cancelHooks.size()]); + sink.cancelHooks.clear(); + } + for (Funcs.VoidFunc0 h : hooks) { + h.call(); + } + } + }); + return sink.controller.stream(); + } + + private static void run(Funcs.VoidFunc1> body, Sink sink) { + try { + body.call(sink); + } catch (Cancelled c) { + // the listener went away; nothing is left to deliver to + } catch (Throwable t) { + if (!sink.cancelled && !sink.controller.isClosed()) { + sink.controller.addError(DartRuntime.caught(t)); + } + } + if (!sink.controller.isClosed()) { + sink.controller.close(); + } + } +} diff --git a/maven/dart-runtime/src/main/java/dart/async/Future.java b/maven/dart-runtime/src/main/java/dart/async/Future.java index 48702b5625a..b1d851b9609 100644 --- a/maven/dart-runtime/src/main/java/dart/async/Future.java +++ b/maven/dart-runtime/src/main/java/dart/async/Future.java @@ -77,6 +77,30 @@ public T getNow() { } } + /** + * The value of a future that has already completed with one, else null. Lets Java + * code act on a {@code FutureOr} answer that is ready now without waiting for it. + */ + /** Whether this future has already completed, with a value or an error (Java side only). */ + public boolean completedNow() { + synchronized (lock) { + return done; + } + } + + public T completedValue() { + synchronized (lock) { + return done && error == null ? value : null; + } + } + + /** The error of a future that has already failed, else null (Java side only). */ + public Throwable completedError() { + synchronized (lock) { + return done ? error : null; + } + } + /** An already-completed future. */ public static Future value(T v) { Future f = new Future(); diff --git a/maven/dart-runtime/src/main/java/dart/async/Stream.java b/maven/dart-runtime/src/main/java/dart/async/Stream.java index 556dff0570b..5b749cf590c 100644 --- a/maven/dart-runtime/src/main/java/dart/async/Stream.java +++ b/maven/dart-runtime/src/main/java/dart/async/Stream.java @@ -167,6 +167,12 @@ void resolve() { * event channel overrides this to call the platform * {@code StreamHandler.onListen}. */ + /// The stream a new listener actually subscribes to: this one, except for a + /// stream that replays from scratch for every listener (Stream.fromIterable). + Stream perListener() { + return this; + } + protected void onFirstListener() { } @@ -213,6 +219,10 @@ public final StreamSubscription listen(Funcs.VoidFunc1 onData, Object onEr */ public final StreamSubscription listen(Funcs.VoidFunc1 onData, Object onError, Funcs.VoidFunc0 onDone, Boolean cancelOnError) { + Stream target = perListener(); + if (target != this) { + return target.listen(onData, onError, onDone, cancelOnError); + } Sub sub = new Sub(); sub.onData = onData; sub.onError = onError; @@ -546,9 +556,20 @@ static void deliverData(Object onData, Object value) { */ @SuppressWarnings("unchecked") static void deliverError(Object onError, Object error) { + try { + callError(onError, error); + } catch (Throwable t) { + reportUncaught(t); + } + } + + /// {@link #deliverError} without the guard: what the handler throws propagates, + /// for a transformer (handleError) whose thrown error belongs on its output. + @SuppressWarnings("unchecked") + static void callError(Object onError, Object error) { Object err = error instanceof Throwable ? dart.runtime.DartRuntime.caught((Throwable) error) : error; - try { + { if (onError instanceof Funcs.VoidFunc1) { ((Funcs.VoidFunc1) onError).call(err); } else if (onError instanceof Funcs.Func1) { @@ -563,8 +584,6 @@ static void deliverError(Object onError, Object error) { // has nowhere else to go but the log. reportUncaught(err instanceof Throwable ? (Throwable) err : new RuntimeException(String.valueOf(err))); } - } catch (Throwable t) { - reportUncaught(t); } } @@ -662,4 +681,772 @@ public boolean isPaused() { return sub.pauseCount > 0; } } + + // ------------------------------------------------------------------ + // Factories, terminal operations and derived streams. + // + // Derived streams are async* generators over a StreamIterator, so they + // inherit its cancellation and start from a timer, as Dart's do. Terminal + // operations listen and complete a future; none of them blocks. + // ------------------------------------------------------------------ + + /** Dart's {@code Stream.fromIterable}: each element in turn, then done. */ + public static Stream fromIterable(final Iterable elements) { + // Dart lets this stream be listened to more than once, each listener getting + // its own iteration; one async* run would hand a second listener only done. + return new Stream() { + @Override + Stream perListener() { + return AsyncStar.asyncStar(new Funcs.VoidFunc1>() { + @Override + public void call(AsyncStar.Sink s) { + if (elements != null) { + for (T e : elements) { + s.yield$(e); + } + } + } + }); + } + }; + } + + /** Dart's {@code Stream.value}. */ + public static Stream value(T v) { + return fromIterable(java.util.Collections.singletonList(v)); + } + + /** Dart's {@code Stream.empty}. */ + public static Stream empty() { + return fromIterable(java.util.Collections.emptyList()); + } + + /** Dart's {@code Stream.error}. */ + public static Stream error(final Object error) { + return AsyncStar.asyncStar(new Funcs.VoidFunc1>() { + @Override + public void call(AsyncStar.Sink s) { + throw dart.runtime.DartRuntime.asError(error); + } + }); + } + + /** Dart's {@code Stream.fromFuture}: the future's value as one event. */ + public static Stream fromFuture(final Future future) { + return AsyncStar.asyncStar(new Funcs.VoidFunc1>() { + @Override + public void call(AsyncStar.Sink s) { + s.yield$(Await.await$(future)); + } + }); + } + + /** + * Dart's {@code Stream.fromFutures}: each value or error as its future completes, + * not in list order, so a slow first future does not hold back the others. Done + * once every future has completed. + */ + public static Stream fromFutures(final Iterable> futures) { + final StreamController c = new StreamController(); + final int[] pending = {1}; + final Funcs.VoidFunc0 settle = new Funcs.VoidFunc0() { + @Override + public void call() { + if (--pending[0] == 0) { + c.close(); + } + } + }; + // Registered on listen: a stream here does not buffer for a listener that is + // not there yet, and these futures usually complete synchronously. + c.onListen(new Funcs.VoidFunc0() { + @Override + public void call() { + for (Future f : futures) { + pending[0]++; + f.then(new Funcs.VoidFunc1() { + @Override + public void call(T v) { + c.add(v); + settle.call(); + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object e) { + c.addError(e); + settle.call(); + } + }); + } + // The extra count held the stream open while the futures were registered. + settle.call(); + } + }); + return c.stream(); + } + + /** Dart's {@code Stream.periodic}; a null computation yields nulls. */ + public static Stream periodic(final dart.core.Duration period, final Funcs.Func1 computation) { + return AsyncStar.asyncStar(new Funcs.VoidFunc1>() { + @Override + public void call(AsyncStar.Sink s) { + for (long i = 0;; i++) { + Await.await$(Future.delayed(period)); + s.yield$(computation == null ? null : computation.call(Long.valueOf(i))); + } + } + }); + } + + /** A derived stream: {@code body} reads this stream through an iterator and yields. */ + private Stream derive(final Funcs.VoidFunc2, AsyncStar.Sink> body) { + return AsyncStar.over(this, body); + } + + /** Dart's {@code expand}. */ + public final Stream expand(final Funcs.Func1> convert) { + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + while (it.next()) { + // A failure converting or iterating one element is one error event + // and the stream goes on, as Dart's expand does. + List out = new ArrayList(); + try { + Iterable items = convert.call(it.current()); + if (items != null) { + for (R r : items) { + out.add(r); + } + } + } catch (Throwable t) { + s.addError(t); + continue; + } + for (R r : out) { + s.yield$(r); + } + } + } + }); + } + + /** Dart's {@code asyncMap}: a callback returning a Future is awaited before the next event. */ + @SuppressWarnings("unchecked") + public final Stream asyncMap(final Funcs.Func1 convert) { + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + while (it.next()) { + // A failed conversion is one error event and the stream goes on, as + // Dart's asyncMap does; letting it escape would close the stream. + Object r; + try { + r = Await.awaitAny(convert.call(it.current())); + } catch (Throwable t) { + s.addError(t); + continue; + } + s.yield$((R) r); + } + } + }); + } + + /** Dart's {@code take}. */ + public final Stream take(final long count) { + // A negative count is an empty stream, not an error (measured on the Dart VM). + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + for (long i = 0; i < count && it.next(); i++) { + s.yield$(it.current()); + } + } + }); + } + + /** Dart's {@code skip}. */ + public final Stream skip(final long count) { + // Dart rejects a negative argument up front; elementAt(-1) otherwise drained + // the whole source (forever, for an endless one) before failing. + dart.core.RangeError.checkNotNegative(count, "count"); + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + long i = 0; + while (it.next()) { + if (i++ >= count) { + s.yield$(it.current()); + } + } + } + }); + } + + /** Dart's {@code takeWhile}. */ + public final Stream takeWhile(final Funcs.Func1 test) { + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + while (it.next() && test.call(it.current()).booleanValue()) { + s.yield$(it.current()); + } + } + }); + } + + /** Dart's {@code skipWhile}. */ + public final Stream skipWhile(final Funcs.Func1 test) { + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + boolean skipping = true; + while (it.next()) { + if (skipping && test.call(it.current()).booleanValue()) { + continue; + } + skipping = false; + s.yield$(it.current()); + } + } + }); + } + + /** Dart's {@code distinct()}: drops an event equal to the one before it. */ + public final Stream distinct() { + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + boolean first = true; + T last = null; + while (it.next()) { + T v = it.current(); + if (first || !dart.runtime.DartRuntime.eq(v, last)) { + s.yield$(v); + } + first = false; + last = v; + } + } + }); + } + + /** Dart's {@code distinct(equals)}. */ + public final Stream distinct(final Funcs.Func2 equals) { + if (equals == null) { + return distinct(); + } + return derive(new Funcs.VoidFunc2, AsyncStar.Sink>() { + @Override + public void call(StreamIterator it, AsyncStar.Sink s) { + boolean first = true; + T last = null; + while (it.next()) { + T v = it.current(); + if (!first) { + boolean same; + try { + same = equals.call(last, v).booleanValue(); + } catch (AsyncStar.Cancelled c) { + throw c; + } catch (RuntimeException e) { + // Dart turns this one event into an error and goes on, + // keeping the previous value for the next comparison. + s.addError(e); + continue; + } + if (same) { + continue; + } + } + s.yield$(v); + first = false; + last = v; + } + } + }); + } + + /** Dart's {@code handleError}: errors go to the handler and the stream goes on. */ + public final Stream handleError(final Object onError) { + return handleError0(onError, null); + } + + private Stream handleError0(final Object onError, final Object test) { + final Stream self = this; + final StreamController out = new StreamController(); + out.onListen(new Funcs.VoidFunc0() { + @Override + public void call() { + final StreamSubscription sub = self.listen(new Funcs.VoidFunc1() { + @Override + public void call(T v) { + out.add(v); + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object e) { + // handleError transforms: what the test or the handler throws (a + // rethrow or a replacement) is emitted downstream, as Dart does. + try { + if (test == null || dart.runtime.Dyn.invoke(test, e) == Boolean.TRUE) { + callError(onError, e); + } else { + out.addError(e); + } + } catch (Throwable t) { + out.addError(t); + } + } + }, new Funcs.VoidFunc0() { + @Override + public void call() { + out.close(); + } + }); + out.onCancel(new Funcs.VoidFunc0() { + @Override + public void call() { + sub.cancel(); + } + }); + } + }); + return out.stream(); + } + + /** Dart's {@code transform}: a converter such as {@code utf8.decoder} or {@code LineSplitter()}. */ + @SuppressWarnings("unchecked") + public final Stream transform(Object transformer) { + if (transformer instanceof StreamTransformer) { + return ((StreamTransformer) transformer).bind(this); + } + throw new dart.core.UnsupportedError("Not a stream transformer: " + transformer); + } + + /** Dart's {@code cast} with no type the emitter could name (dynamic); nothing to check. */ + @SuppressWarnings("unchecked") + public final Stream cast() { + return (Stream) (Stream) this; + } + + /** + * Dart's {@code cast()}: every data event that is not an {@code R} becomes a + * TypeError error event instead of reaching a listener typed for {@code R}. + * {@code type} is the transpiler's witness for {@code R}; {@code admitsNull} says + * whether {@code R} is nullable (Class.isInstance(null) is false). + */ + @SuppressWarnings("unchecked") + public final Stream cast(final Class type, final boolean admitsNull) { + if (type == null) { + return (Stream) (Stream) this; + } + return map(new Funcs.Func1() { + @Override + public R call(T v) { + if (v == null ? admitsNull : type.isInstance(v)) { + return (R) v; + } + throw new dart.core.TypeError("type '" + (v == null ? "Null" : v.getClass().getName()) + + "' is not a subtype of type '" + type.getName() + "' in type cast"); + } + }); + } + + /** Dart's {@code asBroadcastStream}; every stream here already accepts several listeners. */ + public final Stream asBroadcastStream() { + return this; + } + + /** {@code asBroadcastStream(onListen:, onCancel:)}; the callbacks are not called. */ + public final Stream asBroadcastStream(Object onListen, Object onCancel) { + return this; + } + + /** {@code handleError(onError, test:)}; an error {@code test} rejects is passed on. */ + public final Stream handleError(final Object onError, final Object test) { + return handleError0(onError, test); + } + + /** Dart's {@code join()}. */ + public final Future join() { + return join(""); + } + + /** Dart's {@code isBroadcast}. */ + public boolean isBroadcast() { + return true; + } + + /** + * Listens to the end, folding every event into {@code step}; the future + * completes with {@code finish}'s value, or with the stream's first error. + */ + private Future collect(final Funcs.Func1 step, final Funcs.Func0 finish) { + final Completer c = new Completer(); + final StreamSubscription[] holder = new StreamSubscription[1]; + final boolean[] stopped = new boolean[1]; + holder[0] = listen(new Funcs.VoidFunc1() { + @Override + public void call(T v) { + if (stopped[0]) { + return; + } + try { + if (!step.call(v).booleanValue()) { + stopped[0] = true; + if (holder[0] != null) { + holder[0].cancel(); + } + c.complete(finish.call()); + } + } catch (Throwable t) { + stopped[0] = true; + // The result is decided; stop listening, as the error path does, so an + // endless source is not left subscribed for nothing. + if (holder[0] != null) { + holder[0].cancel(); + } + c.completeError(dart.runtime.DartRuntime.caught(t)); + } + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object e) { + if (!stopped[0]) { + stopped[0] = true; + if (holder[0] != null) { + holder[0].cancel(); + } + c.completeError(e); + } + } + }, new Funcs.VoidFunc0() { + @Override + public void call() { + if (!stopped[0]) { + stopped[0] = true; + try { + c.complete(finish.call()); + } catch (Throwable t) { + c.completeError(dart.runtime.DartRuntime.caught(t)); + } + } + } + }); + if (stopped[0]) { + holder[0].cancel(); + } + return c.future(); + } + + /** Dart's {@code toList}. */ + public final Future> toList() { + final dart.core.DartList out = new dart.core.DartList(); + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + out.add(v); + return Boolean.TRUE; + } + }, new Funcs.Func0>() { + @Override + public dart.core.DartList call() { + return out; + } + }); + } + + /** Dart's {@code toSet}. */ + public final Future> toSet() { + final dart.core.DartSet out = new dart.core.DartSet(); + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + out.add(v); + return Boolean.TRUE; + } + }, new Funcs.Func0>() { + @Override + public dart.core.DartSet call() { + return out; + } + }); + } + + /** Dart's {@code forEach}. */ + public final Future forEach(final Funcs.VoidFunc1 action) { + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + action.call(v); + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public Object call() { + return null; + } + }); + } + + /** Dart's {@code drain}. */ + public final Future drain() { + return forEach(new Funcs.VoidFunc1() { + @Override + public void call(T v) { + } + }); + } + + /** Dart's {@code drain(futureValue)}. */ + public final Future drain(final Object futureValue) { + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public Object call() { + return futureValue; + } + }); + } + + /** Dart's {@code length}. */ + public final Future length() { + final long[] n = new long[1]; + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + n[0]++; + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public Long call() { + return Long.valueOf(n[0]); + } + }); + } + + /** Dart's {@code isEmpty}. */ + public final Future isEmpty() { + final boolean[] any = new boolean[1]; + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + any[0] = true; + return Boolean.FALSE; + } + }, new Funcs.Func0() { + @Override + public Boolean call() { + return Boolean.valueOf(!any[0]); + } + }); + } + + /** Dart's {@code first}: a StateError when the stream is empty. */ + public final Future first() { + final java.util.List seen = new java.util.ArrayList(1); + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + seen.add(v); + return Boolean.FALSE; + } + }, new Funcs.Func0() { + @Override + public T call() { + if (seen.isEmpty()) { + throw new dart.core.StateError("No element"); + } + return seen.get(0); + } + }); + } + + /** Dart's {@code last}: a StateError when the stream is empty. */ + public final Future last() { + final java.util.List seen = new java.util.ArrayList(1); + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + seen.clear(); + seen.add(v); + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public T call() { + if (seen.isEmpty()) { + throw new dart.core.StateError("No element"); + } + return seen.get(0); + } + }); + } + + /** Dart's {@code firstWhere(test, {orElse})}. */ + public final Future firstWhere(final Funcs.Func1 test, final Funcs.Func0 orElse) { + final java.util.List seen = new java.util.ArrayList(1); + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + if (test.call(v).booleanValue()) { + seen.add(v); + return Boolean.FALSE; + } + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public T call() { + if (!seen.isEmpty()) { + return seen.get(0); + } + if (orElse != null) { + return orElse.call(); + } + throw new dart.core.StateError("No element"); + } + }); + } + + /** Dart's {@code any}. */ + public final Future any(final Funcs.Func1 test) { + final boolean[] hit = new boolean[1]; + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + hit[0] = test.call(v).booleanValue(); + return Boolean.valueOf(!hit[0]); + } + }, new Funcs.Func0() { + @Override + public Boolean call() { + return Boolean.valueOf(hit[0]); + } + }); + } + + /** Dart's {@code every}. */ + public final Future every(final Funcs.Func1 test) { + final boolean[] miss = new boolean[1]; + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + miss[0] = !test.call(v).booleanValue(); + return Boolean.valueOf(!miss[0]); + } + }, new Funcs.Func0() { + @Override + public Boolean call() { + return Boolean.valueOf(!miss[0]); + } + }); + } + + /** Dart's {@code contains}. */ + public final Future contains(final Object needle) { + return any(new Funcs.Func1() { + @Override + public Boolean call(T v) { + return Boolean.valueOf(dart.runtime.DartRuntime.eq(v, needle)); + } + }); + } + + /** Dart's {@code join([separator])}. */ + public final Future join(final String separator) { + final StringBuilder sb = new StringBuilder(); + final boolean[] started = new boolean[1]; + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + if (started[0] && separator != null) { + sb.append(separator); + } + started[0] = true; + sb.append(dart.runtime.DartRuntime.str(v)); + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public String call() { + return sb.toString(); + } + }); + } + + /** Dart's {@code fold}. */ + @SuppressWarnings("unchecked") + public final Future fold(final R initial, final Funcs.Func2 combine) { + final Object[] acc = {initial}; + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + acc[0] = (R) combine.call((R) acc[0], v); + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public R call() { + return (R) acc[0]; + } + }); + } + + /** Dart's {@code reduce}. */ + @SuppressWarnings("unchecked") + public final Future reduce(final Funcs.Func2 combine) { + final Object[] acc = new Object[1]; + final boolean[] started = new boolean[1]; + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + acc[0] = started[0] ? (T) combine.call((T) acc[0], v) : v; + started[0] = true; + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public T call() { + if (!started[0]) { + throw new dart.core.StateError("No element"); + } + return (T) acc[0]; + } + }); + } + + /** Dart's {@code elementAt}. */ + public final Future elementAt(final long index) { + // Dart rejects a negative argument up front; elementAt(-1) otherwise drained + // the whole source (forever, for an endless one) before failing. + dart.core.RangeError.checkNotNegative(index, "index"); + final long[] i = new long[1]; + final java.util.List seen = new java.util.ArrayList(1); + return collect(new Funcs.Func1() { + @Override + public Boolean call(T v) { + if (i[0]++ == index) { + seen.add(v); + return Boolean.FALSE; + } + return Boolean.TRUE; + } + }, new Funcs.Func0() { + @Override + public T call() { + if (seen.isEmpty()) { + throw new dart.core.RangeError("Index out of range: " + index); + } + return seen.get(0); + } + }); + } } diff --git a/maven/dart-runtime/src/main/java/dart/async/StreamIterator.java b/maven/dart-runtime/src/main/java/dart/async/StreamIterator.java new file mode 100644 index 00000000000..820486d1c0d --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/async/StreamIterator.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.async; + +import dart.runtime.Funcs; + +import java.util.ArrayDeque; + +/** + * Dart's {@code StreamIterator}, and the lowering target of {@code await for}. + * + *

{@code await for (x in s) body} becomes a loop over {@link #next()}, which parks + * the current frame the way {@link Await#await$} does until the stream delivers an + * event, finishes or fails. Events that arrive while the loop body runs are queued, so + * none is lost. Leaving the loop by any path calls {@link #cancel()} in a finally block, + * which cancels the subscription as Dart does on {@code break} or an exception.

+ */ +public final class StreamIterator { + + private static final Object DONE = new Object(); + /** Stands in for a null event: ArrayDeque rejects null, and a stream may carry one. */ + private static final Object NULL = new Object(); + + private final Stream stream; + private final ArrayDeque queue = new ArrayDeque(); + private StreamSubscription sub; + private Completer waiting; + private volatile T current; + private boolean finished; + private Object error; + private boolean hasError; + + /// Set for a derived stream (Stream.derive): source errors go here, in order, and the + /// iterator stays subscribed. Dart's StreamIterator ends at an error, which is right + /// for await for, but take/skip/expand/asyncMap forward an error and carry on. + private final Funcs.VoidFunc1 forward; + + /** A forwarded error waiting in the queue, in order with the data around it. */ + private static final class Err { + final Object error; + + Err(Object error) { + this.error = error; + } + } + + public StreamIterator(Stream stream) { + this(stream, null); + } + + StreamIterator(Stream stream, Funcs.VoidFunc1 forward) { + this.stream = stream; + this.forward = forward; + } + + public T current() { + return current; + } + + /** Dart's {@code moveNext()}: completes with false once the stream is done. */ + public Future moveNext() { + boolean start; + synchronized (this) { + start = sub == null && !finished; + } + if (start) { + subscribe(); + } + Completer c = new Completer(); + Object outcome; + java.util.List errs = forward == null ? null : new java.util.ArrayList(); + // Checking the queue and publishing the waiter is one step: an async* body + // pushes from a timer thread, and an event landing between the two was lost, + // leaving next() parked forever (a rare hang in lang_async_star under load). + synchronized (this) { + outcome = take(errs); + if (outcome == NOTHING) { + waiting = c; + } + } + forwardAll(errs); + settle(c, outcome); + return c.future(); + } + + /// Subscribes now rather than at the first moveNext; events wait in the queue. + void start() { + boolean go; + synchronized (this) { + go = sub == null && !finished; + } + if (go) { + subscribe(); + } + } + + /** The blocking step {@code await for} is lowered to. */ + public boolean next() { + Boolean more = Await.await$(moveNext()); + return more != null && more.booleanValue(); + } + + public Future cancel() { + Completer w; + StreamSubscription s; + synchronized (this) { + finished = true; + queue.clear(); + // A moveNext still pending ends now, so a body blocked in next() -- a derived + // stream whose listener cancelled -- unblocks instead of waiting for an event. + w = waiting; + waiting = null; + s = sub; + sub = null; + } + if (w != null && !w.isCompleted()) { + w.complete(Boolean.FALSE); + } + if (s != null) { + return s.cancel(); + } + return Future.value(null); + } + + private void subscribe() { + StreamSubscription created = stream.listen(new Funcs.VoidFunc1() { + @Override + public void call(T v) { + push(v); + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object e) { + if (forward != null) { + push(new Err(e)); + return; + } + synchronized (StreamIterator.this) { + if (!hasError) { + hasError = true; + error = e; + } + } + push(DONE); + } + }, new Funcs.VoidFunc0() { + @Override + public void call() { + push(DONE); + } + // cancelOnError only for await for; a forwarding iterator outlives an error + }, Boolean.valueOf(forward == null)); + boolean stale; + synchronized (this) { + stale = finished; + if (!stale) { + sub = created; + } + } + if (stale) { + // cancelled, or already done, while listen ran: nothing should stay subscribed + created.cancel(); + } + } + + private void push(Object event) { + Completer c; + Object outcome; + java.util.List errs = forward == null ? null : new java.util.ArrayList(); + synchronized (this) { + queue.addLast(event == null ? NULL : event); + c = waiting; + if (c == null) { + return; + } + outcome = take(errs); + if (outcome != NOTHING) { + waiting = null; + } + } + forwardAll(errs); + settle(c, outcome); + } + + /** No event yet: the caller keeps waiting. */ + private static final Object NOTHING = new Object(); + /** The stream ended (or was cancelled): moveNext answers false. */ + private static final Object END = new Object(); + + /// The next outcome for a moveNext, taken off the queue. Called holding the lock; + /// the completer is settled after it is released, since completing runs listeners. + @SuppressWarnings("unchecked") + private Object take(java.util.List errs) { + while (errs != null && !queue.isEmpty() && queue.peekFirst() instanceof Err) { + errs.add(((Err) queue.removeFirst()).error); + } + if (queue.isEmpty()) { + return finished ? END : NOTHING; + } + Object e = queue.removeFirst(); + if (e == DONE) { + finished = true; + queue.clear(); + sub = null; + if (hasError) { + hasError = false; + Object err = error; + return new Object[] {err}; + } + return END; + } + current = e == NULL ? null : (T) e; + return Boolean.TRUE; + } + + /// Hands drained errors to the derived stream, outside the lock and before the + /// event that followed them settles the waiter, so the order is the source's. + private void forwardAll(java.util.List errs) { + if (errs != null) { + for (Object e : errs) { + forward.call(e); + } + } + } + + private static void settle(Completer c, Object outcome) { + if (outcome == NOTHING) { + return; + } + if (outcome == END) { + c.complete(Boolean.FALSE); + } else if (outcome instanceof Object[]) { + c.completeError(((Object[]) outcome)[0]); + } else { + c.complete(Boolean.TRUE); + } + } +} diff --git a/maven/dart-runtime/src/main/java/dart/async/StreamTransformer.java b/maven/dart-runtime/src/main/java/dart/async/StreamTransformer.java new file mode 100644 index 00000000000..95a6b7e2025 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/async/StreamTransformer.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.async; + +/** + * Dart's {@code StreamTransformer}: what {@link Stream#transform} applies. The + * converters of dart:convert ({@code utf8.decoder}, {@code LineSplitter()}, + * {@code json.decoder}) implement it, so {@code response.transform(utf8.decoder)} works. + */ +public interface StreamTransformer { + Stream bind(Stream stream); +} diff --git a/maven/dart-runtime/src/main/java/dart/async/TimeoutException.java b/maven/dart-runtime/src/main/java/dart/async/TimeoutException.java new file mode 100644 index 00000000000..e40c6ed2fdd --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/async/TimeoutException.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.async; + +/// `dart:async`'s TimeoutException: an asynchronous result did not arrive within +/// the time allowed. `duration` is the limit that expired, or null. +public class TimeoutException extends RuntimeException { + private final Object duration; + + public TimeoutException(String message, Object duration) { + super(message); + this.duration = duration; + } + + public String message() { + return getMessage(); + } + + public Object duration() { + return duration; + } + + @Override + public String toString() { + String m = getMessage(); + return m == null ? "TimeoutException" : "TimeoutException: " + m; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/ByteCodec.java b/maven/dart-runtime/src/main/java/dart/convert/ByteCodec.java new file mode 100644 index 00000000000..f7ff61befcf --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/ByteCodec.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +import dart.core.DartList; +import dart.core.FormatException; +import dart.typed_data.Uint8List; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +/** + * The byte codecs of {@code dart:convert}: {@code utf8}, {@code ascii}, {@code latin1}, + * {@code base64} and {@code base64Url}. A byte input may be a {@link Uint8List} or any + * {@code List}; byte outputs are {@link Uint8List}, as in Dart. + */ +public final class ByteCodec implements dart.async.StreamTransformer { + + static final int UTF8 = 0; + static final int ASCII = 1; + static final int LATIN1 = 2; + static final int BASE64 = 3; + static final int BASE64_URL = 4; + + private final int kind; + + ByteCodec(int kind) { + this.kind = kind; + } + + public String name() { + switch (kind) { + case UTF8: + return "utf-8"; + case ASCII: + return "us-ascii"; + case LATIN1: + return "iso-8859-1"; + default: + return "base64"; + } + } + + /** Text codecs: String to bytes. Base64: bytes to String. */ + public Object encode(Object input) { + if (kind == BASE64 || kind == BASE64_URL) { + return base64(bytesOf(input), kind == BASE64_URL); + } + String s = (String) input; + return Uint8List.fromBytes(textBytes(s)); + } + + /** Text codecs: bytes to String. Base64: String to bytes. */ + public Object decode(Object input) { + return decode(input, null); + } + + public Object decode(Object input, Boolean allowMalformed) { + if (kind == BASE64 || kind == BASE64_URL) { + return Uint8List.fromBytes(unbase64(dart.runtime.DartRuntime.as(input, String.class, false, "String"))); + } + byte[] b = bytesOf(input); + if (kind == UTF8 && !Boolean.TRUE.equals(allowMalformed)) { + checkUtf8(b, b.length); + } + return text(b, true); + } + + /** + * Dart's decoder throws FormatException on malformed UTF-8 unless allowMalformed; + * Java's String constructor would silently substitute U+FFFD instead. + */ + static void checkUtf8(byte[] b, int end) { + int i = 0; + while (i < end) { + int v = b[i] & 0xff; + int n; + int min; + if (v < 0x80) { + i++; + continue; + } else if (v >= 0xc2 && v <= 0xdf) { + n = 1; + min = 0x80; + } else if (v >= 0xe0 && v <= 0xef) { + n = 2; + min = 0x800; + } else if (v >= 0xf0 && v <= 0xf4) { + n = 3; + min = 0x10000; + } else { + throw new FormatException("Unexpected extension byte (at offset " + i + ")"); + } + if (i + n >= end) { + throw new FormatException("Unfinished UTF-8 octet sequence (at offset " + i + ")"); + } + int cp = v & (0x3f >> n); + for (int k = 1; k <= n; k++) { + int c = b[i + k] & 0xff; + if ((c & 0xc0) != 0x80) { + throw new FormatException("Missing extension byte (at offset " + (i + k) + ")"); + } + cp = (cp << 6) | (c & 0x3f); + } + if (cp < min || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) { + throw new FormatException("Invalid UTF-8 encoding (at offset " + i + ")"); + } + i += n + 1; + } + } + + /** + * {@code stream.transform(utf8.decoder)}: chunk by chunk. A UTF-8 sequence split + * across two chunks is held back until the rest of it arrives. + */ + @Override + public dart.async.Stream bind(final dart.async.Stream source) { + // AsyncStar.over: subscribed inside listen() (a broadcast source's next event is + // not lost) and source errors forwarded without ending the stream, as Dart's do. + return dart.async.AsyncStar.over(source, new dart.runtime.Funcs.VoidFunc2, + dart.async.AsyncStar.Sink>() { + @Override + public void call(dart.async.StreamIterator it, dart.async.AsyncStar.Sink sink) { + byte[] carry = new byte[0]; + // Only the BOM at the very start of the stream is dropped; one opening a + // later chunk is a real U+FEFF in the middle of the text. + boolean atStart = true; + // Base64 works in whole groups -- three bytes encode to four characters -- + // so a chunk's incomplete group is carried into the next one and finished + // only when the stream closes; per-chunk padding would corrupt the output. + boolean b64 = kind == BASE64 || kind == BASE64_URL; + StringBuilder textCarry = new StringBuilder(); + try { + while (it.next()) { + Object chunk = it.current(); + if (b64 && chunk instanceof String) { + textCarry.append((String) chunk); + // Padding only ends the input, so text holding '=' waits for the end. + int cut = textCarry.indexOf("=") >= 0 ? 0 : textCarry.length() - textCarry.length() % 4; + if (cut > 0) { + String head = textCarry.substring(0, cut); + textCarry.delete(0, cut); + sink.yield$(convert(head)); + } + continue; + } + if (b64) { + byte[] in = bytesOf(chunk); + byte[] all = new byte[carry.length + in.length]; + System.arraycopy(carry, 0, all, 0, carry.length); + System.arraycopy(in, 0, all, carry.length, in.length); + int cut = all.length - all.length % 3; + carry = new byte[all.length - cut]; + System.arraycopy(all, cut, carry, 0, carry.length); + if (cut > 0) { + byte[] head = new byte[cut]; + System.arraycopy(all, 0, head, 0, cut); + sink.yield$(base64(head, kind == BASE64_URL)); + } + continue; + } + if (kind != UTF8 || chunk instanceof String) { + sink.yield$(convert(chunk)); + continue; + } + byte[] in = bytesOf(chunk); + byte[] all = new byte[carry.length + in.length]; + System.arraycopy(carry, 0, all, 0, carry.length); + System.arraycopy(in, 0, all, carry.length, in.length); + int cut = completeUtf8Prefix(all); + byte[] head = new byte[cut]; + System.arraycopy(all, 0, head, 0, cut); + carry = new byte[all.length - cut]; + System.arraycopy(all, cut, carry, 0, carry.length); + if (cut > 0) { + // Strict as decode() is: Java's decoder would substitute U+FFFD. + checkUtf8(head, head.length); + sink.yield$(text(head, atStart)); + atStart = false; + } + } + if (b64) { + if (textCarry.length() > 0) { + sink.yield$(convert(textCarry.toString())); + } + if (carry.length > 0) { + sink.yield$(base64(carry, kind == BASE64_URL)); + } + return; + } + if (carry.length > 0) { + // An incomplete sequence left at the end of the stream is malformed. + checkUtf8(carry, carry.length); + sink.yield$(text(carry, atStart)); + } + } finally { + it.cancel(); + } + } + }); + } + + /** The length of the longest prefix of {@code b} that ends on a whole UTF-8 sequence. */ + static int completeUtf8Prefix(byte[] b) { + int n = b.length; + int i = n - 1; + int back = 0; + while (i >= 0 && back < 3 && (b[i] & 0xC0) == 0x80) { + i--; + back++; + } + if (i < 0) { + return n; + } + int lead = b[i] & 0xFF; + int need = lead >= 0xF0 ? 4 : lead >= 0xE0 ? 3 : lead >= 0xC0 ? 2 : 1; + return n - i >= need ? n : i; + } + + public ByteCodec encoder() { + return this; + } + + public ByteCodec decoder() { + return this; + } + + /** {@code utf8.encoder.convert(s)} / {@code utf8.decoder.convert(b)}, by input type. */ + public Object convert(Object input) { + boolean textIn = input instanceof String; + if (kind == BASE64 || kind == BASE64_URL) { + return textIn ? decode(input) : encode(input); + } + return textIn ? encode(input) : decode(input); + } + + private byte[] textBytes(String s) { + try { + switch (kind) { + case UTF8: + return s.getBytes("UTF-8"); + default: + byte[] b = new byte[s.length()]; + int limit = kind == ASCII ? 0x7f : 0xff; + for (int i = 0; i < b.length; i++) { + char c = s.charAt(i); + if (c > limit) { + throw new dart.core.ArgumentError("Contains invalid characters."); + } + b[i] = (byte) c; + } + return b; + } + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + private String text(byte[] b, boolean atStart) { + try { + if (kind == UTF8) { + int off = atStart && b.length >= 3 && (b[0] & 0xff) == 0xef && (b[1] & 0xff) == 0xbb + && (b[2] & 0xff) == 0xbf ? 3 : 0; + return new String(b, off, b.length - off, "UTF-8"); + } + char[] c = new char[b.length]; + for (int i = 0; i < b.length; i++) { + int v = b[i] & 0xff; + if (kind == ASCII && v > 0x7f) { + throw new FormatException("Invalid value in input: " + v); + } + c[i] = (char) v; + } + return new String(c); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + public static byte[] bytesOf(Object input) { + if (input instanceof Uint8List) { + return ((Uint8List) input).toBytes(); + } + if (input instanceof byte[]) { + return (byte[]) input; + } + if (input instanceof List) { + List l = (List) input; + byte[] b = new byte[l.size()]; + for (int i = 0; i < b.length; i++) { + Object o = l.get(i); + // a List element: a double (65.5 from JSON) is a TypeError in Dart, + // never truncated into a valid byte + if (!(o instanceof Long || o instanceof Integer || o instanceof Short || o instanceof Byte)) { + throw new dart.core.TypeError("Expected a List"); + } + b[i] = (byte) ((Number) o).intValue(); + } + return b; + } + throw new dart.core.TypeError("Expected a List"); + } + + private static final String STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private static final String URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + static String base64(byte[] b, boolean url) { + String alpha = url ? URL : STD; + StringBuilder sb = new StringBuilder((b.length + 2) / 3 * 4); + for (int i = 0; i < b.length; i += 3) { + int n = (b[i] & 0xff) << 16; + if (i + 1 < b.length) { + n |= (b[i + 1] & 0xff) << 8; + } + if (i + 2 < b.length) { + n |= b[i + 2] & 0xff; + } + sb.append(alpha.charAt((n >> 18) & 63)).append(alpha.charAt((n >> 12) & 63)); + sb.append(i + 1 < b.length ? alpha.charAt((n >> 6) & 63) : '='); + sb.append(i + 2 < b.length ? alpha.charAt(n & 63) : '='); + } + return sb.toString(); + } + + /** Accepts both alphabets, and missing padding, as Dart's decoder does. */ + static byte[] unbase64(String s) { + checkBase64(s); + byte[] out = new byte[s.length() * 3 / 4 + 3]; + int n = 0; + int acc = 0; + int bits = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + int v; + if (c >= 'A' && c <= 'Z') { + v = c - 'A'; + } else if (c >= 'a' && c <= 'z') { + v = c - 'a' + 26; + } else if (c >= '0' && c <= '9') { + v = c - '0' + 52; + } else if (c == '+' || c == '-') { + v = 62; + } else if (c == '/' || c == '_') { + v = 63; + } else if (c == '=') { + break; + } else { + throw new FormatException("Invalid character (at character " + (i + 1) + ")"); + } + acc = (acc << 6) | v; + bits += 6; + if (bits >= 8) { + bits -= 8; + out[n++] = (byte) (acc >> bits); + } + } + byte[] r = new byte[n]; + System.arraycopy(out, 0, r, 0, n); + return r; + } + + /** + * Dart's decoder rejects what this one used to truncate silently: a length that is + * not a multiple of four, padding that does not complete the last quartet, + * anything after the padding, and set bits left over before it. Messages and + * positions follow the Dart VM's. + */ + private static void checkBase64(String s) { + int len = s.length(); + int pad = s.indexOf('='); + int data = pad < 0 ? len : pad; + if (pad >= 0) { + int need = data % 4 == 2 ? 2 : data % 4 == 3 ? 1 : 0; + int have = 0; + while (pad + have < len && s.charAt(pad + have) == '=') { + have++; + } + if (need == 0 || pad + have < len || have > need) { + throw new FormatException("Invalid padding character (at character " + (pad + Math.min(have, need) + 1) + + ")"); + } + if (have < need) { + throw new FormatException("Missing padding character (at character " + (len + 1) + ")"); + } + } else if (len % 4 != 0) { + // Deliberately strict, and checked against the Dart VM: base64.decode and + // base64Url.decode both reject unpadded "QQ" and "QUI" with exactly this + // message. Dart code decoding unpadded input (a JWT segment) calls + // base64.normalize first, which pads it. + throw new FormatException("Invalid length, must be multiple of four (at character " + (len + 1) + ")"); + } + if (data % 4 != 0) { + // The bits of the last character that do not reach a whole byte must be zero. + char last = s.charAt(data - 1); + int v = last >= 'A' && last <= 'Z' ? last - 'A' : last >= 'a' && last <= 'z' ? last - 'a' + 26 + : last >= '0' && last <= '9' ? last - '0' + 52 : last == '+' || last == '-' ? 62 : 63; + int unused = data % 4 == 2 ? 0x0f : 0x03; + if ((v & unused) != 0) { + throw new FormatException("Invalid encoding before padding (at character " + (data + 1) + ")"); + } + } + } + + /** {@code LineSplitter.split(s)} and {@code const LineSplitter().convert(s)}. */ + public static DartList splitLines(String s) { + DartList out = new DartList(); + int start = 0; + int i = 0; + while (i < s.length()) { + char c = s.charAt(i); + if (c == '\n' || c == '\r') { + out.add(s.substring(start, i)); + if (c == '\r' && i + 1 < s.length() && s.charAt(i + 1) == '\n') { + i++; + } + start = i + 1; + } + i++; + } + if (start < s.length()) { + out.add(s.substring(start)); + } + return out; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/Convert.java b/maven/dart-runtime/src/main/java/dart/convert/Convert.java new file mode 100644 index 00000000000..70b5a6766b5 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/Convert.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +import dart.core.DartList; +import dart.typed_data.Uint8List; + +/** The top-level values and functions of {@code dart:convert}. */ +public final class Convert { + + private Convert() { + } + + public static final JsonCodec json = new JsonCodec(); + public static final ByteCodec utf8 = new ByteCodec(ByteCodec.UTF8); + public static final ByteCodec ascii = new ByteCodec(ByteCodec.ASCII); + public static final ByteCodec latin1 = new ByteCodec(ByteCodec.LATIN1); + public static final ByteCodec base64 = new ByteCodec(ByteCodec.BASE64); + public static final ByteCodec base64Url = new ByteCodec(ByteCodec.BASE64_URL); + + public static Object jsonDecode(String source) { + return json.decode(source, null); + } + + public static Object jsonDecode(String source, Object reviver) { + return json.decode(source, reviver); + } + + public static String jsonEncode(Object value) { + return json.encode(value, null); + } + + public static String jsonEncode(Object value, Object toEncodable) { + return json.encode(value, toEncodable); + } + + public static String base64Encode(Object bytes) { + return ByteCodec.base64(ByteCodec.bytesOf(bytes), false); + } + + public static String base64UrlEncode(Object bytes) { + return ByteCodec.base64(ByteCodec.bytesOf(bytes), true); + } + + public static Uint8List base64Decode(String source) { + return Uint8List.fromBytes(ByteCodec.unbase64(source)); + } + + public static DartList splitLines(String s) { + return ByteCodec.splitLines(s); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/JsonCodec.java b/maven/dart-runtime/src/main/java/dart/convert/JsonCodec.java new file mode 100644 index 00000000000..d89e502b64f --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/JsonCodec.java @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +import dart.core.DartList; +import dart.core.DartMap; +import dart.core.FormatException; +import dart.runtime.DartRuntime; +import dart.runtime.Funcs; + +import java.util.Map; + +/** + * Dart's JSON codec ({@code dart:convert}): {@code jsonDecode} builds + * {@link DartMap}/{@link DartList} trees with {@code Long} integers and {@code Double} + * fractions, as Dart's decoder yields int and double; {@code jsonEncode} writes maps, + * iterables, strings, numbers, booleans and null, and calls {@code toJson()} on anything + * else (see {@link JsonEncodable}). + */ +public final class JsonCodec { + + private final Object reviver; + private final Object toEncodable; + + public JsonCodec() { + this(null, null); + } + + public JsonCodec(Object reviver, Object toEncodable) { + this.reviver = reviver; + this.toEncodable = toEncodable; + } + + public Object decode(String source) { + return decode(source, reviver); + } + + public Object decode(String source, Object reviver) { + Object v = new Parser(source).parseTop(); + if (reviver != null) { + v = revive(null, v, reviver); + } + return v; + } + + public String encode(Object value) { + return encode(value, toEncodable); + } + + public String encode(Object value, Object toEncodable) { + StringBuilder sb = new StringBuilder(); + write(sb, value, toEncodable, null, "", 0); + return sb.toString(); + } + + public JsonEncoder encoder() { + return new JsonEncoder(toEncodable); + } + + public JsonDecoder decoder() { + return new JsonDecoder(reviver); + } + + @SuppressWarnings("unchecked") + private static Object revive(Object key, Object value, Object reviver) { + if (value instanceof DartMap) { + DartMap m = (DartMap) value; + for (Map.Entry e : m.entrySet()) { + e.setValue(revive(e.getKey(), e.getValue(), reviver)); + } + } else if (value instanceof DartList) { + DartList l = (DartList) value; + for (int i = 0; i < l.size(); i++) { + l.set(i, revive(Long.valueOf(i), l.get(i), reviver)); + } + } + if (reviver instanceof Funcs.Func2) { + return ((Funcs.Func2) reviver).call(key, value); + } + return value; + } + + @SuppressWarnings("unchecked") + static void write(StringBuilder sb, Object v, Object toEncodable, String indent, String cur, int depth) { + if (depth > 1000) { + throw new JsonUnsupportedObjectError(v, "Cyclic or too deeply nested structure"); + } + if (v == null) { + sb.append("null"); + } else if (v instanceof String) { + quote(sb, (String) v); + } else if (v instanceof Boolean) { + sb.append(((Boolean) v).booleanValue() ? "true" : "false"); + } else if (v instanceof Double || v instanceof Float) { + double d = ((Number) v).doubleValue(); + if (Double.isNaN(d) || Double.isInfinite(d)) { + throw new JsonUnsupportedObjectError(v, "Unsupported number"); + } + sb.append(DartRuntime.doubleStr(d)); + } else if (v instanceof Number) { + sb.append(((Number) v).longValue()); + } else if (v instanceof Map) { + Map m = (Map) v; + if (m.isEmpty()) { + sb.append("{}"); + return; + } + sb.append('{'); + String inner = indent == null ? null : cur + indent; + boolean first = true; + for (Map.Entry e : m.entrySet()) { + if (!first) { + sb.append(','); + } + first = false; + if (inner != null) { + sb.append('\n').append(inner); + } + if (!(e.getKey() instanceof String)) { + throw new JsonUnsupportedObjectError(v, "Map keys must be strings"); + } + quote(sb, (String) e.getKey()); + sb.append(inner != null ? ": " : ":"); + write(sb, e.getValue(), toEncodable, indent, inner == null ? "" : inner, depth + 1); + } + if (inner != null) { + sb.append('\n').append(cur); + } + sb.append('}'); + } else if (v instanceof Iterable) { + String inner = indent == null ? null : cur + indent; + sb.append('['); + boolean first = true; + for (Object o : (Iterable) v) { + if (!first) { + sb.append(','); + } + first = false; + if (inner != null) { + sb.append('\n').append(inner); + } + write(sb, o, toEncodable, indent, inner == null ? "" : inner, depth + 1); + } + if (inner != null && !first) { + sb.append('\n').append(cur); + } + sb.append(']'); + } else { + // Enums (Java enums here) go this way too: toEncodable or a toJson can + // encode one, and only without either is it unsupported, as in Dart. + Object converted; + if (toEncodable instanceof Funcs.Func1) { + converted = ((Funcs.Func1) toEncodable).call(v); + } else if (v instanceof JsonEncodable) { + converted = ((JsonEncodable) v).toJson(); + } else { + throw new JsonUnsupportedObjectError(v, "Converting object to an encodable object failed"); + } + write(sb, converted, toEncodable, indent, cur, depth + 1); + } + } + + private static void quote(StringBuilder sb, String s) { + sb.append('"'); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + case '\b': + sb.append("\\b"); + break; + case '\f': + sb.append("\\f"); + break; + default: + if (c < 0x20) { + String hex = Integer.toHexString(c); + sb.append("\\u"); + for (int k = hex.length(); k < 4; k++) { + sb.append('0'); + } + sb.append(hex); + } else { + sb.append(c); + } + } + } + sb.append('"'); + } + + /** A strict RFC 8259 parser; a malformed document is a FormatException, as in Dart. */ + /// JSON numbers are ASCII digits only; Character.isDigit also took other scripts' + /// decimal digits, which Long.parseLong then accepted, so "1" + ARABIC-INDIC TWO read as 12. + static boolean isAsciiDigit(char c) { + return c >= '0' && c <= '9'; + } + + private static final class Parser { + private final String s; + private int i; + + Parser(String s) { + if (s == null) { + throw new dart.core.TypeError("Null is not a subtype of String"); + } + this.s = s; + } + + Object parseTop() { + skipWs(); + Object v = value(); + skipWs(); + if (i != s.length()) { + fail("Unexpected character"); + } + return v; + } + + private void fail(String msg) { + throw new FormatException(msg + " (at character " + (i + 1) + ")"); + } + + private void skipWs() { + while (i < s.length()) { + char c = s.charAt(i); + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') { + i++; + } else { + break; + } + } + } + + private Object value() { + if (i >= s.length()) { + fail("Unexpected end of input"); + } + char c = s.charAt(i); + switch (c) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case 't': + literal("true"); + return Boolean.TRUE; + case 'f': + literal("false"); + return Boolean.FALSE; + case 'n': + literal("null"); + return null; + default: + if (c == '-' || (c >= '0' && c <= '9')) { + return number(); + } + fail("Unexpected character"); + return null; + } + } + + private void literal(String word) { + if (!s.startsWith(word, i)) { + fail("Unexpected character"); + } + i += word.length(); + } + + private DartMap object() { + DartMap m = new DartMap(); + i++; + skipWs(); + if (i < s.length() && s.charAt(i) == '}') { + i++; + return m; + } + while (true) { + skipWs(); + if (i >= s.length() || s.charAt(i) != '"') { + fail("Expected a string key"); + } + String k = string(); + skipWs(); + if (i >= s.length() || s.charAt(i) != ':') { + fail("Expected ':'"); + } + i++; + skipWs(); + m.put(k, value()); + skipWs(); + if (i >= s.length()) { + fail("Unexpected end of input"); + } + char c = s.charAt(i++); + if (c == '}') { + return m; + } + if (c != ',') { + i--; + fail("Expected ',' or '}'"); + } + } + } + + private DartList array() { + DartList l = new DartList(); + i++; + skipWs(); + if (i < s.length() && s.charAt(i) == ']') { + i++; + return l; + } + while (true) { + skipWs(); + l.add(value()); + skipWs(); + if (i >= s.length()) { + fail("Unexpected end of input"); + } + char c = s.charAt(i++); + if (c == ']') { + return l; + } + if (c != ',') { + i--; + fail("Expected ',' or ']'"); + } + } + } + + private String string() { + i++; + StringBuilder sb = null; + int start = i; + while (true) { + if (i >= s.length()) { + fail("Unterminated string"); + } + char c = s.charAt(i); + if (c == '"') { + String out = sb == null ? s.substring(start, i) : sb.append(s, start, i).toString(); + i++; + return out; + } + if (c < 0x20) { + fail("Control character in string"); + } + if (c == '\\') { + if (sb == null) { + sb = new StringBuilder(); + } + sb.append(s, start, i); + i++; + if (i >= s.length()) { + fail("Unterminated string"); + } + char e = s.charAt(i++); + switch (e) { + case '"': + case '\\': + case '/': + sb.append(e); + break; + case 'b': + sb.append('\b'); + break; + case 'f': + sb.append('\f'); + break; + case 'n': + sb.append('\n'); + break; + case 'r': + sb.append('\r'); + break; + case 't': + sb.append('\t'); + break; + case 'u': + if (i + 4 > s.length()) { + fail("Invalid unicode escape"); + } + int code = 0; + for (int k = 0; k < 4; k++) { + int d = Character.digit(s.charAt(i + k), 16); + if (d < 0) { + fail("Invalid unicode escape"); + } + code = code * 16 + d; + } + sb.append((char) code); + i += 4; + break; + default: + i--; + fail("Invalid escape"); + } + start = i; + } else { + i++; + } + } + } + + private Object number() { + int start = i; + boolean fraction = false; + if (s.charAt(i) == '-') { + i++; + } + if (i >= s.length() || !isAsciiDigit(s.charAt(i))) { + fail("Invalid number"); + } + if (s.charAt(i) == '0') { + i++; + } else { + while (i < s.length() && isAsciiDigit(s.charAt(i))) { + i++; + } + } + if (i < s.length() && s.charAt(i) == '.') { + fraction = true; + i++; + if (i >= s.length() || !isAsciiDigit(s.charAt(i))) { + fail("Invalid number"); + } + while (i < s.length() && isAsciiDigit(s.charAt(i))) { + i++; + } + } + if (i < s.length() && (s.charAt(i) == 'e' || s.charAt(i) == 'E')) { + fraction = true; + i++; + if (i < s.length() && (s.charAt(i) == '+' || s.charAt(i) == '-')) { + i++; + } + if (i >= s.length() || !isAsciiDigit(s.charAt(i))) { + fail("Invalid number"); + } + while (i < s.length() && isAsciiDigit(s.charAt(i))) { + i++; + } + } + String text = s.substring(start, i); + if (!fraction) { + // Dart's decoder yields an int when the literal fits, else a double. + if (text.length() < 19) { + return Long.valueOf(Long.parseLong(text)); + } + try { + return Long.valueOf(Long.parseLong(text)); + } catch (NumberFormatException overflow) { + return Double.valueOf(Double.parseDouble(text)); + } + } + return Double.valueOf(Double.parseDouble(text)); + } + } +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/JsonDecoder.java b/maven/dart-runtime/src/main/java/dart/convert/JsonDecoder.java new file mode 100644 index 00000000000..53e41594dde --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/JsonDecoder.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +/** {@code JsonDecoder}. */ +public final class JsonDecoder implements dart.async.StreamTransformer { + private final Object reviver; + + public JsonDecoder() { + this(null); + } + + public JsonDecoder(Object reviver) { + this.reviver = reviver; + } + + public Object convert(String input) { + return new JsonCodec().decode(input, reviver); + } + + /** {@code stream.transform(json.decoder)}: the whole text, decoded once it has all arrived. */ + @Override + public dart.async.Stream bind(final dart.async.Stream source) { + return dart.async.AsyncStar.asyncStar(new dart.runtime.Funcs.VoidFunc1>() { + @Override + public void call(dart.async.AsyncStar.Sink sink) { + sink.yield$(convert(dart.async.Await.await$(source.join("")))); + } + }); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/JsonEncodable.java b/maven/dart-runtime/src/main/java/dart/convert/JsonEncodable.java new file mode 100644 index 00000000000..e2b9eefab08 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/JsonEncodable.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +/** + * Implemented by every transpiled class that declares {@code toJson()}, so + * {@code jsonEncode} can call it the way Dart does dynamically, with no reflection. + */ +public interface JsonEncodable { + Object toJson(); +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/JsonEncoder.java b/maven/dart-runtime/src/main/java/dart/convert/JsonEncoder.java new file mode 100644 index 00000000000..4bb5e0f2427 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/JsonEncoder.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +/** {@code JsonEncoder} and {@code JsonEncoder.withIndent}. */ +public final class JsonEncoder { + private final Object toEncodable; + private final String indent; + + public JsonEncoder() { + this(null); + } + + public JsonEncoder(Object toEncodable) { + this(null, toEncodable); + } + + private JsonEncoder(String indent, Object toEncodable) { + this.indent = indent; + this.toEncodable = toEncodable; + } + + public static JsonEncoder withIndent(String indent) { + return new JsonEncoder(indent, null); + } + + public static JsonEncoder withIndent(String indent, Object toEncodable) { + return new JsonEncoder(indent, toEncodable); + } + + public String convert(Object value) { + StringBuilder sb = new StringBuilder(); + JsonCodec.write(sb, value, toEncodable, indent, "", 0); + return sb.toString(); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/JsonUnsupportedObjectError.java b/maven/dart-runtime/src/main/java/dart/convert/JsonUnsupportedObjectError.java new file mode 100644 index 00000000000..43277ba6e26 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/JsonUnsupportedObjectError.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +/** {@code JsonUnsupportedObjectError}: jsonEncode met a value it cannot encode. */ +public class JsonUnsupportedObjectError extends RuntimeException { + private static final long serialVersionUID = 1L; + private final transient Object unsupportedObject; + + public JsonUnsupportedObjectError(Object unsupportedObject, String message) { + super(message); + this.unsupportedObject = unsupportedObject; + } + + public Object unsupportedObject() { + return unsupportedObject; + } + + @Override + public String toString() { + return "Converting object to an encodable object failed: " + unsupportedObject; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/convert/LineSplitter.java b/maven/dart-runtime/src/main/java/dart/convert/LineSplitter.java new file mode 100644 index 00000000000..de9205c4a58 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/convert/LineSplitter.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.convert; + +import dart.core.DartList; + +/** {@code LineSplitter}: splits on \n, \r\n and \r, dropping the terminators. */ +public final class LineSplitter implements dart.async.StreamTransformer { + + public static DartList split(String lines) { + return ByteCodec.splitLines(lines); + } + + public DartList convert(String data) { + return ByteCodec.splitLines(data); + } + + /** {@code stream.transform(const LineSplitter())}: a line split across chunks is joined. */ + @Override + public dart.async.Stream bind(final dart.async.Stream source) { + // AsyncStar.over: subscribed inside listen() (a broadcast source's next event is + // not lost) and source errors forwarded without ending the stream, as Dart's do. + return dart.async.AsyncStar.over(source, new dart.runtime.Funcs.VoidFunc2, + dart.async.AsyncStar.Sink>() { + @Override + public void call(dart.async.StreamIterator it, dart.async.AsyncStar.Sink sink) { + StringBuilder pending = new StringBuilder(); + try { + while (it.next()) { + String chunk = it.current(); + if (chunk == null) { + continue; + } + pending.append(chunk); + int start = 0; + for (int i = 0; i < pending.length(); i++) { + char c = pending.charAt(i); + if (c == '\n' || c == '\r') { + if (c == '\r' && i + 1 == pending.length()) { + // A CR at the end of a chunk may be half of a CRLF. + break; + } + sink.yield$(pending.substring(start, i)); + if (c == '\r' && pending.charAt(i + 1) == '\n') { + i++; + } + start = i + 1; + } + } + pending.delete(0, start); + } + String rest = pending.toString(); + if (rest.endsWith("\r")) { + rest = rest.substring(0, rest.length() - 1); + sink.yield$(rest); + } else if (rest.length() > 0) { + sink.yield$(rest); + } + } finally { + it.cancel(); + } + } + }); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/core/DartUri.java b/maven/dart-runtime/src/main/java/dart/core/DartUri.java index 1afabc30c6c..b2d9a67e882 100644 --- a/maven/dart-runtime/src/main/java/dart/core/DartUri.java +++ b/maven/dart-runtime/src/main/java/dart/core/DartUri.java @@ -842,7 +842,7 @@ public DartMap queryParameters() { /// Dart's {@code Uri.decodeComponent}, for path segments: a {@code +} is a /// literal plus. - private static String decodeComponent(String s) { + public static String decodeComponent(String s) { return percentDecode(s, false); } diff --git a/maven/dart-runtime/src/main/java/dart/core/DateTime.java b/maven/dart-runtime/src/main/java/dart/core/DateTime.java index e70ee476a9a..3dee4848bb5 100644 --- a/maven/dart-runtime/src/main/java/dart/core/DateTime.java +++ b/maven/dart-runtime/src/main/java/dart/core/DateTime.java @@ -65,6 +65,89 @@ public static DateTime utc(long year, long month, long day, long hour, long minu millisecond, true)), microsecond), true); } + /// Dart's own grammar for DateTime.parse: an ISO 8601 date, an optional time + /// after `T` or a space, an optional fraction, and an optional `Z` or offset. + private static final java.util.regex.Pattern ISO = java.util.regex.Pattern.compile( + "^([+-]?\\d{4,6})-?(\\d\\d)-?(\\d\\d)(?:[ T](\\d\\d)(?::?(\\d\\d)(?::?(\\d\\d)(?:[.,](\\d+))?)?)?" + + "( ?[zZ]| ?([-+])(\\d\\d)(?::?(\\d\\d))?)?)?$"); + + /// `DateTime.parse`: a value with a `Z` or an offset is UTC, anything else local. + public static DateTime parse(String formattedString) { + DateTime d = tryParse(formattedString); + if (d == null) { + throw new FormatException("Invalid date format\n" + formattedString); + } + return d; + } + + public static DateTime tryParse(String formattedString) { + if (formattedString == null) { + return null; + } + java.util.regex.Matcher m = ISO.matcher(formattedString); + if (!m.matches()) { + return null; + } + long year = Long.parseLong(m.group(1).startsWith("+") ? m.group(1).substring(1) : m.group(1)); + long month = Long.parseLong(m.group(2)); + long day = Long.parseLong(m.group(3)); + long hour = num(m.group(4)); + long minute = num(m.group(5)); + long second = num(m.group(6)); + long milli = 0; + long micro = 0; + String frac = m.group(7); + if (frac != null) { + String six = (frac + "000000").substring(0, 6); + long all = Long.parseLong(six); + // Digits past the sixth are dropped, not rounded, as the Dart VM does: + // .9999999 stays in the same second. + milli = all / 1000; + micro = all % 1000; + } + // A well-formed date outside DateTime's range is still unparseable: tryParse + // answers null (and parse a FormatException), never the constructor's error. + try { + if (m.group(8) == null) { + return new DateTime(year, month, day, hour, minute, second, milli, micro); + } + DateTime u = utc(year, month, day, hour, minute, second, milli, micro); + if (m.group(9) != null) { + long offset = num(m.group(10)) * 60 + num(m.group(11)); + long sign = m.group(9).equals("-") ? -1 : 1; + long at = u.epochMicros - sign * offset * 60000000L; + checkMicrosRange(at); + return new DateTime(at, true); + } + return u; + } catch (ArgumentError outOfRange) { + return null; + } + } + + private static long num(String s) { + return s == null ? 0 : Long.parseLong(s); + } + + /// `toIso8601String`: `2024-01-15T10:30:00.000`, `Z` added for a UTC value. + public String toIso8601String() { + long y = year(); + String ys = y >= -9999 && y <= 9999 ? fourDigits(y) : (y < 0 ? "-" : "+") + sixDigits(Math.abs(y)); + long micros = microsecond(); + String text = ys + "-" + twoDigits(month()) + "-" + twoDigits(day()) + + "T" + twoDigits(hour()) + ":" + twoDigits(minute()) + ":" + twoDigits(second()) + + "." + threeDigits(millisecond()) + (micros == 0 ? "" : threeDigits(micros)); + return utc ? text + "Z" : text; + } + + private static String sixDigits(long n) { + String s = Long.toString(n); + while (s.length() < 6) { + s = "0" + s; + } + return s; + } + public static DateTime fromMillisecondsSinceEpoch(long millisecondsSinceEpoch, boolean isUtc) { return new DateTime(millisToMicros(millisecondsSinceEpoch), isUtc); } diff --git a/maven/dart-runtime/src/main/java/dart/core/NoSuchMethodError.java b/maven/dart-runtime/src/main/java/dart/core/NoSuchMethodError.java new file mode 100644 index 00000000000..6a12824a3f6 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/core/NoSuchMethodError.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.core; + +/** Dart's {@code NoSuchMethodError}: a dynamic access named no member of the receiver. */ +public class NoSuchMethodError extends RuntimeException { + private static final long serialVersionUID = 1L; + + public NoSuchMethodError(String message) { + super(message); + } + + @Override + public String toString() { + return "NoSuchMethodError: " + getMessage(); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/ContentType.java b/maven/dart-runtime/src/main/java/dart/io/ContentType.java new file mode 100644 index 00000000000..fb3a31dceb8 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/ContentType.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +/** Dart's {@code ContentType}: a MIME type with an optional charset. */ +public final class ContentType { + public static final ContentType text = new ContentType("text", "plain", "utf-8"); + public static final ContentType html = new ContentType("text", "html", "utf-8"); + public static final ContentType json = new ContentType("application", "json", "utf-8"); + public static final ContentType binary = new ContentType("application", "octet-stream", null); + + private final String primaryType; + private final String subType; + private final String charset; + /** Parameters other than charset (a multipart boundary), serialized after it. */ + private final java.util.Map parameters = new java.util.LinkedHashMap(); + + public ContentType(String primaryType, String subType) { + this(primaryType, subType, null); + } + + public ContentType(String primaryType, String subType, String charset) { + this.primaryType = primaryType; + this.subType = subType; + this.charset = charset; + } + + /** {@code ContentType(primary, sub, charset:, parameters:)}. */ + public ContentType(String primaryType, String subType, String charset, Object parameters) { + this(primaryType, subType, charset); + if (parameters instanceof java.util.Map) { + for (java.util.Map.Entry e : ((java.util.Map) parameters).entrySet()) { + String k = String.valueOf(e.getKey()); + if (!"charset".equalsIgnoreCase(k)) { + this.parameters.put(k, e.getValue() == null ? null : String.valueOf(e.getValue())); + } + } + } + } + + public java.util.Map parameters() { + return parameters; + } + + /** Dart's {@code ContentType.parse}: {@code type/subtype; charset=x}. */ + public static ContentType parse(String value) { + String[] parts = value.split(";"); + String mime = parts[0].trim(); + int slash = mime.indexOf('/'); + String cs = null; + java.util.Map params = new java.util.LinkedHashMap(); + for (int i = 1; i < parts.length; i++) { + String p = parts[i].trim(); + if (p.regionMatches(true, 0, "charset=", 0, 8)) { + cs = p.substring(8).trim(); + if (cs.length() > 1 && cs.startsWith("\"") && cs.endsWith("\"")) { + cs = cs.substring(1, cs.length() - 1); + } + } else if (p.length() > 0) { + int eq = p.indexOf('='); + params.put(eq < 0 ? p : p.substring(0, eq).trim(), eq < 0 ? null : p.substring(eq + 1).trim()); + } + } + return slash < 0 ? new ContentType(mime, "", cs, params) + : new ContentType(mime.substring(0, slash), mime.substring(slash + 1), cs, params); + } + + public String primaryType() { + return primaryType; + } + + public String subType() { + return subType; + } + + public String mimeType() { + return primaryType + "/" + subType; + } + + public String charset() { + return charset; + } + + public String value() { + return mimeType(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(mimeType()); + if (charset != null) { + sb.append("; charset=").append(charset); + } + for (java.util.Map.Entry e : parameters.entrySet()) { + sb.append("; ").append(e.getKey()); + if (e.getValue() != null) { + sb.append('=').append(e.getValue()); + } + } + return sb.toString(); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/Directory.java b/maven/dart-runtime/src/main/java/dart/io/Directory.java new file mode 100644 index 00000000000..fb3684c9b10 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/Directory.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import dart.async.Future; +import dart.async.Stream; +import dart.core.DartList; + +/** Dart's {@code Directory}. */ +public class Directory extends FileSystemEntity { + + /** Dart's {@code Directory.systemTemp}: the app's cache directory on a device. */ + public static final Directory systemTemp = temp(); + + /** Dart's {@code Directory.current}: the app's home directory unless the app sets it. */ + public static Directory current = home(); + + public Directory(String path) { + super(path); + } + + private static Directory temp() { + if (com.codename1.ui.Display.isInitialized()) { + com.codename1.io.FileSystemStorage fss = com.codename1.io.FileSystemStorage.getInstance(); + String dir = fss.hasCachesDir() ? fss.getCachesDir() : fss.getAppHomePath(); + return new Directory(Fs.trimSlash(Fs.Storage.plain(dir))); + } + return new Directory("/tmp"); + } + + private static Directory home() { + if (com.codename1.ui.Display.isInitialized()) { + return new Directory(Fs.trimSlash(Fs.Storage.plain( + com.codename1.io.FileSystemStorage.getInstance().getAppHomePath()))); + } + return new Directory("/"); + } + + public Directory absolute() { + return isAbsolute() ? this : new Directory(absolutePath(path)); + } + + public void createSync() { + createSync(null); + } + + public void createSync(Boolean recursive) { + Fs fs = Fs.get(); + if (fs.exists(path)) { + if (!fs.isDirectory(path)) { + // Dart fails when another entity occupies the path; answering a + // Directory that was never created only moves the failure. + throw new FileSystemException("Creation failed", path); + } + return; + } + if (recursive != null && recursive.booleanValue()) { + fs.mkdirs(path); + } else { + if (!fs.exists(Fs.parentOf(path))) { + throw new FileSystemException("Creation failed", path); + } + fs.mkdir(path); + } + } + + public Future create() { + return create(null); + } + + public Future create(Boolean recursive) { + try { + createSync(recursive); + return Future.value(this); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public DartList listSync() { + return listSync(null, null); + } + + public DartList listSync(Boolean recursive, Boolean followLinks) { + Fs fs = Fs.get(); + if (!fs.isDirectory(path)) { + throw new FileSystemException("Directory listing failed", path); + } + DartList out = new DartList(); + for (String child : fs.list(path)) { + if (fs.isDirectory(child)) { + Directory d = new Directory(child); + out.add(d); + if (recursive != null && recursive.booleanValue()) { + out.addAll(d.listSync(recursive, followLinks)); + } + } else { + out.add(new File(child)); + } + } + return out; + } + + public Stream list() { + return list(null, null); + } + + public Stream list(Boolean recursive, Boolean followLinks) { + try { + return Stream.fromIterable(listSync(recursive, followLinks)); + } catch (RuntimeException e) { + return Stream.error(e); + } + } + + public Directory renameSync(String newPath) { + Fs fs = Fs.get(); + String from = Fs.trimSlash(absolutePath(path)); + String to = Fs.trimSlash(absolutePath(newPath)); + if (to.equals(from)) { + return new Directory(newPath); + } + if (to.startsWith(from + "/")) { + // The emulated copy would create the target inside the tree it is + // enumerating and recurse into it forever; the OS rejects this move too. + throw new FileSystemException("Rename failed", path); + } + if (fs.exists(newPath) && (!fs.isDirectory(newPath) || !new Directory(newPath).listSync().isEmpty())) { + // Dart fails onto a non-empty directory (or a file) on every platform; the + // emulated move below would merge into it and overwrite same-named files. + throw new FileSystemException("Rename failed", path); + } + fs.mkdirs(newPath); + for (FileSystemEntity e : listSync()) { + String target = Fs.trimSlash(newPath) + "/" + Fs.nameOf(e.path); + if (e instanceof Directory) { + ((Directory) e).renameSync(target); + } else { + ((File) e).renameSync(target); + } + } + fs.delete(path); + return new Directory(newPath); + } + + public Future rename(String newPath) { + try { + return Future.value(renameSync(newPath)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public Directory createTempSync() { + return createTempSync(null); + } + + public Directory createTempSync(String prefix) { + String base = Fs.trimSlash(path) + "/" + (prefix == null ? "" : prefix); + long n = System.currentTimeMillis(); + while (Fs.get().exists(base + n)) { + n++; + } + Directory d = new Directory(base + n); + d.createSync(Boolean.TRUE); + return d; + } + + public Future createTemp() { + return createTemp(null); + } + + public Future createTemp(String prefix) { + try { + return Future.value(createTempSync(prefix)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + @Override + public String toString() { + return "Directory: '" + path + "'"; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/File.java b/maven/dart-runtime/src/main/java/dart/io/File.java new file mode 100644 index 00000000000..8b41e01d363 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/File.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import dart.async.Future; +import dart.convert.ByteCodec; +import dart.convert.Convert; +import dart.core.DartList; +import dart.typed_data.Uint8List; + +/** + * Dart's {@code File}. The asynchronous methods do their I/O before returning a completed + * future, which is what the app observes once it awaits them; failures arrive through the + * future as a {@link FileSystemException}, never thrown from the call. + */ +public class File extends FileSystemEntity { + + public File(String path) { + super(path); + } + + /** Dart's {@code File.fromUri}. */ + public static File fromUri(dart.core.DartUri uri) { + return new File(uriToFilePath(uri)); + } + + public File absolute() { + return isAbsolute() ? this : new File(absolutePath(path)); + } + + private static ByteCodec codec(Object encoding) { + return encoding instanceof ByteCodec ? (ByteCodec) encoding : Convert.utf8; + } + + // ---------------------------------------------------------------- read + + public Uint8List readAsBytesSync() { + return Uint8List.fromBytes(Fs.get().read(path)); + } + + public Future readAsBytes() { + try { + return Future.value(readAsBytesSync()); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public String readAsStringSync() { + return readAsStringSync(null); + } + + public String readAsStringSync(Object encoding) { + return (String) codec(encoding).decode(readAsBytesSync()); + } + + public Future readAsString() { + return readAsString(null); + } + + public Future readAsString(Object encoding) { + try { + return Future.value(readAsStringSync(encoding)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public DartList readAsLinesSync() { + return readAsLinesSync(null); + } + + public DartList readAsLinesSync(Object encoding) { + return dart.convert.LineSplitter.split(readAsStringSync(encoding)); + } + + public Future> readAsLines() { + return readAsLines(null); + } + + public Future> readAsLines(Object encoding) { + try { + return Future.value(readAsLinesSync(encoding)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + /** Dart's {@code openRead()}: the content as one chunk. */ + public dart.async.Stream openRead() { + return openRead(null, null); + } + + public dart.async.Stream openRead(Long start, Long end) { + try { + // Dart rejects a negative bound or an end before the start; clamping them + // made a bad range look like an empty file. + if (start != null && start.longValue() < 0) { + throw new dart.core.RangeError("Invalid value: start must be non-negative: " + start); + } + if (end != null && (end.longValue() < 0 || end.longValue() < (start == null ? 0 : start.longValue()))) { + throw new dart.core.RangeError("Invalid value: end must not be before start: " + end); + } + byte[] all = Fs.get().read(path); + int s = start == null ? 0 : (int) Math.min(all.length, start.longValue()); + int e = end == null ? all.length : (int) Math.min(all.length, end.longValue()); + byte[] part = new byte[Math.max(0, e - s)]; + System.arraycopy(all, s, part, 0, part.length); + return dart.async.Stream.value(Uint8List.fromBytes(part)); + } catch (RuntimeException ex) { + return dart.async.Stream.error(ex); + } + } + + // ---------------------------------------------------------------- write + + private static boolean appends(Object mode) { + return mode instanceof FileMode && ((FileMode) mode).append_; + } + + public void writeAsBytesSync(Object bytes) { + writeAsBytesSync(bytes, null, null); + } + + public void writeAsBytesSync(Object bytes, Object mode, Boolean flush) { + Fs.get().write(path, ByteCodec.bytesOf(bytes), appends(mode)); + } + + public Future writeAsBytes(Object bytes) { + return writeAsBytes(bytes, null, null); + } + + public Future writeAsBytes(Object bytes, Object mode, Boolean flush) { + try { + writeAsBytesSync(bytes, mode, flush); + return Future.value(this); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public void writeAsStringSync(String contents) { + writeAsStringSync(contents, null, null, null); + } + + public void writeAsStringSync(String contents, Object mode, Object encoding, Boolean flush) { + Fs.get().write(path, ByteCodec.bytesOf(codec(encoding).encode(contents)), appends(mode)); + } + + public Future writeAsString(String contents) { + return writeAsString(contents, null, null, null); + } + + public Future writeAsString(String contents, Object mode, Object encoding, Boolean flush) { + try { + writeAsStringSync(contents, mode, encoding, flush); + return Future.value(this); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + // ---------------------------------------------------------------- other + + public void createSync() { + createSync(null, null); + } + + public void createSync(Boolean recursive, Boolean exclusive) { + Fs fs = Fs.get(); + if (fs.exists(path)) { + if (fs.isDirectory(path)) { + // a directory is not a file that already exists: Dart fails here + throw new FileSystemException("Cannot create file, path is a directory", path); + } + if (exclusive != null && exclusive.booleanValue()) { + throw new FileSystemException("Cannot create file, it already exists", path); + } + return; + } + if (recursive != null && recursive.booleanValue()) { + fs.mkdirs(Fs.parentOf(path)); + } + fs.write(path, new byte[0], false); + } + + public Future create() { + return create(null, null); + } + + public Future create(Boolean recursive, Boolean exclusive) { + try { + createSync(recursive, exclusive); + return Future.value(this); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public long lengthSync() { + return Fs.get().length(path); + } + + public Future length() { + try { + return Future.value(Long.valueOf(lengthSync())); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public dart.core.DateTime lastModifiedSync() { + return dart.core.DateTime.fromMillisecondsSinceEpoch(Fs.get().lastModified(path), false); + } + + public Future lastModified() { + try { + return Future.value(lastModifiedSync()); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public File copySync(String newPath) { + Fs.get().write(newPath, Fs.get().read(path), false); + return new File(newPath); + } + + public Future copy(String newPath) { + try { + return Future.value(copySync(newPath)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public File renameSync(String newPath) { + Fs fs = Fs.get(); + if (absolutePath(newPath).equals(absolutePath(path))) { + // Renaming onto itself is a no-op; the copy-and-delete below would delete it. + return new File(newPath); + } + fs.write(newPath, fs.read(path), false); + fs.delete(path); + return new File(newPath); + } + + public Future rename(String newPath) { + try { + return Future.value(renameSync(newPath)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + @Override + public String toString() { + return "File: '" + path + "'"; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/FileMode.java b/maven/dart-runtime/src/main/java/dart/io/FileMode.java new file mode 100644 index 00000000000..7896634dc10 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/FileMode.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +/** Dart's {@code FileMode}; only whether a write appends matters here. */ +public final class FileMode { + public static final FileMode read = new FileMode(false); + public static final FileMode write = new FileMode(false); + public static final FileMode append = new FileMode(true); + public static final FileMode writeOnly = new FileMode(false); + public static final FileMode writeOnlyAppend = new FileMode(true); + + final boolean append_; + + private FileMode(boolean append) { + this.append_ = append; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/FileSystemEntity.java b/maven/dart-runtime/src/main/java/dart/io/FileSystemEntity.java new file mode 100644 index 00000000000..b6004226555 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/FileSystemEntity.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import dart.async.Future; + +/** Dart's {@code FileSystemEntity}: the part {@link File} and {@link Directory} share. */ +public abstract class FileSystemEntity { + + final String path; + + FileSystemEntity(String path) { + if (path == null) { + throw new dart.core.ArgumentError("path must not be null"); + } + this.path = path; + } + + public String path() { + return path; + } + + public dart.core.DartUri uri() { + // Escaped first: a literal '#', '?' or '%' in a file name is part of the name, and + // parsing it raw would turn it into a fragment, a query or an escape (a/b#c, a%20b). + if (path.startsWith("file://")) { + return dart.core.DartUri.parse("file://" + escapePath(path.substring(7))); + } + return dart.core.DartUri.parse(path.startsWith("/") ? "file://" + escapePath(path) : escapePath(path)); + } + + /// Percent-encodes everything but RFC 3986 unreserved characters and '/', as UTF-8. + static String escapePath(String p) { + StringBuilder sb = new StringBuilder(p.length()); + byte[] bytes; + try { + bytes = p.getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + throw new IllegalStateException(e.toString()); + } + String hex = "0123456789ABCDEF"; + for (byte b : bytes) { + int c = b & 0xff; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || c == '-' || c == '.' || c == '_' || c == '~' || c == '/') { + sb.append((char) c); + } else { + sb.append('%').append(hex.charAt(c >> 4)).append(hex.charAt(c & 15)); + } + } + return sb.toString(); + } + + public boolean existsSync() { + return Fs.get().exists(path); + } + + public Future exists() { + return Future.value(Boolean.valueOf(existsSync())); + } + + public Directory parent() { + return new Directory(Fs.parentOf(path)); + } + + /// The path {@code absolute} answers: a relative one joined to Directory.current, + /// the same base every read and write resolves it against. + /// Dart's {@code uri.toFilePath()} as {@code File.fromUri} uses it: only a file + /// (or scheme-less) URI names a file, and its path is percent-decoded, so + /// file:///tmp/a%20b is "/tmp/a b" rather than a file literally named a%20b. + static String uriToFilePath(dart.core.DartUri uri) { + String scheme = uri.scheme(); + if (scheme.length() != 0 && !"file".equals(scheme)) { + throw new dart.core.UnsupportedError("Cannot extract a file path from a " + scheme + " URI"); + } + if (uri.query().length() != 0) { + throw new dart.core.UnsupportedError("Cannot extract a file path from a URI with a query component"); + } + if (uri.fragment().length() != 0) { + throw new dart.core.UnsupportedError("Cannot extract a file path from a URI with a fragment component"); + } + return dart.core.DartUri.decodeComponent(uri.path()); + } + + static String absolutePath(String path) { + if (path.startsWith("/") || path.startsWith("file:")) { + return path; + } + String base = Directory.current.path(); + return (base.endsWith("/") ? base : base + "/") + path; + } + + public boolean isAbsolute() { + return path.startsWith("/") || path.startsWith("file:"); + } + + public void deleteSync() { + deleteSync(Boolean.FALSE); + } + + public void deleteSync(Boolean recursive) { + Fs fs = Fs.get(); + if (!fs.exists(path)) { + throw new FileSystemException("Deletion failed", path); + } + if (recursive != null && recursive.booleanValue() && fs.isDirectory(path)) { + for (String child : fs.list(path)) { + if (fs.isDirectory(child)) { + new Directory(child).deleteSync(Boolean.TRUE); + } else { + fs.delete(child); + } + } + } + fs.delete(path); + } + + public Future delete() { + return delete(Boolean.FALSE); + } + + public Future delete(Boolean recursive) { + try { + deleteSync(recursive); + return Future.value(this); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + /** Dart's {@code FileSystemEntity.isDirectorySync(path)}. */ + public static boolean isDirectorySync(String path) { + return Fs.get().isDirectory(path); + } + + public static Future isDirectory(String path) { + return Future.value(Boolean.valueOf(isDirectorySync(path))); + } + + public static boolean isFileSync(String path) { + Fs fs = Fs.get(); + return fs.exists(path) && !fs.isDirectory(path); + } + + public static Future isFile(String path) { + return Future.value(Boolean.valueOf(isFileSync(path))); + } + + @Override + public boolean equals(Object o) { + return o != null && o.getClass() == getClass() && ((FileSystemEntity) o).path.equals(path); + } + + @Override + public int hashCode() { + return path.hashCode(); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/FileSystemException.java b/maven/dart-runtime/src/main/java/dart/io/FileSystemException.java new file mode 100644 index 00000000000..7e7e147d2a1 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/FileSystemException.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +/** Dart's {@code FileSystemException}. */ +public class FileSystemException extends IOException { + private static final long serialVersionUID = 1L; + + private final String path; + + public FileSystemException(String message, String path) { + super(message); + this.path = path; + } + + public FileSystemException(String message) { + this(message, null); + } + + public FileSystemException() { + this("", null); + } + + public String path() { + return path; + } + + @Override + public String toString() { + return "FileSystemException: " + getMessage() + (path == null ? "" : ", path = '" + path + "'"); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/Fs.java b/maven/dart-runtime/src/main/java/dart/io/Fs.java new file mode 100644 index 00000000000..e4dd9fd62f3 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/Fs.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import com.codename1.io.FileSystemStorage; +import com.codename1.io.Util; +import com.codename1.ui.Display; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Where dart:io's files live. On a device -- and in the simulator -- that is + * {@link FileSystemStorage}; Dart's plain paths are turned into its {@code file://} + * URLs and back. Before Codename One is initialized (the transpiler's own tests run + * transpiled code headless) an in-memory file system stands in, so code that writes + * and reads back a file still behaves. + */ +abstract class Fs { + + private static Fs memory; + + static Fs get() { + if (Display.isInitialized()) { + return Storage.INSTANCE; + } + synchronized (Fs.class) { + if (memory == null) { + memory = new Memory(); + } + return memory; + } + } + + abstract boolean exists(String path); + + abstract boolean isDirectory(String path); + + abstract void mkdir(String path); + + abstract void delete(String path); + + /** Full paths of the direct children of a directory. */ + abstract List list(String path); + + abstract byte[] read(String path); + + abstract void write(String path, byte[] data, boolean append); + + abstract long length(String path); + + abstract long lastModified(String path); + + static String parentOf(String path) { + String p = trimSlash(path); + int i = p.lastIndexOf('/'); + if (i < 0) { + return "."; + } + return i == 0 ? "/" : p.substring(0, i); + } + + static String nameOf(String path) { + String p = trimSlash(path); + return p.substring(p.lastIndexOf('/') + 1); + } + + static String trimSlash(String path) { + String p = path; + while (p.length() > 1 && p.endsWith("/")) { + p = p.substring(0, p.length() - 1); + } + return p; + } + + /** + * Creates {@code path} and every missing parent. An existing component that is + * a file, or a directory that could not be created, is a FileSystemException: + * nothing can live under a file, and the device backend's mkdir reports no + * failure of its own. + */ + void mkdirs(String path) { + if (path.equals("/") || path.equals(".")) { + return; + } + if (exists(path)) { + if (!isDirectory(path)) { + throw new FileSystemException("Creation failed", path); + } + return; + } + mkdirs(parentOf(path)); + mkdir(path); + if (!isDirectory(path)) { + throw new FileSystemException("Creation failed", path); + } + } + + /** FileSystemStorage, with Dart paths mapped to file:// URLs. */ + static final class Storage extends Fs { + static final Storage INSTANCE = new Storage(); + + private static FileSystemStorage fss() { + return FileSystemStorage.getInstance(); + } + + static String url(String path) { + if (path.startsWith("file:")) { + return path; + } + if (path.startsWith("/")) { + return "file://" + path; + } + // Dart resolves a relative path against Directory.current (the app home + // here); FileSystemStorage would use the process working directory, which + // on Android is not even writable. + String base = Directory.current.path(); + return "file://" + (base.endsWith("/") ? base : base + "/") + path; + } + + static String plain(String url) { + return url.startsWith("file://") ? url.substring(7) : url; + } + + @Override + boolean exists(String path) { + return fss().exists(url(path)); + } + + @Override + boolean isDirectory(String path) { + return fss().isDirectory(url(path)); + } + + @Override + void mkdir(String path) { + fss().mkdir(url(path)); + } + + @Override + void delete(String path) { + fss().delete(url(path)); + } + + @Override + List list(String path) { + List out = new ArrayList(); + try { + String base = trimSlash(path); + String[] names = fss().listFiles(url(base)); + if (names != null) { + for (String n : names) { + String name = n.endsWith("/") ? n.substring(0, n.length() - 1) : n; + out.add(plain(base.endsWith("/") ? base + name : base + "/" + name)); + } + } + } catch (java.io.IOException e) { + throw new FileSystemException("Directory listing failed: " + e.getMessage(), path); + } + return out; + } + + @Override + byte[] read(String path) { + InputStream in = null; + try { + in = fss().openInputStream(url(path)); + return Util.readInputStream(in); + } catch (java.io.IOException e) { + throw new FileSystemException("Cannot open file: " + e.getMessage(), path); + } finally { + Util.cleanup(in); + } + } + + @Override + void write(String path, byte[] data, boolean append) { + OutputStream out = null; + try { + String u = url(path); + out = append && fss().exists(u) ? fss().openOutputStream(u, (int) fss().getLength(u)) + : fss().openOutputStream(u); + out.write(data); + } catch (java.io.IOException e) { + throw new FileSystemException("Cannot write file: " + e.getMessage(), path); + } finally { + Util.cleanup(out); + } + } + + @Override + long length(String path) { + return fss().getLength(url(path)); + } + + @Override + long lastModified(String path) { + if (!exists(path)) { + throw new FileSystemException("Cannot retrieve modification time", path); + } + return fss().getLastModified(url(path)); + } + } + + /** A process-local file system for code that runs before Codename One starts. */ + static final class Memory extends Fs { + private final Map files = new HashMap(); + private final Map times = new HashMap(); + private final java.util.Set dirs = new java.util.HashSet(); + + Memory() { + dirs.add("/"); + } + + private static String key(String path) { + // Relative paths resolve against Directory.current, as the device-backed + // Storage does, so File('a.txt') and its absolute() are one entry. + return trimSlash(Storage.plain(FileSystemEntity.absolutePath(path))); + } + + @Override + synchronized boolean exists(String path) { + String k = key(path); + return files.containsKey(k) || dirs.contains(k); + } + + @Override + synchronized boolean isDirectory(String path) { + return dirs.contains(key(path)); + } + + @Override + synchronized void mkdir(String path) { + dirs.add(key(path)); + } + + @Override + synchronized void delete(String path) { + String k = key(path); + files.remove(k); + dirs.remove(k); + } + + @Override + synchronized List list(String path) { + String k = key(path); + String prefix = k.endsWith("/") ? k : k + "/"; + List out = new ArrayList(); + for (String f : files.keySet()) { + if (f.startsWith(prefix) && f.indexOf('/', prefix.length()) < 0) { + out.add(f); + } + } + for (String d : dirs) { + if (d.startsWith(prefix) && d.length() > prefix.length() && d.indexOf('/', prefix.length()) < 0) { + out.add(d); + } + } + java.util.Collections.sort(out); + return out; + } + + @Override + synchronized byte[] read(String path) { + byte[] b = files.get(key(path)); + if (b == null) { + throw new FileSystemException("Cannot open file", path); + } + return b.clone(); + } + + @Override + synchronized void write(String path, byte[] data, boolean append) { + String k = key(path); + if (!dirs.contains(parentOf(k))) { + throw new FileSystemException("Cannot open file", path); + } + byte[] old = append ? files.get(k) : null; + if (old == null) { + files.put(k, data.clone()); + } else { + byte[] all = new byte[old.length + data.length]; + System.arraycopy(old, 0, all, 0, old.length); + System.arraycopy(data, 0, all, old.length, data.length); + files.put(k, all); + } + times.put(k, Long.valueOf(System.currentTimeMillis())); + } + + @Override + synchronized long length(String path) { + byte[] b = files.get(key(path)); + if (b == null) { + throw new FileSystemException("Cannot retrieve length of file", path); + } + return b.length; + } + + @Override + synchronized long lastModified(String path) { + // A missing file is an error, as in Dart, never the epoch. + Long t = times.get(key(path)); + if (t == null || !files.containsKey(key(path))) { + throw new FileSystemException("Cannot retrieve modification time", path); + } + return t.longValue(); + } + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/HttpClient.java b/maven/dart-runtime/src/main/java/dart/io/HttpClient.java new file mode 100644 index 00000000000..589a77cae70 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/HttpClient.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import dart.async.Future; +import dart.core.DartUri; +import dart.core.Duration; + +/** + * Dart's {@code HttpClient}, over Codename One's {@code ConnectionRequest}. A request is + * sent when the app closes it ({@code await request.close()}), and the response body + * arrives whole, as one chunk of its stream -- what {@code package:http} and dio's + * {@code IOHttpClientAdapter} read it as. + */ +public class HttpClient { + + private Duration connectionTimeout; + private Duration idleTimeout = Duration.of(0, 0, 0, 15, 0, 0); + private String userAgent = "Dart/3.0 (dart:io)"; + private boolean autoUncompress = true; + private boolean closed; + /// Requests opened and not yet settled, so close(force: true) can abort them. + private final java.util.List open = new java.util.ArrayList(); + private Object badCertificateCallback; + + public HttpClient() { + } + + /** {@code HttpClient(context: ...)}: the security context is the platform's. */ + public HttpClient(Object context) { + } + + public Duration connectionTimeout() { + return connectionTimeout; + } + + public void connectionTimeout(Duration d) { + connectionTimeout = d; + } + + public Duration idleTimeout() { + return idleTimeout; + } + + public void idleTimeout(Duration d) { + idleTimeout = d; + } + + public String userAgent() { + return userAgent; + } + + public void userAgent(String ua) { + userAgent = ua; + } + + public boolean autoUncompress() { + return autoUncompress; + } + + public void autoUncompress(boolean v) { + autoUncompress = v; + } + + /** Accepted and kept; the platform's own certificate checks still apply. */ + public void badCertificateCallback(Object callback) { + badCertificateCallback = callback; + } + + public Object badCertificateCallback() { + return badCertificateCallback; + } + + public void maxConnectionsPerHost(Long n) { + } + + public void findProxy(Object f) { + } + + public Future openUrl(String method, DartUri url) { + if (closed) { + return Future.error(new StateErrorClosed()); + } + HttpClientRequest r = new HttpClientRequest(this, method, url); + for (int i = open.size() - 1; i >= 0; i--) { + if (open.get(i).finished()) { + open.remove(i); + } + } + open.add(r); + if (userAgent != null) { + r.headers().set(HttpHeaders.userAgentHeader, userAgent); + } + if (autoUncompress) { + r.headers().set(HttpHeaders.acceptEncodingHeader, "gzip"); + } + return Future.value(r); + } + + public Future getUrl(DartUri url) { + return openUrl("GET", url); + } + + public Future postUrl(DartUri url) { + return openUrl("POST", url); + } + + public Future putUrl(DartUri url) { + return openUrl("PUT", url); + } + + public Future patchUrl(DartUri url) { + return openUrl("PATCH", url); + } + + public Future deleteUrl(DartUri url) { + return openUrl("DELETE", url); + } + + public Future headUrl(DartUri url) { + return openUrl("HEAD", url); + } + + public Future open(String method, String host, long port, String path) { + return openUrl(method, DartUri.parse("http://" + host + ":" + port + path)); + } + + public Future get(String host, long port, String path) { + return open("GET", host, port, path); + } + + public Future post(String host, long port, String path) { + return open("POST", host, port, path); + } + + public void close() { + close(null); + } + + public void close(Boolean force) { + closed = true; + if (force != null && force.booleanValue()) { + // Dart terminates active connections on a forced close and fails their + // futures; otherwise a queued request could still reach the server. + HttpClientRequest[] active = open.toArray(new HttpClientRequest[open.size()]); + open.clear(); + for (HttpClientRequest r : active) { + r.abort(new HttpException("HttpClient was forcefully closed", r.uri()), null); + } + } + } + + /** Dart's StateError for a request on a closed client. */ + private static final class StateErrorClosed extends dart.core.StateError { + private static final long serialVersionUID = 1L; + + StateErrorClosed() { + super("Client is closed"); + } + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/HttpClientRequest.java b/maven/dart-runtime/src/main/java/dart/io/HttpClientRequest.java new file mode 100644 index 00000000000..6c9812d442e --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/HttpClientRequest.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import com.codename1.io.ConnectionRequest; +import com.codename1.io.NetworkManager; +import com.codename1.ui.Display; +import dart.async.Completer; +import dart.async.Future; +import dart.convert.ByteCodec; +import dart.core.DartUri; +import dart.core.Duration; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +/** Dart's {@code HttpClientRequest}: headers and body collected until {@link #close}. */ +public class HttpClientRequest { + + private final HttpClient client; + private final String method; + private final DartUri uri; + private final HttpHeaders headers = new HttpHeaders(); + private final ByteArrayOutputStream body = new ByteArrayOutputStream(); + private boolean followRedirects = true; + private long maxRedirects = 5; + private long contentLength = -1; + private Object encoding; + private Completer done; + /// The request in flight once close() queued it, so abort() can kill it. + private ConnectionRequest sent; + + HttpClientRequest(HttpClient client, String method, DartUri uri) { + this.client = client; + this.method = method; + this.uri = uri; + } + + public String method() { + return method; + } + + public DartUri uri() { + return uri; + } + + public HttpHeaders headers() { + return headers; + } + + public boolean followRedirects() { + return followRedirects; + } + + public void followRedirects(boolean v) { + followRedirects = v; + } + + public long maxRedirects() { + return maxRedirects; + } + + public void maxRedirects(long v) { + maxRedirects = v; + } + + public long contentLength() { + return contentLength; + } + + public void contentLength(long v) { + contentLength = v; + } + + public boolean persistentConnection() { + return true; + } + + public void persistentConnection(boolean v) { + } + + public Object encoding() { + return encoding == null ? dart.convert.Convert.utf8 : encoding; + } + + public void encoding(Object e) { + encoding = e; + } + + /** Dart's {@code add(List)}. */ + public void add(Object bytes) { + byte[] b = ByteCodec.bytesOf(bytes); + body.write(b, 0, b.length); + } + + /** Dart's {@code write(Object)}: the value's text, in the request's encoding. */ + public void write(Object obj) { + Object enc = encoding(); + String s = dart.runtime.DartRuntime.str(obj); + add(enc instanceof ByteCodec ? ((ByteCodec) enc).encode(s) : dart.convert.Convert.utf8.encode(s)); + } + + public void writeln(Object obj) { + write(obj); + write("\n"); + } + + public void writeln() { + write("\n"); + } + + /** Dart's {@code addStream}: every chunk is appended. */ + public Future addStream(dart.async.Stream stream) { + return stream.forEach(new dart.runtime.Funcs.VoidFunc1() { + @Override + public void call(Object chunk) { + add(chunk); + } + }); + } + + public Future flush() { + return Future.value(null); + } + + public void abort() { + abort(null, null); + } + + public void abort(Object exception, Object stackTrace) { + // Terminal either way: before close() it settles `done` so close() never sends; + // after, it also kills the queued request so it neither runs on nor mutates the + // server behind a cancelled call. + if (done == null) { + done = new Completer(); + } + if (!done.isCompleted()) { + done.completeError(exception != null ? exception : new HttpException("Request aborted", uri)); + } + if (sent != null) { + sent.kill(); + sent = null; + } + } + + boolean finished() { + return done != null && done.isCompleted(); + } + + public Future done() { + return done == null ? close() : done.future(); + } + + /** Sends the request; the future completes with the response, or a SocketException. */ + public Future close() { + if (done != null) { + return done.future(); + } + done = new Completer(); + if (!Display.isInitialized()) { + done.completeError(new SocketException("No network: Codename One is not initialized")); + return done.future(); + } + final byte[] payload = body.toByteArray(); + final Completer result = done; + final HttpHeaders responseHeaders = new HttpHeaders(); + final ByteArrayOutputStream received = new ByteArrayOutputStream(); + ConnectionRequest req = new ConnectionRequest() { + private Exception failure; + private int redirects; + private boolean tooManyRedirects; + + /// maxRedirects, counted per hop. Past the limit Dart fails the request + /// with "Redirect limit exceeded" rather than following on, and so does + /// this; returning true tells ConnectionRequest not to follow. (iOS + /// follows redirects natively and never calls this.) + @Override + public boolean onRedirect(String url) { + redirects++; + if (redirects > maxRedirects) { + tooManyRedirects = true; + return true; + } + return false; + } + + @Override + protected void readHeaders(Object connection) throws java.io.IOException { + String[] names = getHeaderFieldNames(connection); + if (names != null) { + for (String n : names) { + if (n == null) { + continue; + } + String[] vs = getHeaders(connection, n); + if (vs != null) { + for (String v : vs) { + responseHeaders.add(n, v); + } + } + } + } + } + + @Override + protected void buildRequestBody(OutputStream os) throws java.io.IOException { + os.write(payload); + } + + @Override + protected void readResponse(InputStream input) throws java.io.IOException { + byte[] buf = new byte[8192]; + int n; + while ((n = input.read(buf)) > 0) { + received.write(buf, 0, n); + } + } + + @Override + protected void handleErrorResponseCode(int code, String message) { + // The status reaches the app through the response, as in dart:io. + } + + /// Transport failures reach only this (NetworkManager never calls + /// postResponse after one), so they settle here, on the EDT. With + /// failSilently set they were only logged and the call stayed pending. + @Override + protected void handleException(Exception err) { + failure = err; + if (com.codename1.ui.Display.isInitialized() && !com.codename1.ui.Display.getInstance().isEdt()) { + com.codename1.ui.Display.getInstance().callSerially(new Runnable() { + @Override + public void run() { + finish(); + } + }); + } else { + finish(); + } + } + + @Override + protected void postResponse() { + finish(); + } + + private void finish() { + if (result.isCompleted()) { + return; + } + if (tooManyRedirects) { + result.completeError(new HttpException("Redirect limit exceeded")); + return; + } + if (failure != null) { + result.completeError(new SocketException(String.valueOf(failure.getMessage()))); + return; + } + byte[] body = received.toByteArray(); + boolean inflated = false; + // We asked for gzip (autoUncompress), so we undo it. Checked by magic as well + // as header: iOS decompresses natively but keeps Content-Encoding, while + // JavaSE and Android hand over the compressed bytes. + if (client.autoUncompress() && body.length > 2 && (body[0] & 0xff) == 0x1f + && (body[1] & 0xff) == 0x8b + && "gzip".equalsIgnoreCase(String.valueOf(responseHeaders.value(HttpHeaders.contentEncodingHeader)).trim())) { + try { + body = gunzip(body); + inflated = true; + } catch (java.io.IOException e) { + result.completeError(new HttpException("Failed to decompress the response: " + e.getMessage())); + return; + } + } + HttpClientResponse res = new HttpClientResponse(getResponseCode(), getResponseErrorMessage(), + responseHeaders, body); + // Content-Length is the compressed size whoever inflated it: iOS hands + // over decompressed bytes (no magic left) under the same header. + res.uncompressed = inflated || (client.autoUncompress() && "gzip".equalsIgnoreCase( + String.valueOf(responseHeaders.value(HttpHeaders.contentEncodingHeader)).trim())); + result.complete(res); + } + }; + req.setUrl(uri.toString()); + req.setHttpMethod(method); + boolean hasBody = payload.length > 0; + req.setPost(hasBody); + // Not failSilently: it keeps transport errors away from handleException. + req.setReadResponseForErrors(true); + req.setFollowRedirects(followRedirects); + req.setDuplicateSupported(true); + Duration timeout = client.connectionTimeout(); + if (timeout != null) { + req.setTimeout((int) timeout.inMilliseconds()); + } + for (Map.Entry> e : headers.all().entrySet()) { + StringBuilder v = new StringBuilder(); + for (String s : e.getValue()) { + if (v.length() > 0) { + v.append(", "); + } + v.append(s); + } + req.addRequestHeader(e.getKey(), v.toString()); + } + if (hasBody && headers.value(HttpHeaders.contentTypeHeader) != null) { + req.setContentType(headers.value(HttpHeaders.contentTypeHeader)); + } + sent = req; + NetworkManager.getInstance().addToQueue(req); + return done.future(); + } + + static byte[] gunzip(byte[] gz) throws java.io.IOException { + com.codename1.io.gzip.GZIPInputStream in = + new com.codename1.io.gzip.GZIPInputStream(new java.io.ByteArrayInputStream(gz)); + try { + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(gz.length * 4); + byte[] buf = new byte[8192]; + int n; + while ((n = in.read(buf)) > 0) { + out.write(buf, 0, n); + } + return out.toByteArray(); + } finally { + in.close(); + } + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/HttpClientResponse.java b/maven/dart-runtime/src/main/java/dart/io/HttpClientResponse.java new file mode 100644 index 00000000000..95dd8626b65 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/HttpClientResponse.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import dart.async.Stream; +import dart.core.DartList; +import dart.typed_data.Uint8List; + +/** + * Dart's {@code HttpClientResponse}: a stream of byte chunks -- here the whole body as + * one chunk, delivered once someone listens -- plus the status line and headers. + */ +public class HttpClientResponse extends Stream { + + private final long statusCode; + private final String reasonPhrase; + private final HttpHeaders headers; + private final byte[] body; + private boolean delivered; + + HttpClientResponse(long statusCode, String reasonPhrase, HttpHeaders headers, byte[] body) { + this.statusCode = statusCode; + this.reasonPhrase = reasonPhrase == null ? "" : reasonPhrase; + this.headers = headers; + this.body = body; + } + + @Override + protected void onFirstListener() { + if (delivered) { + return; + } + delivered = true; + new dart.async.Timer(dart.core.Duration.zero, new dart.runtime.Funcs.VoidFunc0() { + @Override + public void call() { + if (body.length > 0) { + emit(Uint8List.fromBytes(body)); + } + emitDone(); + } + }); + } + + public long statusCode() { + return statusCode; + } + + public String reasonPhrase() { + return reasonPhrase; + } + + public HttpHeaders headers() { + return headers; + } + + /// Set when the body was gunzipped here: the advertised length is the compressed one. + boolean uncompressed; + + /// The length the server declared up front (Content-Length), -1 when it declared none + /// or the body was decompressed -- not the bytes buffered: a HEAD response has none. + public long contentLength() { + if (uncompressed) { + return -1; + } + String v = headers.value(HttpHeaders.contentLengthHeader); + if (v == null) { + return -1; + } + v = v.trim(); + if (v.length() == 0 || v.length() > 18) { + return -1; + } + long n = 0; + for (int i = 0; i < v.length(); i++) { + char c = v.charAt(i); + if (c < '0' || c > '9') { + return -1; + } + n = n * 10 + (c - '0'); + } + return n; + } + + public boolean isRedirect() { + return statusCode >= 300 && statusCode < 400 && headers.value(HttpHeaders.locationHeader) != null; + } + + public boolean persistentConnection() { + return true; + } + + public DartList redirects() { + return new DartList(); + } + + public DartList cookies() { + return new DartList(); + } + + /** Dart's {@code compressionState}; the platform has already decompressed the body. */ + public String compressionState() { + return "decompressed"; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/HttpException.java b/maven/dart-runtime/src/main/java/dart/io/HttpException.java new file mode 100644 index 00000000000..fb7786da0e2 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/HttpException.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +/** Dart's {@code HttpException}. */ +public class HttpException extends IOException { + private static final long serialVersionUID = 1L; + + private final dart.core.DartUri uri; + + public HttpException(String message, dart.core.DartUri uri) { + super(message); + this.uri = uri; + } + + public HttpException(String message) { + this(message, null); + } + + public dart.core.DartUri uri() { + return uri; + } + + @Override + public String toString() { + return "HttpException: " + getMessage() + (uri == null ? "" : ", uri = " + uri); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/HttpHeaders.java b/maven/dart-runtime/src/main/java/dart/io/HttpHeaders.java new file mode 100644 index 00000000000..39e669766b9 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/HttpHeaders.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import dart.core.DartList; +import dart.runtime.Dyn; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Dart's {@code HttpHeaders}: names are case-insensitive and kept lower-cased, as + * dart:io does; each name holds a list of values. + */ +public class HttpHeaders { + public static final String acceptHeader = "accept"; + public static final String acceptEncodingHeader = "accept-encoding"; + public static final String acceptLanguageHeader = "accept-language"; + public static final String authorizationHeader = "authorization"; + public static final String cacheControlHeader = "cache-control"; + public static final String contentEncodingHeader = "content-encoding"; + public static final String contentLengthHeader = "content-length"; + public static final String contentTypeHeader = "content-type"; + public static final String cookieHeader = "cookie"; + public static final String dateHeader = "date"; + public static final String etagHeader = "etag"; + public static final String hostHeader = "host"; + public static final String ifModifiedSinceHeader = "if-modified-since"; + public static final String ifNoneMatchHeader = "if-none-match"; + public static final String lastModifiedHeader = "last-modified"; + public static final String locationHeader = "location"; + public static final String setCookieHeader = "set-cookie"; + public static final String userAgentHeader = "user-agent"; + + private final Map> values = new LinkedHashMap>(); + private boolean chunked; + + /** ASCII lower-casing: header names are ASCII and must not fold by locale. */ + static String asciiLower(String s) { + StringBuilder sb = null; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c >= 'A' && c <= 'Z') { + if (sb == null) { + sb = new StringBuilder(s); + } + sb.setCharAt(i, (char) (c + 32)); + } + } + return sb == null ? s : sb.toString(); + } + + public DartList $index(String name) { + List v = values.get(asciiLower(name)); + if (v == null) { + return null; + } + DartList out = new DartList(); + out.addAll(v); + return out; + } + + public String value(String name) { + List v = values.get(asciiLower(name)); + if (v == null || v.isEmpty()) { + return null; + } + if (v.size() > 1) { + throw new HttpException("More than one value for header " + name); + } + return v.get(0); + } + + public void add(String name, Object value) { + add(name, value, null); + } + + public void add(String name, Object value, Boolean preserveHeaderCase) { + String key = asciiLower(name); + List v = values.get(key); + if (v == null) { + v = new ArrayList(); + values.put(key, v); + } + if (value instanceof Iterable) { + for (Object o : (Iterable) value) { + v.add(String.valueOf(o)); + } + } else { + v.add(String.valueOf(value)); + } + } + + public void set(String name, Object value) { + set(name, value, null); + } + + public void set(String name, Object value, Boolean preserveHeaderCase) { + values.remove(asciiLower(name)); + if (value != null) { + add(name, value); + } + } + + public void remove(String name, Object value) { + List v = values.get(asciiLower(name)); + if (v != null) { + v.remove(String.valueOf(value)); + if (v.isEmpty()) { + values.remove(asciiLower(name)); + } + } + } + + public void removeAll(String name) { + values.remove(asciiLower(name)); + } + + public void clear() { + values.clear(); + } + + /** Dart's {@code forEach((name, values) {...})}. */ + public void forEach(Object action) { + for (Map.Entry> e : new ArrayList>>(values.entrySet())) { + DartList l = new DartList(); + l.addAll(e.getValue()); + Dyn.invoke(action, e.getKey(), l); + } + } + + public ContentType contentType() { + String v = value(contentTypeHeader); + return v == null ? null : ContentType.parse(v); + } + + public void contentType(ContentType type) { + set(contentTypeHeader, type == null ? null : type.toString()); + } + + public long contentLength() { + String v = value(contentLengthHeader); + if (v == null) { + return -1; + } + try { + return Long.parseLong(v.trim()); + } catch (NumberFormatException e) { + return -1; + } + } + + public void contentLength(long length) { + if (length < 0) { + removeAll(contentLengthHeader); + } else { + set(contentLengthHeader, Long.toString(length)); + } + } + + public boolean chunkedTransferEncoding() { + return chunked; + } + + public void chunkedTransferEncoding(boolean v) { + chunked = v; + } + + /** Every name and value, in insertion order; for the request that sends them. */ + Map> all() { + return values; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Map.Entry> e : values.entrySet()) { + for (String v : e.getValue()) { + sb.append(e.getKey()).append(": ").append(v).append('\n'); + } + } + return sb.toString(); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/HttpStatus.java b/maven/dart-runtime/src/main/java/dart/io/HttpStatus.java new file mode 100644 index 00000000000..85e49e2939b --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/HttpStatus.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +/** Dart's {@code HttpStatus} constants (the ones apps compare against). */ +public final class HttpStatus { + public static final long continue_ = 100; + public static final long ok = 200; + public static final long created = 201; + public static final long accepted = 202; + public static final long noContent = 204; + public static final long movedPermanently = 301; + public static final long found = 302; + public static final long seeOther = 303; + public static final long notModified = 304; + public static final long temporaryRedirect = 307; + public static final long permanentRedirect = 308; + public static final long badRequest = 400; + public static final long unauthorized = 401; + public static final long paymentRequired = 402; + public static final long forbidden = 403; + public static final long notFound = 404; + public static final long methodNotAllowed = 405; + public static final long notAcceptable = 406; + public static final long requestTimeout = 408; + public static final long conflict = 409; + public static final long gone = 410; + public static final long unprocessableEntity = 422; + public static final long tooManyRequests = 429; + public static final long internalServerError = 500; + public static final long notImplemented = 501; + public static final long badGateway = 502; + public static final long serviceUnavailable = 503; + public static final long gatewayTimeout = 504; + + private HttpStatus() { + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/IOException.java b/maven/dart-runtime/src/main/java/dart/io/IOException.java new file mode 100644 index 00000000000..404b2810087 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/IOException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +/** Dart's {@code IOException}: the base of the dart:io failures an app catches. */ +public class IOException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public IOException(String message) { + super(message); + } + + public String message() { + return getMessage(); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/Platform.java b/maven/dart-runtime/src/main/java/dart/io/Platform.java new file mode 100644 index 00000000000..f8b68bb0093 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/Platform.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import com.codename1.ui.Display; +import dart.core.DartMap; + +/** + * Dart's {@code Platform}, answered from {@link Display#getPlatformName()} -- so the + * simulator reports the platform of the skin it is showing, as a Flutter app expects. + */ +public final class Platform { + + /** One of Dart's operatingSystem names: android, ios, macos, windows, linux. */ + public static final String operatingSystem = detect(); + public static final boolean isAndroid = operatingSystem.equals("android"); + public static final boolean isIOS = operatingSystem.equals("ios"); + public static final boolean isMacOS = operatingSystem.equals("macos"); + public static final boolean isWindows = operatingSystem.equals("windows"); + public static final boolean isLinux = operatingSystem.equals("linux"); + public static final boolean isFuchsia = false; + public static final String operatingSystemVersion = + Display.isInitialized() ? Display.getInstance().getProperty("OSVer", "") : ""; + public static final String localeName = locale(); + public static final long numberOfProcessors = 1; + public static final String pathSeparator = "/"; + public static final String localHostname = "localhost"; + public static final String version = "3.0.0"; + /** No process environment on a device; always empty. */ + public static final DartMap environment = new DartMap(); + public static final String resolvedExecutable = ""; + public static final String script = ""; + + // Dart's Platform members are static getters, which the transpiler reads as static + // fields; they are computed when the class is first used, after Codename One started. + + private Platform() { + } + + private static String detect() { + String p = null; + if (Display.isInitialized()) { + p = Display.getInstance().getPlatformName(); + } + if (p == null) { + String os = System.getProperty("os.name"); + p = os == null ? "" : os; + } + if (p.equals("and") || p.regionMatches(true, 0, "android", 0, 7)) { + return "android"; + } + if (p.equals("ios") || p.regionMatches(true, 0, "iphone", 0, 6)) { + return "ios"; + } + if (p.equals("mac") || p.regionMatches(true, 0, "mac", 0, 3)) { + return "macos"; + } + if (p.equals("win") || p.regionMatches(true, 0, "windows", 0, 7)) { + return "windows"; + } + return "linux"; + } + + private static String locale() { + if (Display.isInitialized()) { + com.codename1.l10n.L10NManager l = Display.getInstance().getLocalizationManager(); + if (l != null) { + return l.getLanguage() + "_" + l.getLocale(); + } + } + return "en_US"; + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/SocketException.java b/maven/dart-runtime/src/main/java/dart/io/SocketException.java new file mode 100644 index 00000000000..43a9e0834d3 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/SocketException.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +/** Dart's {@code SocketException}: the connection could not be made or was lost. */ +public class SocketException extends IOException { + private static final long serialVersionUID = 1L; + + public SocketException(String message) { + super(message); + } + + @Override + public String toString() { + return "SocketException: " + getMessage(); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/io/WebSocket.java b/maven/dart-runtime/src/main/java/dart/io/WebSocket.java new file mode 100644 index 00000000000..fdd9fcd7037 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/io/WebSocket.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.io; + +import com.codename1.ui.Display; +import dart.async.Completer; +import dart.async.Future; +import dart.async.Stream; +import dart.convert.ByteCodec; +import dart.typed_data.Uint8List; + +/** + * Dart's {@code WebSocket} over {@link com.codename1.io.WebSocket}: a stream of the + * messages received (a String per text frame, a Uint8List per binary frame) that the + * app writes to with {@link #add}. Events reach listeners on the EDT. + */ +public class WebSocket extends Stream { + public static final long connecting = 0; + public static final long open = 1; + public static final long closing = 2; + public static final long closed = 3; + + private com.codename1.io.WebSocket socket; + private volatile long readyState = connecting; + private Long closeCode; + private String closeReason; + private final Completer done = new Completer(); + private WebSocket() { + } + + public static Future connect(String url) { + return connect(url, null, null, null); + } + + /** {@code WebSocket.connect(url, protocols:, headers:, compression:)}. */ + public static Future connect(String url, Object protocols, Object headers, Object compression) { + final Completer result = new Completer(); + if (!Display.isInitialized() || !com.codename1.io.WebSocket.isSupported()) { + result.completeError(new SocketException("WebSockets are not supported here")); + return result.future(); + } + final WebSocket ws = new WebSocket(); + com.codename1.io.WebSocket s = com.codename1.io.WebSocket.build(url); + if (protocols instanceof Iterable) { + java.util.List ps = new java.util.ArrayList(); + for (Object p : (Iterable) protocols) { + ps.add(String.valueOf(p)); + } + s.subprotocols(ps.toArray(new String[ps.size()])); + } + if (headers instanceof java.util.Map) { + for (java.util.Map.Entry e : ((java.util.Map) headers).entrySet()) { + s.header(String.valueOf(e.getKey()), String.valueOf(e.getValue())); + } + } + s.onConnect(new com.codename1.io.WebSocket.ConnectHandler() { + @Override + public void onConnect(com.codename1.io.WebSocket w) { + ws.readyState = open; + onEdt(new Runnable() { + @Override + public void run() { + result.complete(ws); + } + }); + } + }); + s.onTextMessage(new com.codename1.io.WebSocket.TextHandler() { + @Override + public void onText(com.codename1.io.WebSocket w, final String message) { + onEdt(new Runnable() { + @Override + public void run() { + ws.emit(message); + } + }); + } + }); + s.onBinaryMessage(new com.codename1.io.WebSocket.BinaryHandler() { + @Override + public void onBinary(com.codename1.io.WebSocket w, final byte[] message) { + onEdt(new Runnable() { + @Override + public void run() { + ws.emit(Uint8List.fromBytes(message)); + } + }); + } + }); + s.onClose(new com.codename1.io.WebSocket.CloseHandler() { + @Override + public void onClose(com.codename1.io.WebSocket w, final int statusCode, final String reason) { + ws.readyState = closed; + onEdt(new Runnable() { + @Override + public void run() { + ws.closeCode = Long.valueOf(statusCode); + ws.closeReason = reason; + if (!result.isCompleted()) { + result.completeError(new SocketException("WebSocket closed during connect: " + reason)); + } + ws.finish(); + } + }); + } + }); + s.onError(new com.codename1.io.WebSocket.ErrorHandler() { + @Override + public void onError(com.codename1.io.WebSocket w, final Exception ex) { + onEdt(new Runnable() { + @Override + public void run() { + SocketException err = new SocketException(String.valueOf(ex.getMessage())); + if (!result.isCompleted()) { + ws.readyState = closed; + result.completeError(err); + } else { + ws.emitError(err); + } + } + }); + } + }); + ws.socket = s; + s.connect(); + return result.future(); + } + + private static void onEdt(Runnable r) { + if (Display.getInstance().isEdt()) { + r.run(); + } else { + Display.getInstance().callSerially(r); + } + } + + private void finish() { + if (!done.isCompleted()) { + emitDone(); + done.complete(null); + } + } + + /** Sends a String as a text frame, anything else as a binary frame. */ + public void add(Object data) { + // Adding to a closed sink is a StateError in Dart; dropping the frame silently + // hid send/close races as successful sends. + if (readyState != open) { + throw new dart.core.StateError("WebSocket is not open (readyState " + readyState + ")"); + } + if (data instanceof String) { + socket.send((String) data); + } else { + socket.send(ByteCodec.bytesOf(data)); + } + } + + public void addUtf8Text(Object bytes) { + add(dart.convert.Convert.utf8.decode(bytes)); + } + + public Future addStream(Stream stream) { + return stream.forEach(new dart.runtime.Funcs.VoidFunc1() { + @Override + public void call(Object v) { + add(v); + } + }); + } + + public Future close() { + return close(null, null); + } + + public Future close(Long code) { + return close(code, null); + } + + public Future close(Long code, String reason) { + if (readyState == open || readyState == connecting) { + readyState = closing; + closeCode = code; + closeReason = reason; + socket.close(); + } + return done.future(); + } + + public Future done() { + return done.future(); + } + + public long readyState() { + return readyState; + } + + public Long closeCode() { + return closeCode; + } + + public String closeReason() { + return closeReason; + } + + public String protocol() { + return socket == null ? null : socket.getSelectedSubprotocol(); + } + + public String extensions() { + return ""; + } + + public void pingInterval(Object interval) { + } +} diff --git a/maven/dart-runtime/src/main/java/dart/runtime/DartRuntime.java b/maven/dart-runtime/src/main/java/dart/runtime/DartRuntime.java index 8ad1c9c52a3..b4c0d97becd 100644 --- a/maven/dart-runtime/src/main/java/dart/runtime/DartRuntime.java +++ b/maven/dart-runtime/src/main/java/dart/runtime/DartRuntime.java @@ -680,6 +680,29 @@ public static double mod(double a, double b) { return r < 0 ? r + Math.abs(b) : r; } + /// `num.toInt()` on a value typed only num: a boxed Long or Double. + public static long numToInt(Object n) { + if (n instanceof Double d) { + return toInt(d.doubleValue()); + } + if (n instanceof Number num) { + return num.longValue(); + } + throw new dart.core.NoSuchMethodError("Class '" + (n == null ? "Null" : n.getClass().getSimpleName()) + "' has no instance method 'toInt'."); + } + + public static double numToDouble(Object n) { + if (n instanceof Number num) { + return num.doubleValue(); + } + throw new dart.core.NoSuchMethodError("Class '" + (n == null ? "Null" : n.getClass().getSimpleName()) + "' has no instance method 'toDouble'."); + } + + /// `x.hashCode` for an object the emitter has no declaration for. + public static long hashCodeOf(Object o) { + return o == null ? 0 : o.hashCode(); + } + /** * Dart string conversion used by string interpolation and print: * null prints as "null", doubles use Dart's formatting. @@ -694,6 +717,18 @@ public static String str(Object v) { if (v instanceof Float f) { return doubleStr(f); } + if (v instanceof Enum e) { + // Dart prints an enum value qualified -- `ResponseType.json` -- and a runtime + // enum's own toString is only the constant. A transpiled enum overrides + // toString with its Dart name, which its Java name may not be (a library's + // enum renamed to keep two same-named ones apart), so that answer stands. + String own = e.toString(); + return own.equals(e.name()) ? e.getDeclaringClass().getSimpleName() + "." + own : own; + } + if (v instanceof Class c) { + // A Dart Type (`runtimeType`) is a Class here; Dart prints the bare type name. + return c.getSimpleName(); + } return v.toString(); } @@ -1026,6 +1061,14 @@ public static void setPrintSink(Funcs.VoidFunc1 sink) { * Dart's `throw expr` accepts any object; wrap non-throwables so the * JVM can propagate them while toString stays Dart-like. */ + /** + * `throw` used as an expression (`x ?? (throw ...)`, `=> throw ...`): never returns, but + * its generic result lets it stand in any expression position, as Dart's Never does. + */ + public static T throwValue(Object thrown) { + throw asError(thrown); + } + public static RuntimeException asError(Object thrown) { if (thrown instanceof RuntimeException re) { return re; diff --git a/maven/dart-runtime/src/main/java/dart/runtime/Dyn.java b/maven/dart-runtime/src/main/java/dart/runtime/Dyn.java new file mode 100644 index 00000000000..3b278826340 --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/runtime/Dyn.java @@ -0,0 +1,769 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.runtime; + +import dart.core.DString; +import dart.core.DartIterable; +import dart.core.DartList; +import dart.core.DartMap; +import dart.core.NoSuchMethodError; +import dart.typed_data.Uint8List; + +import java.util.Map; + +/** + * Member access through a {@code dynamic} receiver -- the code Dart runs for + * {@code json['user']['name']}, {@code data.length} or {@code obj.save(x)} when the + * static type says nothing. + * + *

A transpiled class answers for itself through {@link DynamicObject}. The runtime's + * own representations of Dart's built-in values -- maps, lists, iterables, strings, + * numbers, booleans and functions -- are dispatched here by hand, covering the members + * decoded-JSON code reaches for. Anything else is Dart's NoSuchMethodError.

+ * + *

No reflection is used: ParparVM has none.

+ */ +public final class Dyn { + + /** Returned by a {@link DynamicObject} for a member it does not have. */ + public static final Object NO_MEMBER = new Object(); + + private static final Object[] NONE = new Object[0]; + + private Dyn() { + } + + // ------------------------------------------------------------------ get + + public static Object get(Object o, String name) { + if (o instanceof DynamicObject) { + Object v = ((DynamicObject) o).$dget(name); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof Map) { + Object v = mapGet((Map) o, name); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof Iterable) { + Object v = iterableGet(DartIterable.wrap(asIterable(o)), name); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof String) { + Object v = stringGet((String) o, name); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof Number) { + Object v = numberGet((Number) o, name); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof Uint8List) { + Uint8List b = (Uint8List) o; + if (name.equals("length")) { + return Long.valueOf(b.length()); + } + if (name.equals("isEmpty")) { + return Boolean.valueOf(b.isEmpty()); + } + if (name.equals("isNotEmpty")) { + return Boolean.valueOf(b.isNotEmpty()); + } + } + if (name.equals("hashCode")) { + return Long.valueOf(o == null ? 0 : o.hashCode()); + } + if (name.equals("runtimeType")) { + return o == null ? null : o.getClass(); + } + throw noSuch(o, name); + } + + private static Object mapGet(Map m, String name) { + switch (name) { + case "length": + return Long.valueOf(m.size()); + case "isEmpty": + return Boolean.valueOf(m.isEmpty()); + case "isNotEmpty": + return Boolean.valueOf(!m.isEmpty()); + case "keys": + return DartIterable.wrap(copy(m.keySet())); + case "values": + return DartIterable.wrap(copy(m.values())); + case "entries": + if (m instanceof DartMap) { + return ((DartMap) m).entries(); + } + return NO_MEMBER; + default: + return NO_MEMBER; + } + } + + private static Object iterableGet(DartIterable it, String name) { + switch (name) { + case "length": + return Long.valueOf(it.length()); + case "isEmpty": + return Boolean.valueOf(it.isEmpty()); + case "isNotEmpty": + return Boolean.valueOf(it.isNotEmpty()); + case "first": + return it.first(); + case "last": + return it.last(); + case "reversed": + return DartIterable.wrap(reversed(it)); + default: + return NO_MEMBER; + } + } + + private static DartList copy(java.util.Collection c) { + DartList l = new DartList(); + for (Object o : c) { + l.add(o); + } + return l; + } + + private static DartList reversed(Iterable it) { + DartList l = new DartList(); + for (Object o : it) { + l.add(0, o); + } + return l; + } + + private static Object stringGet(String s, String name) { + switch (name) { + case "length": + return Long.valueOf(s.length()); + case "isEmpty": + return Boolean.valueOf(s.isEmpty()); + case "isNotEmpty": + return Boolean.valueOf(!s.isEmpty()); + default: + return NO_MEMBER; + } + } + + private static Object numberGet(Number n, String name) { + boolean dbl = n instanceof Double || n instanceof Float; + switch (name) { + case "isNaN": + return Boolean.valueOf(dbl && Double.isNaN(n.doubleValue())); + case "isNegative": + return Boolean.valueOf(n.doubleValue() < 0); + case "isFinite": + return Boolean.valueOf(!dbl || !(Double.isNaN(n.doubleValue()) || Double.isInfinite(n.doubleValue()))); + case "isInfinite": + return Boolean.valueOf(dbl && Double.isInfinite(n.doubleValue())); + case "isEven": + return Boolean.valueOf(n.longValue() % 2 == 0); + case "isOdd": + return Boolean.valueOf(n.longValue() % 2 != 0); + case "sign": + // Hand-written: ParparVM's class library (vm/JavaAPI) has neither + // Math.signum nor Long.signum, and the native builds failed to link. + // d itself is returned for 0.0, -0.0 and NaN, as Dart's sign does. + if (dbl) { + double d = n.doubleValue(); + return Double.valueOf(d > 0 ? 1.0 : d < 0 ? -1.0 : d); + } + long l = n.longValue(); + return Long.valueOf(l > 0 ? 1L : l < 0 ? -1L : 0L); + default: + return NO_MEMBER; + } + } + + // ------------------------------------------------------------------ set + + @SuppressWarnings("unchecked") + public static Object set(Object o, String name, Object value) { + if (o instanceof DynamicObject && ((DynamicObject) o).$dset(name, value)) { + return value; + } + throw noSuch(o, name + "="); + } + + // ------------------------------------------------------------------ index + + @SuppressWarnings("unchecked") + public static Object index(Object o, Object key) { + if (o instanceof Map) { + return ((Map) o).get(key); + } + if (o instanceof java.util.List && key instanceof Number) { + java.util.List l = (java.util.List) o; + long i = ((Number) key).longValue(); + dart.core.RangeError.checkValidIndex(i, l.size()); + return l.get((int) i); + } + if (o instanceof String && key instanceof Number) { + return DString.idx((String) o, ((Number) key).longValue()); + } + if (o instanceof Uint8List && key instanceof Number) { + return Long.valueOf(((Uint8List) o).$index(((Number) key).longValue())); + } + if (o instanceof DynamicObject) { + return call(o, "[]", new Object[] {key}, null); + } + throw noSuch(o, "[]"); + } + + @SuppressWarnings("unchecked") + public static Object indexSet(Object o, Object key, Object value) { + if (o instanceof Map) { + ((Map) o).put(key, value); + return value; + } + if (o instanceof java.util.List && key instanceof Number) { + java.util.List l = (java.util.List) o; + long i = ((Number) key).longValue(); + dart.core.RangeError.checkValidIndex(i, l.size()); + l.set((int) i, value); + return value; + } + if (o instanceof Uint8List && key instanceof Number && value instanceof Number) { + ((Uint8List) o).$indexSet(((Number) key).longValue(), ((Number) value).longValue()); + return value; + } + if (o instanceof DynamicObject) { + call(o, "[]=", new Object[] {key, value}, null); + return value; + } + throw noSuch(o, "[]="); + } + + // ------------------------------------------------------------------ call + + public static Object call(Object o, String name, Object[] args) { + return call(o, name, args, null); + } + + @SuppressWarnings("unchecked") + public static Object call(Object o, String name, Object[] args, DartMap named) { + if (args == null) { + args = NONE; + } + if (o instanceof DynamicObject) { + Object v = ((DynamicObject) o).$dcall(name, args, named); + if (v != NO_MEMBER) { + return v; + } + // A field holding a function: `obj.callback(x)`. + Object f = ((DynamicObject) o).$dget(name); + if (f != NO_MEMBER && f != null && isFunction(f)) { + return invoke(f, args); + } + } else if (name.equals("call") && isFunction(o)) { + return invoke(o, args); + } else if (named != null && !named.isEmpty() + && (o instanceof Map || o instanceof Iterable || o instanceof String || o instanceof Number)) { + // Named arguments arrive apart from the positional ones; the built-ins that + // take any are handled here, and any other name is a mismatched call. + if (o instanceof Iterable && named.size() == 1 && args.length == 0 && name.equals("toList") + && named.containsKey("growable")) { + return DartList.from((Iterable) o, !Boolean.FALSE.equals(named.get("growable"))); + } + if (o instanceof Iterable && named.size() == 1 && args.length == 1 && named.containsKey("orElse") + && name.equals("firstWhere")) { + return call(o, name, new Object[] {args[0], named.get("orElse")}, null); + } + throw noSuch(o, name + "()"); + } else if (args.length > builtinMaxArgs(name) + && (o instanceof Map || o instanceof Iterable || o instanceof String || o instanceof Number)) { + // Dart rejects a mismatched invocation before running anything: m.clear(42) + // on a dynamic map is a NoSuchMethodError, not a cleared map. + throw noSuch(o, name + "()"); + } else if (o instanceof Map) { + Object v = mapCall((Map) o, name, args); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof Iterable) { + Object v = iterableCall(o, name, args); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof String) { + Object v = stringCall((String) o, name, args); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof Number) { + Object v = numberCall((Number) o, name, args); + if (v != NO_MEMBER) { + return v; + } + } else if (o instanceof Uint8List && name.equals("toList")) { + return ((Uint8List) o).toList(); + } + if (args.length == 0 && name.equals("toString")) { + return DartRuntime.str(o); + } + if (args.length == 1 && name.equals("==")) { + return Boolean.valueOf(o == null ? args[0] == null : o.equals(args[0])); + } + throw noSuch(o, name + "()"); + } + + /// The most positional arguments the built-in member {@code name} accepts on a Map, + /// Iterable, String or num; the dispatchers below only check that required ones + /// are present. Unknown names answer Integer.MAX_VALUE and reach NO_MEMBER as before. + private static int builtinMaxArgs(String name) { + switch (name) { + case "clear": case "removeLast": case "toList": case "toSet": case "cast": case "toLowerCase": + case "toUpperCase": case "trim": case "toString": case "toInt": case "toDouble": case "round": + case "floor": case "ceil": case "abs": + return 0; + case "containsKey": case "containsValue": case "remove": case "addAll": case "forEach": case "add": + case "removeAt": case "sort": case "map": case "where": case "any": case "every": + case "join": case "elementAt": case "skip": case "take": case "endsWith": + case "split": case "codeUnitAt": case "compareTo": case "toStringAsFixed": + return 1; + case "putIfAbsent": case "insert": case "indexOf": case "sublist": case "firstWhere": case "fold": + case "substring": case "replaceAll": case "padLeft": case "clamp": case "contains": case "startsWith": + return 2; + default: + return Integer.MAX_VALUE; + } + } + + private static Object mapCall(Map m, String name, Object[] a) { + switch (name) { + case "containsKey": + return Boolean.valueOf(m.containsKey(arg(a, 0))); + case "containsValue": + return Boolean.valueOf(m.containsValue(arg(a, 0))); + case "remove": + return m.remove(arg(a, 0)); + case "clear": + m.clear(); + return null; + case "putIfAbsent": { + Object k = arg(a, 0); + if (!m.containsKey(k)) { + m.put(k, invoke(arg(a, 1), NONE)); + } + return m.get(k); + } + case "addAll": + if (arg(a, 0) instanceof Map) { + m.putAll((Map) arg(a, 0)); + } + return null; + case "forEach": { + Object f = arg(a, 0); + for (Object o : copy(m.entrySet())) { + Map.Entry e = (Map.Entry) o; + invoke(f, new Object[] {e.getKey(), e.getValue()}); + } + return null; + } + case "cast": + return m; + default: + return NO_MEMBER; + } + } + + @SuppressWarnings("unchecked") + private static Object iterableCall(Object o, String name, Object[] a) { + DartIterable it = DartIterable.wrap(asIterable(o)); + if (o instanceof DartList) { + DartList l = (DartList) o; + switch (name) { + case "add": + l.add(arg(a, 0)); + return null; + case "addAll": + l.addAllIterable(asIterable(arg(a, 0))); + return null; + case "remove": + return Boolean.valueOf(l.removeValue(arg(a, 0))); + case "removeAt": + return l.removeAt(num(arg(a, 0)).longValue()); + case "removeLast": + return l.removeLast(); + case "insert": + l.insert(num(arg(a, 0)).longValue(), arg(a, 1)); + return null; + case "clear": + l.clear(); + return null; + case "indexOf": + return Long.valueOf(l.indexOfDart(arg(a, 0))); + case "sublist": + return a.length > 1 && a[1] != null ? l.sublist(num(a[0]).longValue(), num(a[1]).longValue()) + : l.sublist(num(arg(a, 0)).longValue()); + case "sort": + if (a.length == 0 || a[0] == null) { + l.sortDefault(); + } else { + final Object cmp = a[0]; + l.sort(new Funcs.Func2() { + @Override + public Long call(Object x, Object y) { + return Long.valueOf(num(invoke(cmp, new Object[] {x, y})).longValue()); + } + }); + } + return null; + default: + break; + } + } + switch (name) { + case "map": + return it.map(fn1(arg(a, 0))); + case "where": + return it.where(pred(arg(a, 0))); + case "any": + return Boolean.valueOf(it.any(pred(arg(a, 0)))); + case "every": + return Boolean.valueOf(it.every(pred(arg(a, 0)))); + case "contains": + return Boolean.valueOf(it.contains(arg(a, 0))); + case "toList": + return it.toList(); + case "toSet": + return it.toSet(); + case "join": + return a.length == 0 || a[0] == null ? it.join() : it.join(DartRuntime.as(a[0], String.class, false, "String")); + case "forEach": { + Object f = arg(a, 0); + for (Object e : it.toList()) { + invoke(f, new Object[] {e}); + } + return null; + } + case "elementAt": + return it.elementAt(num(arg(a, 0)).longValue()); + case "firstWhere": { + final Object orElse = a.length > 1 ? a[1] : null; + for (Object e : it) { + if (truthy(invoke(arg(a, 0), new Object[] {e}))) { + return e; + } + } + if (orElse != null) { + return invoke(orElse, NONE); + } + throw new dart.core.StateError("No element"); + } + case "fold": { + Object acc = arg(a, 0); + for (Object e : it) { + acc = invoke(arg(a, 1), new Object[] {acc, e}); + } + return acc; + } + case "skip": + return it.skip(num(arg(a, 0)).longValue()); + case "take": + return it.take(num(arg(a, 0)).longValue()); + case "cast": + return o; + default: + return NO_MEMBER; + } + } + + /// A value already checked to be a Number (or null), boxed as the Java type an + /// {@code int?} or {@code double?} parameter takes: Long or Double. + public static Object numAs(Number n, boolean isInt) { + if (n == null) { + return null; + } + if (isInt) { + return n instanceof Long ? n : Long.valueOf(n.longValue()); + } + return n instanceof Double ? n : Double.valueOf(n.doubleValue()); + } + + private static Object stringCall(String s, String name, Object[] a) { + switch (name) { + case "toLowerCase": + return DString.toLowerCase(s); + case "toUpperCase": + return DString.toUpperCase(s); + case "trim": + return DString.trim(s); + case "contains": + // The optional offset goes to DString's range-checked overloads, as a typed call would. + return Boolean.valueOf(a.length > 1 && a[1] != null + ? DString.contains(s, String.valueOf(a[0]), num(a[1]).longValue()) + : s.contains(String.valueOf(arg(a, 0)))); + case "startsWith": + return Boolean.valueOf(a.length > 1 && a[1] != null + ? DString.startsWith(s, String.valueOf(a[0]), num(a[1]).longValue()) + : s.startsWith(String.valueOf(arg(a, 0)))); + case "endsWith": + return Boolean.valueOf(s.endsWith(String.valueOf(arg(a, 0)))); + case "split": + return DString.split(s, String.valueOf(arg(a, 0))); + case "substring": + return a.length > 1 && a[1] != null ? DString.substring(s, num(a[0]).longValue(), num(a[1]).longValue()) + : DString.substring(s, num(arg(a, 0)).longValue()); + case "replaceAll": + return DString.replaceAll(s, String.valueOf(arg(a, 0)), String.valueOf(arg(a, 1))); + case "indexOf": + return Long.valueOf(a.length > 1 && a[1] != null + ? DString.indexOf(s, String.valueOf(a[0]), num(a[1]).longValue()) + : DString.indexOf(s, String.valueOf(arg(a, 0)))); + case "codeUnitAt": + return Long.valueOf(DString.codeUnitAt(s, num(arg(a, 0)).longValue())); + case "padLeft": + return a.length > 1 && a[1] != null ? DString.padLeft(s, num(a[0]).longValue(), DartRuntime.as(a[1], String.class, false, "String")) + : DString.padLeft(s, num(arg(a, 0)).longValue()); + case "compareTo": + return Long.valueOf(DString.compareTo(s, DartRuntime.as(arg(a, 0), String.class, false, "String"))); + case "toString": + return s; + default: + return NO_MEMBER; + } + } + + private static Object numberCall(Number n, String name, Object[] a) { + boolean dbl = n instanceof Double || n instanceof Float; + double d = n.doubleValue(); + switch (name) { + // The DartRuntime helpers refuse NaN and the infinities with UnsupportedError, + // as Dart does; a bare (long) cast fabricated 0 or a saturated long. + case "toInt": + return Long.valueOf(dbl ? DartRuntime.toInt(d) : n.longValue()); + case "toDouble": + return Double.valueOf(d); + case "round": + return Long.valueOf(dbl ? DartRuntime.round(d) : n.longValue()); + case "floor": + return Long.valueOf(dbl ? DartRuntime.floor(d) : n.longValue()); + case "ceil": + return Long.valueOf(dbl ? DartRuntime.ceil(d) : n.longValue()); + case "abs": + return dbl ? (Object) Double.valueOf(Math.abs(d)) : (Object) Long.valueOf(Math.abs(n.longValue())); + case "toStringAsFixed": + return DartRuntime.toStringAsFixed(d, num(arg(a, 0)).longValue()); + case "compareTo": + // Dart's total order: NaN after everything, -0.0 before 0.0 + return Long.valueOf(dart.core.DartComparable.compare(n, num(arg(a, 0)))); + case "clamp": { + Number lo = num(arg(a, 0)); + Number hi = num(arg(a, 1)); + if (d < lo.doubleValue()) { + return lo; + } + return d > hi.doubleValue() ? hi : n; + } + case "toString": + return DartRuntime.str(n); + default: + return NO_MEMBER; + } + } + + // ------------------------------------------------------------------ functions + + public static boolean isFunction(Object f) { + return f instanceof Funcs.Func0 || f instanceof Funcs.Func1 || f instanceof Funcs.Func2 + || f instanceof Funcs.Func3 || f instanceof Funcs.Func4 || f instanceof Funcs.Func5 + || f instanceof Funcs.VoidFunc0 || f instanceof Funcs.VoidFunc1 || f instanceof Funcs.VoidFunc2 + || f instanceof Funcs.VoidFunc3 || f instanceof Funcs.VoidFunc4 || f instanceof Funcs.VoidFunc5; + } + + /** Calls a function value whose static type is dynamic or Function. */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static Object invoke(Object f, Object... a) { + if (a == null) { + a = NONE; + } + switch (a.length) { + case 0: + if (f instanceof Funcs.Func0) { + return ((Funcs.Func0) f).call(); + } + if (f instanceof Funcs.VoidFunc0) { + ((Funcs.VoidFunc0) f).call(); + return null; + } + break; + case 1: + if (f instanceof Funcs.Func1) { + return ((Funcs.Func1) f).call(a[0]); + } + if (f instanceof Funcs.VoidFunc1) { + ((Funcs.VoidFunc1) f).call(a[0]); + return null; + } + break; + case 2: + if (f instanceof Funcs.Func2) { + return ((Funcs.Func2) f).call(a[0], a[1]); + } + if (f instanceof Funcs.VoidFunc2) { + ((Funcs.VoidFunc2) f).call(a[0], a[1]); + return null; + } + break; + case 3: + if (f instanceof Funcs.Func3) { + return ((Funcs.Func3) f).call(a[0], a[1], a[2]); + } + if (f instanceof Funcs.VoidFunc3) { + ((Funcs.VoidFunc3) f).call(a[0], a[1], a[2]); + return null; + } + break; + case 4: + if (f instanceof Funcs.Func4) { + return ((Funcs.Func4) f).call(a[0], a[1], a[2], a[3]); + } + if (f instanceof Funcs.VoidFunc4) { + ((Funcs.VoidFunc4) f).call(a[0], a[1], a[2], a[3]); + return null; + } + break; + case 5: + if (f instanceof Funcs.Func5) { + return ((Funcs.Func5) f).call(a[0], a[1], a[2], a[3], a[4]); + } + if (f instanceof Funcs.VoidFunc5) { + ((Funcs.VoidFunc5) f).call(a[0], a[1], a[2], a[3], a[4]); + return null; + } + break; + default: + break; + } + throw new NoSuchMethodError("Closure call with mismatched arguments: " + a.length + " argument(s)"); + } + + private static Funcs.Func1 fn1(final Object f) { + return new Funcs.Func1() { + @Override + public Object call(Object x) { + return invoke(f, new Object[] {x}); + } + }; + } + + private static Funcs.Func1 pred(final Object f) { + return new Funcs.Func1() { + @Override + public Boolean call(Object x) { + return Boolean.valueOf(truthy(invoke(f, new Object[] {x}))); + } + }; + } + + // ------------------------------------------------------------------ helpers + + private static boolean truthy(Object v) { + if (v instanceof Boolean) { + return ((Boolean) v).booleanValue(); + } + throw new dart.core.TypeError("type '" + typeName(v) + "' is not a subtype of type 'bool'"); + } + + private static Number num(Object v) { + if (v instanceof Number) { + return (Number) v; + } + throw new dart.core.TypeError("type '" + typeName(v) + "' is not a subtype of type 'num'"); + } + + private static Object arg(Object[] a, int i) { + if (i >= a.length) { + throw new NoSuchMethodError("Missing positional argument " + (i + 1)); + } + return a[i]; + } + + @SuppressWarnings("unchecked") + private static Iterable asIterable(Object o) { + if (o instanceof Iterable) { + return (Iterable) o; + } + throw new dart.core.TypeError("type '" + typeName(o) + "' is not a subtype of type 'Iterable'"); + } + + private static String typeName(Object o) { + return o == null ? "Null" : o.getClass().getName(); + } + + /** + * Validates a dynamic call to a transpiled method against its declaration, as + * Dart does before invoking: {@code minPos..maxPos} positional arguments, only the + * {@code named} parameters, and every one of {@code requiredNamed}. Generated + * {@code $dcall} code calls this; a null array means none. + */ + public static void checkArgs(Object o, String member, Object[] a, java.util.Map n, + int minPos, int maxPos, String[] named, String[] requiredNamed) { + int count = a == null ? 0 : a.length; + if (count < minPos || count > maxPos) { + throw noSuch(o, member); + } + if (n != null) { + for (String k : n.keySet()) { + if (!contains(named, k)) { + throw noSuch(o, member); + } + } + } + if (requiredNamed != null) { + for (String k : requiredNamed) { + if (n == null || !n.containsKey(k)) { + throw noSuch(o, member); + } + } + } + } + + private static boolean contains(String[] names, String k) { + if (names == null) { + return false; + } + for (String s : names) { + if (s.equals(k)) { + return true; + } + } + return false; + } + + private static NoSuchMethodError noSuch(Object o, String member) { + if (o == null) { + return new NoSuchMethodError("The getter or method '" + member + "' was called on null."); + } + return new NoSuchMethodError("Class '" + typeName(o) + "' has no instance member '" + member + "'."); + } +} diff --git a/maven/dart-runtime/src/main/java/dart/runtime/DynamicObject.java b/maven/dart-runtime/src/main/java/dart/runtime/DynamicObject.java new file mode 100644 index 00000000000..5f040d890bc --- /dev/null +++ b/maven/dart-runtime/src/main/java/dart/runtime/DynamicObject.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package dart.runtime; + +import dart.core.DartMap; + +/** + * Implemented by every transpiled class so a member can be reached through a + * {@code dynamic} receiver ({@code json.name}, {@code obj.save(x)}) with no reflection. + * The transpiler generates each method as a switch over the class's own members, then + * defers to its superclass; {@link Dyn#NO_MEMBER} means "not mine". + */ +public interface DynamicObject { + Object $dget(String name); + + /** False when the class has no such setter or field. */ + boolean $dset(String name, Object value); + + Object $dcall(String name, Object[] positional, DartMap named); +} diff --git a/maven/dart-runtime/src/main/java/dart/typed_data/Uint8List.java b/maven/dart-runtime/src/main/java/dart/typed_data/Uint8List.java index 71e03a00d3a..38ffc756cf1 100644 --- a/maven/dart-runtime/src/main/java/dart/typed_data/Uint8List.java +++ b/maven/dart-runtime/src/main/java/dart/typed_data/Uint8List.java @@ -54,6 +54,10 @@ public static Uint8List fromBytes(byte[] bytes) { /** {@code Uint8List.fromList(source)} with a source typed only dynamic. */ public static Uint8List fromList(Object source) { + if (source instanceof Uint8List) { + // a Uint8List IS a List in Dart (utf8.encode returns one), but not a java.util.List here + return fromBytes(((Uint8List) source).bytes); + } if (!(source instanceof List)) { throw new dart.core.TypeError("type '" + (source == null ? "Null" : source.getClass().getName()) + "' is not a subtype of type 'List'"); @@ -81,6 +85,47 @@ public long length() { return bytes.length; } + /** {@code bytes[i]}: the unsigned byte, as Dart reads a Uint8List. */ + public long $index(long index) { + dart.core.RangeError.checkValidIndex(index, bytes.length); + return bytes[(int) index] & 0xff; + } + + public void $indexSet(long index, long value) { + dart.core.RangeError.checkValidIndex(index, bytes.length); + bytes[(int) index] = (byte) value; + } + + public dart.core.DartList toList() { + dart.core.DartList out = new dart.core.DartList(); + for (byte b : bytes) { + out.add(Long.valueOf(b & 0xff)); + } + return out; + } + + public Uint8List sublist(long start) { + return sublist(start, null); + } + + public Uint8List sublist(long start, Long end) { + int e = end == null ? bytes.length : (int) end.longValue(); + if (start < 0 || start > e || e > bytes.length) { + throw new dart.core.RangeError("Invalid range " + start + ".." + e); + } + byte[] out = new byte[e - (int) start]; + System.arraycopy(bytes, (int) start, out, 0, out.length); + return new Uint8List(out); + } + + public boolean isEmpty() { + return bytes.length == 0; + } + + public boolean isNotEmpty() { + return bytes.length != 0; + } + /** The raw backing array (used by image decoders). */ public byte[] toBytes() { return bytes; diff --git a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/analyze/Program.java b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/analyze/Program.java index 67e799ac4ec..f79f9bab149 100644 --- a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/analyze/Program.java +++ b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/analyze/Program.java @@ -165,6 +165,53 @@ public void finishParts() { libraries.add(part); register(part, part); } + Set done = new HashSet(); + for (Ast.Library lib : libraries) { + for (Ast.ClassDecl c : lib.classes) { + forwardSuperCtors(c, done); + } + } + } + + /** + * Gives a mixin application class ({@code class C = S with M;}) one constructor per + * generative constructor of S, each taking S's parameters as {@code super.x} parameters + * and calling the same-named S constructor. S is completed first when it is itself a + * mixin application, so a chain forwards all the way down. + */ + private void forwardSuperCtors(Ast.ClassDecl c, Set done) { + if (!c.forwardSuperCtors || !done.add(c) || c.superclass == null) { + return; + } + Ast.ClassDecl sup = resolveClass(c.superclass.name, c.ownerLibrary); + if (sup == null) { + return; // a stub or JDK superclass: only its default constructor is reachable + } + forwardSuperCtors(sup, done); + for (Ast.CtorDecl sc : sup.ctors) { + if (sc.isFactory || (sc.name != null && sc.name.startsWith("_") + && sup.ownerLibrary != c.ownerLibrary)) { + continue; + } + Ast.CtorDecl fc = new Ast.CtorDecl().at(c.file, c.line, c.col); + fc.name = sc.name; + fc.isConst = sc.isConst; + for (Ast.Param sp : sc.params) { + Ast.Param p = new Ast.Param().at(c.file, c.line, c.col); + p.type = sp.type; + p.name = sp.name; + p.named = sp.named; + p.required = sp.required; + p.optional = sp.optional; + p.isSuper = true; + p.defaultValue = sp.defaultValue; + fc.params.add(p); + } + Ast.SuperInit si = new Ast.SuperInit(); + si.namedCtor = sc.name; + fc.superInit = si; + c.ctors.add(fc); + } } private boolean ownsPart(Ast.Library owner, Ast.Library part) { @@ -400,7 +447,18 @@ public Ast.Library resolveImportedLibrary(Ast.Library from, String uri) { // package:/x.dart is this project's lib/x.dart; any other package is // external (a stub or unsupported) and never a user library. int slash = uri.indexOf('/'); - if (slash < 0 || !ownPackages.contains(uri.substring("package:".length(), slash))) { + if (slash < 0) { + return null; + } + if (!ownPackages.contains(uri.substring("package:".length(), slash))) { + // A pub package transpiled with the app: cn1:import-flutter-project copies + // the files the app reaches to packages//, beside the app's own. + String inPackage = normalizePath("packages/" + uri.substring("package:".length())); + for (Ast.Library lib : libraries) { + if (normalizePath(lib.fileName.replace('\\', '/')).equals(inPackage)) { + return lib; + } + } return null; } String rest = normalizePath(uri.substring(slash + 1)); diff --git a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/DartTranspiler.java b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/DartTranspiler.java index 1058db09cb9..4eccc79b3a1 100644 --- a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/DartTranspiler.java +++ b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/DartTranspiler.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -67,8 +68,18 @@ public TranspileResult transpile(TranspileRequest req) { // The output PACKAGE is part of what was generated, so it is part of the // key: changing cn1.flutter.package alone used to leave every generated // source declared in the old package, because nothing else had changed. + // The packs the app's own Java names decide output too, and editing that Java + // touches no Dart file, so they are part of the key. + Set javaPacks; + try { + javaPacks = PluginPacks.namedByJava(req.javaSourceRoots); + } catch (IOException e) { + diags.error(null, 0, 0, "E0005", "Cannot read application Java sources: " + e); + return new TranspileResult(diags.asList(), new ArrayList(), false); + } + // "-pkg:" stays LAST: removePreviousPackage reads the package back from it. String digest = digest(dartFiles) + stubDigest(req.stubClasspath) - + "-pkg:" + req.packageName; + + "-java:" + javaPacks + "-pkg:" + req.packageName; if (req.stateFile != null && req.stateFile.exists() && req.outputDir != null && req.outputDir.exists()) { try { List lines = Files.readAllLines(req.stateFile.toPath(), StandardCharsets.UTF_8); @@ -120,8 +131,24 @@ && generatedFilesStillMatch(req, lines)) { JavaEmitter emitter = new JavaEmitter(program, stubs, diags, req.packageName); List files = emitter.emit(); + Map packSources = new java.util.TreeMap(); + if (!diags.hasErrors() && req.outputDir != null) { + try { + List roots = new ArrayList(); + for (GeneratedFile gf : files) { + roots.add(gf.content); + } + for (String p : javaPacks) { + roots.add(PluginPacks.PACKAGE + p + "."); + } + packSources = pluginPackSources(req.stubClasspath, roots); + } catch (IOException e) { + diags.error(null, 0, 0, "E0005", "Cannot read plugin pack sources: " + e); + } + } if (!diags.hasErrors() && req.outputDir != null) { writeOutput(req, files, diags); + writePackSources(req, packSources, diags); if (req.stateFile != null && diags.hasErrors()) { // A file failed to write, so the output does not match these inputs. // Recording the digest anyway sent the next unchanged build down the @@ -134,7 +161,8 @@ && generatedFilesStillMatch(req, lines)) { } else if (req.stateFile != null) { try { req.stateFile.getParentFile().mkdirs(); - Files.write(req.stateFile.toPath(), stateFileContent(digest, files).getBytes(StandardCharsets.UTF_8)); + Files.write(req.stateFile.toPath(), + stateFileContent(digest, files, packSources).getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { diags.warn(null, "W0001", "Could not write transpiler state file: " + e); } @@ -442,15 +470,81 @@ private static void collectFiles(File dir, List out) { * -- a compile failure the transpiler itself reported as "up to date". Recording the * file set here lets a cache hit verify it is still true before trusting it.

*/ - private static String stateFileContent(String digest, List files) { + private static String stateFileContent(String digest, List files, + Map packSources) { StringBuilder sb = new StringBuilder(digest); for (GeneratedFile gf : files) { sb.append('\n').append(gf.relativePath).append('\t') .append(gf.content.getBytes(StandardCharsets.UTF_8).length); } + // Pack sources live under the output root, not the package: PACK_LINE marks them. + for (Map.Entry e : packSources.entrySet()) { + sb.append('\n').append(PACK_LINE).append(e.getKey()).append('\t') + .append(e.getValue().getBytes(StandardCharsets.UTF_8).length); + } return sb.toString(); } + private static final String PACK_LINE = "@"; + + /** + * The flutter-runtime plugin pack sources {@code roots} need -- the packs they name, + * and the packs those name -- keyed by path relative to a Java source root. The + * transpiler writes these beside its output; a caller that drives the emitter itself + * (a test harness, a benchmark) must compile them with the generated code too. + */ + public static Map pluginPackSources(List stubClasspath, + Iterable roots) throws IOException { + PluginPacks packs = PluginPacks.load(stubClasspath); + return packs.sources(packs.closure(roots)); + } + + /** + * Writes the selected plugin pack sources under the output root and deletes any + * pack file an earlier run copied that this one did not select -- an import the app + * dropped must take its pack (and the permissions its classes imply) with it. + */ + private static void writePackSources(TranspileRequest req, Map packSources, + Diagnostics diags) { + for (Map.Entry e : packSources.entrySet()) { + File out = new File(req.outputDir, e.getKey().replace('/', File.separatorChar)); + out.getParentFile().mkdirs(); + try { + byte[] content = e.getValue().getBytes(StandardCharsets.UTF_8); + if (out.exists() && Arrays.equals(Files.readAllBytes(out.toPath()), content)) { + continue; + } + Files.write(out.toPath(), content); + } catch (IOException ex) { + diags.error(e.getKey(), 0, 0, "E0004", "Cannot write plugin pack source: " + ex); + } + } + File root = new File(req.outputDir, PluginPacks.PATH.replace('/', File.separatorChar)); + sweepPacks(req.outputDir, root, packSources.keySet()); + } + + private static void sweepPacks(File outputDir, File dir, Set keep) { + File[] children = dir.listFiles(); + if (children == null) { + return; + } + String base = outputDir.getPath() + File.separator; + for (File c : children) { + if (c.isDirectory()) { + sweepPacks(outputDir, c, keep); + String[] left = c.list(); + if (left != null && left.length == 0) { + c.delete(); + } + } else { + String rel = c.getPath().substring(base.length()).replace(File.separatorChar, '/'); + if (!keep.contains(rel)) { + c.delete(); + } + } + } + } + /** * Validates the generated-file lines a matching-digest state file recorded (see * {@link #stateFileContent}) against what is actually in {@code req.outputDir} right @@ -474,7 +568,10 @@ private static boolean generatedFilesStillMatch(TranspileRequest req, List}), which + * reach an application only through the transpiler. + * + *

The runtime jar carries each pack as Java SOURCE under {@link #ROOT}, never as + * classes. A pack is copied into the transpiler's output when the generated code names + * it, when the application's own Java does (the import-generated entry point installs + * the firebase and app_links hooks), or when another copied pack does (share uses + * crossfile). Everything else is simply never generated, so an app that does not import + * geolocator contains no class naming {@code LocationManager}, and the builders -- which + * derive permissions, manifest entries and native dependencies from the classes they + * find -- have nothing to prune.

+ */ +final class PluginPacks { + /** Where a runtime jar (or class directory) keeps the pack sources. */ + static final String ROOT = "META-INF/dart/java/"; + static final String PACKAGE = "com.codename1.flutter.plugins."; + static final String PATH = "com/codename1/flutter/plugins/"; + + /** pack name -> (path under {@link #ROOT} -> source). */ + private final Map> packs = new TreeMap>(); + + private PluginPacks() { + } + + static PluginPacks load(List stubClasspath) throws IOException { + PluginPacks p = new PluginPacks(); + for (File entry : stubClasspath) { + if (entry.isDirectory()) { + File root = new File(entry, ROOT + PATH); + List files = new ArrayList(); + collect(root, files); + String base = new File(entry, ROOT).getPath() + File.separator; + for (File f : files) { + String rel = f.getPath().substring(base.length()).replace(File.separatorChar, '/'); + p.add(rel, new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8)); + } + } else if (entry.isFile() && entry.getName().endsWith(".jar")) { + ZipFile zip = new ZipFile(entry); + try { + java.util.Enumeration en = zip.entries(); + while (en.hasMoreElements()) { + ZipEntry ze = en.nextElement(); + String n = ze.getName(); + if (!ze.isDirectory() && n.startsWith(ROOT + PATH) && n.endsWith(".java")) { + p.add(n.substring(ROOT.length()), read(zip.getInputStream(ze))); + } + } + } finally { + zip.close(); + } + } + } + return p; + } + + private void add(String rel, String source) { + if (!rel.startsWith(PATH) || !rel.endsWith(".java")) { + return; + } + int slash = rel.indexOf('/', PATH.length()); + if (slash < 0) { + return; + } + String pack = rel.substring(PATH.length(), slash); + Map files = packs.get(pack); + if (files == null) { + files = new TreeMap(); + packs.put(pack, files); + } + // First classpath entry wins, as it would for a class. + if (!files.containsKey(rel)) { + files.put(rel, source); + } + } + + /** + * The packs {@code roots} name, plus every pack those name in turn. {@code roots} + * is the generated code and the application's own Java. + */ + Set closure(Iterable roots) { + Set kept = new TreeSet(); + List todo = new ArrayList(); + for (CharSequence text : roots) { + for (String n : named(text)) { + if (packs.containsKey(n) && kept.add(n)) { + todo.add(n); + } + } + } + while (!todo.isEmpty()) { + String pack = todo.remove(todo.size() - 1); + for (String src : packs.get(pack).values()) { + for (String n : named(src)) { + if (packs.containsKey(n) && kept.add(n)) { + todo.add(n); + } + } + } + } + return kept; + } + + /** The sources of {@code selected}, keyed by path relative to the output root. */ + Map sources(Set selected) { + Map out = new TreeMap(); + for (String pack : selected) { + out.putAll(packs.get(pack)); + } + return out; + } + + /** + * Every pack name {@code text} spells as {@code com.codename1.flutter.plugins.}, + * qualified or in an import. A mention in a comment over-includes a pack, which costs + * size, never a failed build. + */ + static Set named(CharSequence text) { + Set out = new TreeSet(); + String s = text.toString(); + int at = s.indexOf(PACKAGE); + while (at >= 0) { + int start = at + PACKAGE.length(); + int end = start; + while (end < s.length() && Character.isJavaIdentifierPart(s.charAt(end))) { + end++; + } + if (end > start) { + out.add(s.substring(start, end)); + } + at = s.indexOf(PACKAGE, end); + } + return out; + } + + /** The packs the Java sources under {@code roots} name. */ + static Set namedByJava(List roots) throws IOException { + Set out = new TreeSet(); + for (File root : roots) { + List files = new ArrayList(); + collect(root, files); + for (File f : files) { + out.addAll(named(new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8))); + } + } + return out; + } + + private static void collect(File dir, List out) { + File[] children = dir.listFiles(); + if (children == null) { + return; + } + java.util.Arrays.sort(children); + for (File c : children) { + if (c.isDirectory()) { + collect(c, out); + } else if (c.getName().endsWith(".java")) { + out.add(c); + } + } + } + + private static String read(InputStream in) throws IOException { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + byte[] chunk = new byte[8192]; + int n; + while ((n = in.read(chunk)) > 0) { + buf.write(chunk, 0, n); + } + return new String(buf.toByteArray(), StandardCharsets.UTF_8); + } finally { + in.close(); + } + } +} diff --git a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/TranspileRequest.java b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/TranspileRequest.java index 14123ec2b5f..786a10bed30 100644 --- a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/TranspileRequest.java +++ b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/api/TranspileRequest.java @@ -44,6 +44,12 @@ public final class TranspileRequest { public String packageName = "com.codename1.generated.flutter"; /** Fast-skip digest state file; null disables the check. */ public File stateFile; + /** + * The application's own Java source roots. A plugin pack they name is copied into + * the output even when no Dart code calls it (the import-generated entry point + * installs the firebase_messaging and app_links hooks). Never the output dir. + */ + public final List javaSourceRoots = new ArrayList(); public TranspileRequest sourceRoot(File root) { sourceRoots.add(root); @@ -65,6 +71,11 @@ public TranspileRequest packageName(String pkg) { return this; } + public TranspileRequest javaSourceRoot(File root) { + javaSourceRoots.add(root); + return this; + } + public TranspileRequest stateFile(File f) { this.stateFile = f; return this; diff --git a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/ast/Ast.java b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/ast/Ast.java index ec7d10de784..de6a6f2b339 100644 --- a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/ast/Ast.java +++ b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/ast/Ast.java @@ -178,10 +178,13 @@ public static class ClassDecl extends Node { public boolean isAbstract; public boolean isSealed; // Dart 3: sealed class C { } public boolean isMixin; // mixin M { } + public List mixinOn = new ArrayList(); // mixin M on S1, S2 public TypeRef extensionOn; // extension X on T { } — non-null marks an extension /** An `extension on T` with no name: no show combinator can import it, no hide drop it. */ public boolean unnamedExtension; public List mixins = new ArrayList(); // class C with M1, M2 + /** {@code class C = S with M;}: C gets a forwarding constructor for each of S's. */ + public boolean forwardSuperCtors; public TypeRef superclass; // null if none/Object public List interfaces = new ArrayList(); public List typeParams = new ArrayList(); @@ -309,6 +312,7 @@ public static class Param extends Node { public String name; public boolean named; public boolean required; + public boolean optional; // [optional positional] public boolean isThis; // this.x public boolean isSuper; // super.x public Expr defaultValue; // nullable @@ -321,6 +325,7 @@ public static class CtorDecl extends Node { public List params = new ArrayList(); public List fieldInits = new ArrayList(); // initializer list entries public SuperInit superInit; // nullable + public SuperInit redirect; // `: this(...)` / `: this.named(...)`; nullable public Block body; // nullable (";" body) } @@ -346,6 +351,7 @@ public static class MethodDecl extends Node { public boolean isAbstract; // no body public boolean isAsync; public boolean isSyncStar; // sync* generator body + public boolean isAsyncStar; // async* generator body public Block body; // nullable when isAbstract or expression-bodied public Expr exprBody; // for `=> expr` } @@ -357,6 +363,7 @@ public static class FunctionDecl extends Node { public boolean isExternal; public boolean isAsync; public boolean isSyncStar; // sync* generator body + public boolean isAsyncStar; // async* generator body public boolean isGetter; // top-level `T get x => ...` public boolean isSetter; // top-level `set x(v) { ... }` public List typeParams = new ArrayList(); // generic function: fn(...) @@ -419,6 +426,7 @@ public static class ForInStmt extends Stmt { public TypeRef varType; // may be VAR public String varName; public Pattern pattern; // Dart 3 pattern for-in (destructuring); null for simple var + public boolean isAwait; // await for (x in stream) public Expr iterable; public Stmt body; } @@ -455,6 +463,8 @@ public static class LocalFunc extends Stmt { public Block body; // nullable when expression-bodied public Expr exprBody; // for `=> expr` public boolean isAsync; + public boolean isSyncStar; // Iterable f() sync* { ... } + public boolean isAsyncStar; // Stream f() async* { ... } } // ------------------------------------------------------------------ @@ -673,6 +683,9 @@ public static class AsCast extends Expr { public static class Lambda extends Expr { public boolean isAsync; + public boolean isSyncStar; // () sync* { ... } + public boolean isAsyncStar; // () async* { ... } + public List typeParams = new ArrayList(); // (T x) => ... (erased) public List params = new ArrayList(); public Block body; // nullable public Expr exprBody; // for `=> expr` diff --git a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java index 91be38025ff..5097407e9bd 100644 --- a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java +++ b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/codegen/JavaEmitter.java @@ -96,7 +96,49 @@ private void emitCrash(Library lib, String what, Throwable ex) { + (ex.getMessage() != null ? ": " + ex.getMessage() : "") + at); } + /// Marks a case of the $dget/$dset/$dcall switches. The switches are emitted while the + /// program's dynamic accesses are still being found, so each case is marked and + /// pruneDynamicCases drops, after the whole program is emitted, every case whose + /// name no `dynamic` access in the program uses. A case keeps its member alive + /// against ParparVM's dead-code removal, and unpruned they were most of a 20% + /// native size regression (one case per localized string, for instance). + private static final String DYN_CASE = "/*dyn*/"; + + /// Every member name a `dynamic` receiver is read, written or called with. + private final java.util.Set dynNames = new java.util.HashSet(); + + private List pruneDynamicCases(List files) { + List pruned = new ArrayList(files.size()); + for (GeneratedFile g : files) { + if (g == null || g.content.indexOf(DYN_CASE) < 0) { + pruned.add(g); + continue; + } + StringBuilder sb = new StringBuilder(g.content.length()); + for (String line : g.content.split("\n", -1)) { + int at = line.indexOf(DYN_CASE + "case \""); + if (at >= 0) { + int start = at + DYN_CASE.length() + 6; + String name = line.substring(start, line.indexOf('"', start)); + // a case spanning lines (a closure default) is kept rather than cut in half + if (!dynNames.contains(name) && line.trim().endsWith(";")) { + continue; + } + line = line.substring(0, at) + line.substring(at + DYN_CASE.length()); + } + sb.append(line).append('\n'); + } + sb.setLength(sb.length() - 1); + pruned.add(new GeneratedFile(g.relativePath, sb.toString())); + } + return pruned; + } + public List emit() { + return pruneDynamicCases(emitAll()); + } + + private List emitAll() { List out = new ArrayList(); // Every library with a top-level main(), collected in source order (which is itself // sorted by path -- see DartTranspiler.transpile -- not necessarily the order a @@ -359,6 +401,7 @@ private GeneratedFile emitEnum(EnumDecl e) { mm.isAbstract = m.isAbstract; mm.isAsync = m.isAsync; mm.isSyncStar = m.isSyncStar; + mm.isAsyncStar = m.isAsyncStar; mm.returnType = m.returnType; mm.params = m.params; mm.typeParams = m.typeParams; @@ -695,6 +738,7 @@ private GeneratedFile emitLibClass(Library lib) { m.isStatic = true; m.isAsync = f.isAsync; m.isSyncStar = f.isSyncStar; + m.isAsyncStar = f.isAsyncStar; m.name = f.name.equals("main") ? "main$" : f.name; m.returnType = f.returnType; m.params = f.params; @@ -926,6 +970,7 @@ private GeneratedFile emitClass(ClassDecl c) { mm.isAbstract = m.isAbstract; mm.isAsync = m.isAsync; mm.isSyncStar = m.isSyncStar; + mm.isAsyncStar = m.isAsyncStar; mm.returnType = m.returnType; // Dart lets a value-returning method override a void one; Java forbids it, so pin the // override's return to void to keep it a valid override. @@ -1024,6 +1069,23 @@ private GeneratedFile emitClass(ClassDecl c) { impls.append(javaType(itf, false, ctx)); emitChangeNotifierState(itf, body); } + // jsonEncode calls toJson() on what it cannot encode itself; Dart does that + // dynamically, the runtime through this interface. + MethodDecl toJson = c.method("toJson"); + if (toJson != null && !toJson.isStatic && toJson.params.isEmpty() && !c.isMixin + && (toJson.returnType == null || !(toJson.returnType.is("int") || toJson.returnType.is("double") + || toJson.returnType.is("bool") || toJson.returnType.is("void")))) { + if (impls.length() > 0) { + impls.append(", "); + } + impls.append("dart.convert.JsonEncodable"); + } + if (!c.isMixin && emitDynamicMembers(c, body, ctx)) { + if (impls.length() > 0) { + impls.append(", "); + } + impls.append("dart.runtime.DynamicObject"); + } body.append(emitStubGetterBridges(c, ctx)); // Dart 3 sealed → Java sealed: a sealed class with subtypes lists them in a permits clause and // its direct subtypes are marked non-sealed. Falls back to a plain abstract class when the @@ -1079,6 +1141,246 @@ private GeneratedFile emitClass(ClassDecl c) { permits, body, ctx, c.file); } + /** + * The {@code $dget}/{@code $dset}/{@code $dcall} members through which dart.runtime.Dyn + * reaches this class's fields, getters and methods from a {@code dynamic} receiver. + * Each falls back to the superclass's when that is a program class, and answers + * {@code Dyn.NO_MEMBER} otherwise. A method is reachable when it is an instance method + * with no type parameters; named and optional parameters are filled from the call. + * + * @return whether this class declares the interface itself (no program superclass) + */ + private boolean emitDynamicMembers(ClassDecl c, StringBuilder body, Ctx ctx) { + boolean inherits = c.superclass != null && program.classes.containsKey(c.superclass.name) + && !program.classes.get(c.superclass.name).isMixin; + // A class extending a framework or runtime class (a widget, a State, a + // BoxPainter, a RestorableProperty) is not the model data dynamic code reaches + // into, and its overrides of stub methods have no return type of their own to + // dispatch with; leave it out. A pure value base -- Equatable -- is the + // exception: `dynamic pet; pet.name` on a `Pet extends Equatable` is model data. + if (c.superclass != null && !inherits && !MODEL_BASES.contains(c.superclass.name)) { + return false; + } + if (inherits && !hasDynamicMembers(program.classes.get(c.superclass.name))) { + return false; + } + TypeRef self = new TypeRef(c.name); + for (String tp : c.typeParams) { + self.args.add(new TypeRef(tp)); + } + Out thisOut = new Out("this", self); + java.util.Set seen = new java.util.HashSet(); + StringBuilder get = new StringBuilder(); + StringBuilder set = new StringBuilder(); + StringBuilder call = new StringBuilder(); + for (FieldDecl f : c.fields) { + if (f.isStatic || !seen.add(f.name)) { + continue; + } + get.append(" ").append(DYN_CASE).append("case \"").append(f.name).append("\": return this.get$").append(f.name) + .append("();\n"); + if (!f.isFinal && !f.isConst) { + TypeRef ft = fieldType(f, ctx); + set.append(" ").append(DYN_CASE).append("case \"").append(f.name).append("\": this.set$").append(f.name).append('(') + .append(dynCast("v", ft, ctx)).append("); return true;\n"); + } + } + for (MethodDecl m : c.methods) { + if (m.isStatic || m.name.startsWith("$")) { + continue; + } + if (m.isSetter) { + // `dynamic a = A(); a.value = 3` reaches an explicit setter as it would a field. + if (m.params.size() == 1 && seen.add("set " + m.name)) { + TypeRef pt = m.params.get(0).type == null || m.params.get(0).type.is("var") + ? TypeRef.DYNAMIC : m.params.get(0).type; + set.append(" ").append(DYN_CASE).append("case \"").append(m.name).append("\": this.") + .append(javaMethodName(m.name)).append('(').append(dynCast("v", pt, ctx)) + .append("); return true;\n"); + } + continue; + } + // A generic method called dynamically gets no type arguments (Dart infers + // dynamic), so an argument typed by one of its type parameters is passed erased: + // T as Object, List as a raw list -- the parameters are not in scope here. + java.util.Set methodTypeParams = new java.util.HashSet(m.typeParams); + if (m.isGetter) { + if (seen.add(m.name)) { + get.append(" ").append(DYN_CASE).append("case \"").append(m.name).append("\": return this.") + .append(javaMethodName(m.name)).append("();\n"); + } + continue; + } + if (!seen.add("()" + m.name)) { + continue; + } + StringBuilder args = new StringBuilder(); + int positional = 0; + for (Param p : m.params) { + if (!p.named) { + positional++; + } + } + int pi = 0; + for (Param p : m.params) { + if (args.length() > 0) { + args.append(", "); + } + TypeRef pt = p.type == null || p.type.is("var") ? TypeRef.DYNAMIC : p.type; + String raw = p.named ? "(n == null ? null : n.idx(\"" + p.name + "\"))" + : "(a.length > " + pi + " ? a[" + pi + "] : null)"; + String cast; + if (mentionsAny(pt, methodTypeParams)) { + cast = pt.args.isEmpty() && methodTypeParams.contains(pt.name) ? raw + : "(" + erasedJavaType(pt, ctx) + ") dart.runtime.DartRuntime.as(" + raw + ", " + + erasedJavaType(pt, ctx) + ".class, " + pt.nullable + ", " + + quote(pt.toString()) + ")"; + } else { + cast = dynCast(raw, pt, ctx); + } + if (p.defaultValue != null && !mentionsAny(pt, methodTypeParams)) { + // An omitted argument takes the declared default, as a static call + // would: null would be the wrong value, and fails unboxing an int. + String present = p.named ? "(n != null && n.containsKey(\"" + p.name + "\"))" + : "(a.length > " + pi + ")"; + cast = "(" + present + " ? " + cast + " : " + + coerce(emitExpr(p.defaultValue, pt, ctx), pt, ctx) + ")"; + } + if (!p.named) { + pi++; + } + args.append(cast); + } + TypeRef rt = m.returnType == null ? TypeRef.DYNAMIC : m.returnType; + String invoke = "this." + javaMethodName(m.name) + "(" + args + ")"; + call.append(" ").append(DYN_CASE).append("case \"").append(m.name).append("\": "); + // Dart checks a dynamic call's shape before running the method: a missing + // required argument, an extra positional one or an unknown name is a + // NoSuchMethodError, never a silent null or a later unboxing failure. + int minPos = 0; + StringBuilder named = new StringBuilder(); + StringBuilder requiredNamed = new StringBuilder(); + for (Param p : m.params) { + if (p.named) { + named.append(named.length() > 0 ? ", " : "").append('"').append(p.name).append('"'); + if (p.required) { + requiredNamed.append(requiredNamed.length() > 0 ? ", " : "").append('"').append(p.name).append('"'); + } + } else if (!p.optional && p.defaultValue == null) { + minPos++; + } + } + call.append("dart.runtime.Dyn.checkArgs(this, \"").append(m.name).append("\", a, n, ").append(minPos).append(", ") + .append(positional).append(", ") + .append(named.length() == 0 ? "null" : "new String[] {" + named + "}").append(", ") + .append(requiredNamed.length() == 0 ? "null" : "new String[] {" + requiredNamed + "}").append("); "); + if (rt.is("void") || overridesVoid(c, m.name)) { + call.append(invoke).append("; return null;\n"); + } else { + call.append("return ").append(invoke).append(";\n"); + } + } + String noMember = inherits ? null : "dart.runtime.Dyn.NO_MEMBER"; + body.append(" @Override\n @SuppressWarnings(\"unchecked\")\n public Object $dget(String name) {\n switch (name) {\n").append(get) + .append(" default: return ").append(noMember == null ? "super.$dget(name)" : noMember) + .append(";\n }\n }\n\n"); + body.append(" @Override\n @SuppressWarnings(\"unchecked\")\n public boolean $dset(String name, Object v) {\n switch (name) {\n") + .append(set).append(" default: return ").append(inherits ? "super.$dset(name, v)" : "false") + .append(";\n }\n }\n\n"); + body.append(" @Override\n @SuppressWarnings(\"unchecked\")\n public Object $dcall(String name, Object[] a, dart.core.DartMap n) {\n" + + " switch (name) {\n").append(call).append(" default: return ") + .append(noMember == null ? "super.$dcall(name, a, n)" : noMember).append(";\n }\n }\n\n"); + return !inherits; + } + + /** Whether {@code t} names any of {@code vars}, at any depth of its type arguments. */ + private static boolean mentionsAny(TypeRef t, java.util.Set vars) { + if (t == null || vars.isEmpty()) { + return false; + } + if (t.name != null && vars.contains(t.name)) { + return true; + } + for (TypeRef a : t.args) { + if (mentionsAny(a, vars)) { + return true; + } + } + if (mentionsAny(t.funcReturn, vars)) { + return true; + } + if (t.funcParams != null) { + for (TypeRef a : t.funcParams) { + if (mentionsAny(a, vars)) { + return true; + } + } + } + return false; + } + + /** Whether {@link #emitDynamicMembers} gives this program class its dynamic members. */ + private boolean hasDynamicMembers(ClassDecl c) { + java.util.Set guard = new java.util.HashSet(); + while (c != null && guard.add(c.name)) { + if (c.isMixin) { + return false; + } + if (c.superclass == null) { + return true; + } + c = program.classes.get(c.superclass.name); + } + return false; + } + + /// A dynamic value converted to the Java form of a parameter or field type, unboxing + /// primitives. Checked through DartRuntime.as (an instanceof test that throws Dart's + /// TypeError): a bare Java cast is unchecked on ParparVM, so `d.setCount("3")` + /// against an int parameter called longValue() on a String -- a native crash no + /// Dart catch could see. A type variable stays an unchecked cast, as `as T` does. + private String dynCast(String raw, TypeRef t, Ctx ctx) { + if (t == null || isDynamicType(t)) { + return raw; + } + if (isTypeVariable(t, ctx)) { + return "(" + javaType(boxType(t), true, ctx) + ") " + raw; + } + String checked; + if (t.is("int") || t.is("double") || t.is("num")) { + checked = "dart.runtime.DartRuntime.as(" + raw + ", Number.class, " + t.nullable + ", " + + quote(t.toString()) + ")"; + if (t.nullable || t.is("num")) { + return t.is("num") ? checked + : "(" + javaType(boxType(t), true, ctx) + ") dart.runtime.Dyn.numAs(" + checked + ", " + + t.is("int") + ")"; + } + return checked + "." + (t.is("int") ? "longValue()" : "doubleValue()"); + } + String erased = erasedJavaType(t, ctx); + checked = "dart.runtime.DartRuntime.as(" + raw + ", " + erased + ".class, " + t.nullable + ", " + + quote(t.toString()) + ")"; + if (t.is("bool") && !t.nullable) { + return checked + ".booleanValue()"; + } + String jt = javaType(boxType(t), true, ctx); + return jt.equals(erased) ? checked : "((" + jt + ") " + checked + ")"; + } + + /** Whether a program superclass declares {@code name} void, which a Java override keeps. */ + private boolean overridesVoid(ClassDecl c, String name) { + java.util.Set guard = new java.util.HashSet(); + ClassDecl k = c.superclass == null ? null : program.classes.get(c.superclass.name); + while (k != null && guard.add(k.name)) { + MethodDecl sm = k.method(name); + if (sm != null && sm.returnType != null && sm.returnType.is("void")) { + return true; + } + k = k.superclass == null ? null : program.classes.get(k.superclass.name); + } + return false; + } + /** * A program class Java can stand in for an interface by extending it: abstract, with * no superclass or mixins of its own, no instance fields, no member with a body and @@ -1105,8 +1407,14 @@ private boolean isPureInterface(ClassDecl c) { } } for (CtorDecl ct : c.ctors) { - if (ct.isFactory || !ct.params.isEmpty() || !ct.fieldInits.isEmpty() - || ct.superInit != null || ct.body != null) { + // A factory is a static method, so extending the class inherits nothing from + // it: `abstract class Shape { factory Shape.circle(..) = Circle; }` stays an + // interface, which is the shape every freezed model has. + if (ct.isFactory) { + continue; + } + if (!ct.params.isEmpty() || !ct.fieldInits.isEmpty() + || ct.superInit != null || ct.redirect != null || ct.body != null) { return false; } } @@ -1229,6 +1537,28 @@ private String emitCtor(ClassDecl c, CtorDecl ct, Ctx ctx) { } } else if (ct.superInit != null && ct.superInit.args != null && stubClassOf(c.superclass) != null) { + // Positional arguments go to the stub superclass's Java constructor -- + // `class C extends Cubit { C() : super(0); }` -- which a stub declares when + // its runtime class has no no-arg constructor. Named ones stay setters. + // Only when the stub declares an unnamed constructor taking positionals: one + // that declares none (the gallery's BoxPainter) has just Java's no-arg one, + // and the Dart super arguments have nowhere to go. + if (!ct.superInit.args.positional.isEmpty() && ct.superInit.namedCtor == null + && stubTakesPositionals(stubClassOf(c.superclass))) { + StringBuilder sa = new StringBuilder(); + for (Expr e : ct.superInit.args.positional) { + if (sa.length() > 0) { + sa.append(", "); + } + sa.append(emitExpr(e, null, ctx).code); + } + inSuperInitializer = true; + try { + w.line("super(" + sa + ");"); + } finally { + inSuperInitializer = false; + } + } for (NamedArg na : ct.superInit.args.named) { Out v = emitExpr(na.value, null, ctx); w.line("this." + na.name + "(" + v.code + ");"); @@ -1270,11 +1600,30 @@ && stubClassOf(c.superclass) != null) { * the class; unnamed factories become {@code $create} and call sites * route through it. */ + /** + * A generic class's static constructor method cannot see the class's type variables, so + * it declares its own of the same names: {@code public static Box of(T v)}. + */ + private static String staticTypeParams(ClassDecl c) { + if (c.typeParams.isEmpty()) { + return ""; + } + return "<" + String.join(", ", c.typeParams) + "> "; + } + + private static String staticTypeArgs(ClassDecl c) { + if (c.typeParams.isEmpty()) { + return ""; + } + return "<" + String.join(", ", c.typeParams) + ">"; + } + private String emitFactoryCtor(ClassDecl c, CtorDecl ct, Ctx ctx) { StringBuilder sb = new StringBuilder(); String name = ct.name == null ? "$create" : javaIdent(ct.name); ctx.pushScope(); - sb.append(" public static ").append(javaClassName(c)).append(' ').append(name).append('('); + sb.append(" public static ").append(staticTypeParams(c)).append(javaClassName(c)) + .append(staticTypeArgs(c)).append(' ').append(name).append('('); appendParams(sb, c, ct.params, ctx); sb.append(") {\n"); ctx.pushWriter(2); @@ -1311,7 +1660,8 @@ private String emitNamedCtor(ClassDecl c, CtorDecl ct, Ctx ctx) { paramSig.append(javaType(pt, false, ctx)).append(' ').append(jn); argList.append(jn); } - sb.append(" public static ").append(javaClassName(c)).append(' ').append(javaIdent(ct.name)) + sb.append(" public static ").append(staticTypeParams(c)).append(javaClassName(c)) + .append(staticTypeArgs(c)).append(' ').append(javaIdent(ct.name)) .append('(').append(paramSig).append(") {\n"); sb.append(" ").append(javaClassName(c)).append(" $self = new ").append(javaClassName(c)).append("(($NamedCtor) null);\n"); sb.append(" $self.$init$").append(ct.name).append('(').append(argList).append(");\n"); @@ -1368,6 +1718,15 @@ private String emitUnnamedInit(ClassDecl c, Ctx ctx) { */ private void emitInitBody(ClassDecl c, CtorDecl ct, Ctx ctx) { Ctx.Writer w = ctx.writer(); + if (ct.redirect != null) { + // `C.a(x) : this.b(x, 0);` runs b's initialization in place of its own: b stores the + // fields and calls the superclass, so this one does nothing else. + String target = ct.redirect.namedCtor; + CtorDecl targetCtor = target != null ? c.namedCtor(target) : c.defaultCtor(); + w.line("this.$init$" + (target != null ? target : "") + "(" + + canonicalArgs(targetCtor, ct.redirect.args, ctx) + ");"); + return; + } ClassDecl progSuper = c.superclass != null ? program.classes.get(c.superclass.name) : null; for (Param p : ct.params) { if (p.isThis) { @@ -1495,6 +1854,7 @@ private static class Method { boolean isAbstract; boolean isAsync; boolean isSyncStar; + boolean isAsyncStar; TypeRef returnType; List typeParams = new ArrayList(); List params = new ArrayList(); @@ -1898,6 +2258,59 @@ private String emitMethodLike(Method m, Ctx ctx, boolean classIsAbstract) { ? CaptureScan.boxedLocals(m.body) : CaptureScan.boxedLocals(m.exprBody)); ctx.valueCalledLocals.clear(); ctx.valueCalledLocals.addAll(CaptureScan.valueCalledNames(m.body, m.exprBody)); + if (m.isAsyncStar && !m.isSetter) { + // async* generator. As for sync*, the body moves into a helper, here taking the + // generator's AsyncStar.Sink: `yield x` -> sink.yield$(x), `yield* s` -> + // sink.yieldAll(s). AsyncStar.asyncStar starts it when the stream is listened to, + // on the event loop, where its awaits park like any async body's. + ctx.importClass("dart.async.AsyncStar"); + TypeRef elem = rt.is("Stream") && !rt.args.isEmpty() ? rt.arg(0) : TypeRef.DYNAMIC; + String ej = javaType(elem, true, ctx); + String sink = ctx.newTemp(); + String helper = m.name + "$asyncStar"; + ctx.writer().line("return AsyncStar.<" + ej + ">asyncStar((AsyncStar.Sink<" + ej + "> " + sink + + ") -> " + helper + "(" + sink + paramNames + "));"); + sb.append(ctx.popWriter()); + sb.append(" }\n\n"); + StringBuilder hb = new StringBuilder(" private "); + if (m.isStatic) { + hb.append("static "); + } + if (m.typeParams != null && !m.typeParams.isEmpty()) { + hb.append('<'); + for (int i = 0; i < m.typeParams.size(); i++) { + if (i > 0) { + hb.append(", "); + } + hb.append(m.typeParams.get(i)); + } + hb.append("> "); + } + hb.append("void ").append(helper).append("(AsyncStar.Sink<").append(ej).append("> ").append(sink) + .append(paramDecls).append(") {\n"); + ctx.pushWriter(2); + ctx.methodReturnType = TypeRef.VOID; + ctx.inAsyncBody = true; + String savedList = ctx.syncStarList; + String savedSink = ctx.asyncStarSink; + TypeRef savedElem = ctx.syncStarElem; + ctx.syncStarList = null; + ctx.asyncStarSink = sink; + ctx.syncStarElem = elem; + if (m.body != null) { + emitStatements(m.body, ctx); + } + ctx.syncStarList = savedList; + ctx.asyncStarSink = savedSink; + ctx.syncStarElem = savedElem; + hb.append(ctx.popWriter()); + hb.append(" }\n\n"); + sb.append(hb); + ctx.popScope(); + ctx.methodReturnType = null; + ctx.inAsyncBody = false; + return sb.toString(); + } if (m.isSyncStar && !rt.is("List") && !m.isSetter) { // sync* generator. The body moves into a private helper that yields into the list // it is given (`yield x` -> out.add(x), `yield* xs` -> out.addAllIterable(xs), and a @@ -2045,16 +2458,52 @@ private String emitMethodLike(Method m, Ctx ctx, boolean classIsAbstract) { return sb.toString(); } - /** Shallow check: does the block's last statement definitely leave the method? */ + /** Does the block's last statement definitely leave the method? */ private boolean endsWithJump(Block b) { if (b.statements.isEmpty()) { return false; } - Stmt last = b.statements.get(b.statements.size() - 1); + return leavesMethod(b.statements.get(b.statements.size() - 1)); + } + + /** + * Whether a statement always returns or throws, by the rules javac applies to the + * statement after it: an if/else when both branches do, a try when its finally does + * or its body and every catch do. An `async` body that ends in such a try had a + * trailing `return Future.value(null);` appended, which javac rejected as unreachable. + */ + private boolean leavesMethod(Stmt last) { if (last instanceof ReturnStmt) { return true; } - return last instanceof ExprStmt && ((ExprStmt) last).expr instanceof ThrowExpr; + if (last instanceof ExprStmt) { + return ((ExprStmt) last).expr instanceof ThrowExpr; + } + if (last instanceof Block) { + List ss = ((Block) last).statements; + return !ss.isEmpty() && leavesMethod(ss.get(ss.size() - 1)); + } + if (last instanceof IfStmt) { + IfStmt is = (IfStmt) last; + return is.casePattern == null && is.elseStmt != null + && leavesMethod(is.thenStmt) && leavesMethod(is.elseStmt); + } + if (last instanceof TryStmt) { + TryStmt ts = (TryStmt) last; + if (ts.finallyBlock != null && leavesMethod(ts.finallyBlock)) { + return true; + } + if (!leavesMethod(ts.tryBlock)) { + return false; + } + for (CatchClause cc : ts.catches) { + if (!leavesMethod(cc.body)) { + return false; + } + } + return true; + } + return false; } // ================================================================== @@ -2342,8 +2791,16 @@ private void emitStatementBody(Stmt s, Ctx ctx) { } } else if (s instanceof YieldStmt) { YieldStmt y = (YieldStmt) s; - if (ctx.syncStarList == null) { - diags.error(y, "E0304", "yield outside a sync* generator body"); + if (ctx.asyncStarSink != null) { + if (y.star) { + Out o = emitExpr(y.value, null, ctx); + w.line(ctx.asyncStarSink + ".yieldAll(" + o.code + ");"); + } else { + Out o = emitExpr(y.value, ctx.syncStarElem, ctx); + w.line(ctx.asyncStarSink + ".yield$(" + coerce(o, ctx.syncStarElem, ctx) + ");"); + } + } else if (ctx.syncStarList == null) { + diags.error(y, "E0304", "yield outside a generator body"); } else if (y.star) { Out o = emitExpr(y.value, null, ctx); w.line(ctx.syncStarList + ".addAllIterable(" + o.code + ");"); @@ -2368,7 +2825,7 @@ private void emitStatementBody(Stmt s, Ctx ctx) { return; } if (r.value == null) { - w.line("return;"); + w.line(ctx.bareReturnIsNull ? "return null;" : "return;"); } else if (rt != null && rt.is("void")) { // Dart allows `return expr;` from a method Java-typed void (it overrides a void // base). The value is discarded but the expression still runs: `return n++;` @@ -2496,6 +2953,54 @@ public void run() { } }); ctx.popScope(); + } else if (s instanceof ForInStmt && ((ForInStmt) s).isAwait) { + // `await for (x in stream) body`: a StreamIterator whose next() parks like an + // await until the next event, and is cancelled however the loop is left. + ForInStmt f = (ForInStmt) s; + ctx.importClass("dart.async.StreamIterator"); + Out iter = emitExpr(f.iterable, null, ctx); + TypeRef elem = f.varType != null && !f.varType.is("var") + ? f.varType + : (iter.type != null && iter.type.is("Stream") && !iter.type.args.isEmpty() + ? iter.type.arg(0) : TypeRef.DYNAMIC); + String ej = javaType(elem, true, ctx); + String it = ctx.newTemp(); + String src = iter.code; + if (isDynamic(elem)) { + // The element type is unknown here (a call through a local function, say) + // while the Java value may be a Stream; read it as Stream. + ctx.importClass("dart.async.Stream"); + src = "(Stream) (Stream) " + paren(src); + } + w.line("StreamIterator<" + ej + "> " + it + " = new StreamIterator<" + ej + ">(" + src + ");"); + w.line("try {"); + ctx.indent(1); + ctx.pushScope(); + ctx.pushBreakTarget(null); + w.line(loopLabel(s) + "while (" + it + ".next()) {"); + ctx.indent(1); + if (f.pattern != null) { + String loopVar = ctx.newTemp(); + w.line(ej + " " + loopVar + " = " + it + ".current();"); + ctx.declare(loopVar, elem); + List binds = new ArrayList(); + patternMatch(f.pattern, loopVar, elem, ctx, binds); + for (String bind : binds) { + w.line(bind); + } + } else { + String loopVar = ctx.declareShadowSafe(f.varName, elem); + w.line(ej + " " + loopVar + " = " + it + ".current();"); + } + emitStatement(unwrapBlock(f.body), ctx); + ctx.indent(-1); + w.line("}"); + ctx.popBreakTarget(); + ctx.popScope(); + ctx.indent(-1); + w.line("} finally {"); + w.line(" " + it + ".cancel();"); + w.line("}"); } else if (s instanceof ForInStmt && isIndexedRecordFor(((ForInStmt) s).pattern, ((ForInStmt) s).iterable)) { // `for (final (int i, E e) in xs.indexed)` — Dart's Iterable.indexed pairs each @@ -2578,12 +3083,24 @@ private void emitLocalFunc(Ast.LocalFunc lf, Ctx ctx) { l.line = lf.line; l.col = lf.col; l.isAsync = lf.isAsync; + l.isSyncStar = lf.isSyncStar; + l.isAsyncStar = lf.isAsyncStar; l.params = lf.params; l.body = lf.body; l.exprBody = lf.exprBody; // Declare the local first so a recursive body can reference the name. String jn = ctx.declareShadowSafe(lf.name, new TypeRef("Function")); - Out init = emitExpr(l, funcTypeRef(lf.returnType, lf.params), ctx); + TypeRef expected = funcTypeRef(lf.returnType, lf.params); + if (lf.isSyncStar || lf.isAsyncStar) { + // A generator's element type comes from its declared Iterable/Stream, + // which the generator lowering reads from the target's return type. + expected.funcParams = new ArrayList(); + for (Param p : lf.params) { + expected.funcParams.add(paramValueType(p)); + } + expected.funcReturn = lf.returnType; + } + Out init = emitExpr(l, expected, ctx); ctx.writer().line(samType + " " + jn + " = " + init.code + ";"); } @@ -3000,6 +3517,30 @@ private boolean stmtTerminates(Stmt last) { List ss = ((Block) last).statements; return !ss.isEmpty() && stmtTerminates(ss.get(ss.size() - 1)); } + // Java's own rules, which decide whether a statement after these is unreachable: + // an if/else ends when both branches do, and a try when its finally does, or when + // its body and every catch do. A Dart `on T` clause with no catch-all still ends: + // what it does not match is rethrown. + if (last instanceof IfStmt) { + IfStmt is = (IfStmt) last; + return is.casePattern == null && is.elseStmt != null + && stmtTerminates(is.thenStmt) && stmtTerminates(is.elseStmt); + } + if (last instanceof TryStmt) { + TryStmt ts = (TryStmt) last; + if (ts.finallyBlock != null && stmtTerminates(ts.finallyBlock)) { + return true; + } + if (!stmtTerminates(ts.tryBlock)) { + return false; + } + for (CatchClause cc : ts.catches) { + if (!stmtTerminates(cc.body)) { + return false; + } + } + return true; + } return false; } @@ -3745,7 +4286,17 @@ private Out emitExprRaw(Expr e, TypeRef expected, Ctx ctx) { return ((ListLit) e).isConst ? constCollection(o, ctx) : o; } if (e instanceof MapLit) { - Out o = emitMapLit((MapLit) e, expected, ctx); + MapLit ml = (MapLit) e; + if (ml.keys.isEmpty() && ml.elements.isEmpty() && ml.keyType == null + && expected != null && expected.is("Set")) { + // An empty `{}` is a map unless its context type is a Set, which is + // how Dart reads `this.favorites = const {}` for a Set field. + SetLit sl = new SetLit(); + sl.isConst = ml.isConst; + Out o = emitSetLit(sl, expected, ctx); + return sl.isConst ? constCollection(o, ctx) : o; + } + Out o = emitMapLit(ml, expected, ctx); return ((MapLit) e).isConst ? constCollection(o, ctx) : o; } if (e instanceof SetLit) { @@ -3800,8 +4351,16 @@ private Out emitExprRaw(Expr e, TypeRef expected, Ctx ctx) { return new Out(tmp, target.type); } if (e instanceof ThrowExpr) { - diags.error(e, "E0205", "throw is only supported in statement position in M2"); - return new Out("null", TypeRef.DYNAMIC); + // Expression position: a generic helper that never returns, typed as whatever the + // context expects (Dart's Never), so it fits a ternary arm, `??` or an arrow body. + ctx.importClass("dart.runtime.DartRuntime"); + Out v = emitExpr(((ThrowExpr) e).value, null, ctx); + if (expected != null && !expected.is("void") && !expected.is("dynamic") + && !expected.is("var")) { + return new Out("DartRuntime.<" + javaType(expected, true, ctx) + ">throwValue(" + + v.code + ")", expected); + } + return new Out("DartRuntime.throwValue(" + v.code + ")", TypeRef.DYNAMIC); } if (e instanceof AwaitExpr) { Out o = emitExpr(((AwaitExpr) e).operand, null, ctx); @@ -3928,6 +4487,20 @@ private Out emitExprRaw(Expr e, TypeRef expected, Ctx ctx) { // Duration's unary minus, under the mangled name (see emitBinary). return new Out(paren(o.code) + ".$minus()", o.type); } + if ((u.op.equals("-") || u.op.equals("~")) && o.type != null && !isNumeric(o.type) + && !o.type.is("bool")) { + // A class's own `operator -()` / `operator ~()`: declared under the mangled + // binary name with no parameter (see AstBuilder.mangleOperator). + String mangled = u.op.equals("-") ? "$minus" : "$bitNot"; + ClassDecl cls = program.classes.get(o.type.name); + MethodDecl m = cls != null ? findMethodInHierarchy(cls, mangled) : null; + boolean declares = cls != null ? m != null + : stubs.isStubClass(o.type.name) && stubs.findMethod(o.type.name, mangled, false) != null; + if (declares) { + TypeRef rt = m != null && m.returnType != null && !m.returnType.is("var") ? m.returnType : o.type; + return new Out(paren(o.code) + "." + mangled + "()", rt); + } + } return new Out(u.op + paren(o.code), o.type); } if (e instanceof IncDec) { @@ -3948,6 +4521,9 @@ private Out emitExprRaw(Expr e, TypeRef expected, Ctx ctx) { } try { if (needsShort(inc) + // `a[i]++`: the read is `a.get(i)`, a call `++` cannot apply to; the + // index assignment writes through `[]=` (the old value for a postfix). + || id.operand instanceof IndexGet || (id.operand instanceof Ident && isTopLevelSetter(((Ident) id.operand).name, ctx))) { // `a?.b++` is shorted like every assignment behind a `?.`; `top++` on a // library-level getter/setter pair writes through the setter. @@ -4352,15 +4928,20 @@ private Out emitListLit(ListLit l, TypeRef expected, Ctx ctx) { StringBuilder sb = new StringBuilder(); List codes = new ArrayList(); TypeRef inferred = null; + boolean mixed = false; for (Expr e : l.elements) { Out o = emitExpr(e, elem, ctx); codes.add(elementCode(o, elem, ctx)); if (inferred == null) { inferred = o.type; + } else if (o.type == null || inferred.name == null || !inferred.name.equals(o.type.name)) { + // `[1, 2.5, 'x', null]`: elements that disagree make a List + // (Dart would say List), never a list of the first one's type. + mixed = true; } } if (elem == null) { - elem = inferred != null ? inferred : TypeRef.DYNAMIC; + elem = mixed || inferred == null ? TypeRef.DYNAMIC : inferred; } String pk = primitiveListKind(TypeRef.of("List", elem)); if (pk != null) { @@ -4961,6 +5542,12 @@ private Out emitIdent(Ident id, TypeRef expected, Ctx ctx) { } } } + if (cc != null) { + Out onSelf = mixinOnSelf(cc, n, true, ctx); + if (onSelf != null) { + return emitMemberGet(onSelf, n, id, ctx); + } + } Out top = resolveTopLevel(n, ctx); if (top != null) { return top; @@ -5073,6 +5660,16 @@ private Out enumValues(String enumName, Ctx ctx) { } private Out emitPropertyGet(PropertyGet pg, Ctx ctx) { + // `ids.map(int.parse)`: a tear-off of the numeric parsers, as the method reference + // the call form already lowers to (see emitPrimitiveStaticCall). + if (pg.target instanceof Ident && (pg.name.equals("parse") || pg.name.equals("tryParse"))) { + String tn = ((Ident) pg.target).name; + if ((tn.equals("int") || tn.equals("double")) && ctx.lookup(tn) == null) { + ctx.importClass("dart.core.DString"); + return new Out("DString::" + pg.name + (tn.equals("int") ? "Int" : "Double"), + new TypeRef("Function")); + } + } // `prefix.member` where prefix is an `import '...' as prefix` name: the member // is a top-level const/var/class/enum/function of another user (or stub) library. // In the whole-program single-package model that is just a global top-level lookup. @@ -5151,6 +5748,47 @@ private Out emitPrimitiveTypeConstant(String type, String member) { } /** Property access driven by the target's static type. */ + /** + * Inside {@code mixin M on S}, a bare member that M does not declare is S's: the mixin is + * a Java interface, so it is reached through {@code ((S) this)}. Null when {@code cc} is + * not such a mixin or no {@code on} type has the member. + */ + private Out mixinOnSelf(ClassDecl cc, String n, boolean getter, Ctx ctx) { + if (!cc.isMixin || cc.mixinOn.isEmpty()) { + return null; + } + for (TypeRef on : cc.mixinOn) { + if (hasMember(on.name, n, getter, ctx)) { + return new Out("((" + javaType(on, false, ctx) + ") this)", on); + } + } + return null; + } + + /** Whether the named program or stub class, or one of its superclasses, has member n. */ + private boolean hasMember(String cls, String n, boolean getter, Ctx ctx) { + java.util.Set seen = new java.util.HashSet(); + String cur = cls; + while (cur != null && seen.add(cur)) { + ClassDecl pc = program.resolveClass(cur, ctx.library()); + if (pc == null) { + return stubs.isStubClass(cur) && (stubs.findMethod(cur, n, getter) != null + || (getter && stubs.findMethod(cur, n, false) != null)); + } + if (pc.method(n) != null || pc.field(n) != null || (getter && pc.getter(n) != null)) { + return true; + } + for (TypeRef m : pc.mixins) { + ClassDecl mx = program.resolveClass(m.name, ctx.library()); + if (mx != null && (mx.method(n) != null || mx.field(n) != null || mx.getter(n) != null)) { + return true; + } + } + cur = pc.superclass != null ? pc.superclass.name : null; + } + return false; + } + private Out emitMemberGet(Out target, String name, Node posNode, Ctx ctx) { TypeRef tt = target.type; // A field/param typed with an import prefix (`intl.DateFormat`, `ui.Size`) keeps the @@ -5168,7 +5806,9 @@ private Out emitMemberGet(Out target, String name, Node posNode, Ctx ctx) { // resolved at runtime) — e.g. `(dynamic demo) => demo.slug` or a dynamic-typed // `platformDispatcher.platformBrightness`. Emit the access with a dynamic result. if (tt != null && tt.is("dynamic")) { - return new Out(target.code + "." + name, TypeRef.DYNAMIC); + ctx.importClass("dart.runtime.Dyn"); + dynNames.add(name); + return new Out("Dyn.get(" + target.code + ", \"" + name + "\")", TypeRef.DYNAMIC); } // record component accessor: `r.$1`, `r.$2`, or `r.namedField` if (tt != null && tt.name.startsWith("Rec$")) { @@ -5208,7 +5848,30 @@ private Out emitMemberGet(Out target, String name, Node posNode, Ctx ctx) { if (stubs.isStubClass(cls)) { Ast.MethodDecl g = stubs.findMethod(cls, name, true); if (g != null && g.isStatic) { - return new Out(stubSimpleName(cls, ctx) + "." + name, g.returnType); + // Icons are methods in the runtime (see Icons.java: a field per icon + // kept all 2,176 alive), so an icon read is a call. + return new Out(stubSimpleName(cls, ctx) + "." + name + + (cls.equals("Icons") ? "()" : ""), g.returnType); + } + // Material's style variants (`Icons.home_outlined`, `_rounded`, `_sharp`) are + // separate glyphs in Flutter's font; the icon font here has one glyph per + // icon, so a variant shows its base icon (an `_outlined` one the hollow + // `_outline` glyph where the font has it) rather than failing the build. + if (cls.equals("Icons")) { + for (String suffix : new String[] {"_outlined", "_rounded", "_sharp"}) { + if (!name.endsWith(suffix)) { + continue; + } + String base = name.substring(0, name.length() - suffix.length()); + String[] cands = suffix.equals("_outlined") + ? new String[] {base + "_outline", base} : new String[] {base}; + for (String cand : cands) { + Ast.MethodDecl bg = stubs.findMethod(cls, cand, true); + if (bg != null && bg.isStatic) { + return new Out(stubSimpleName(cls, ctx) + "." + cand + "()", bg.returnType); + } + } + } } Ast.MethodDecl m = stubs.findMethod(cls, name, false); if (m != null && m.isStatic) { @@ -5291,6 +5954,12 @@ private Out emitMemberGet(Out target, String name, Node posNode, Ctx ctx) { return new Out("(" + target.code + " % 2 != 0)", TypeRef.BOOL); } } + // The same implicit members on a stub enum, which is a plain Java enum. + if (!program.enums.containsKey(tt.name) && stubs.isStubEnum(tt.name) + && (name.equals("index") || name.equals("name"))) { + return name.equals("index") ? new Out("(long) " + paren(target.code) + ".ordinal()", TypeRef.INT) + : new Out(target.code + ".name()", TypeRef.STRING); + } // implicit instance members on a program enum value: .index (int), .name (String) if (program.enums.containsKey(tt.name)) { if (name.equals("index")) { @@ -5427,16 +6096,41 @@ private Out emitMemberGet(Out target, String name, Node posNode, Ctx ctx) { ? TypeRef.DYNAMIC : eg.returnType; return new Out(extSimple + "." + name + "(" + target.code + ")", rt); } + // `e.message` on dart:core's errors that declare one. Each maps to a Java exception + // built from its message, so getMessage() is that field. + if (name.equals("message") && CORE_ERRORS.containsKey(tt.name) && !tt.is("Exception") + && !tt.is("TypeError") && !tt.is("ConcurrentModificationError")) { + TypeRef str = new TypeRef("String"); + str.nullable = true; + return new Out(target.code + ".getMessage()", str); + } if (tt.is("Stopwatch")) { TypeRef rt = name.equals("isRunning") ? TypeRef.BOOL : name.equals("elapsed") ? new TypeRef("Duration") : TypeRef.INT; return new Out(target.code + "." + name + "()", rt); } + // Every Dart object has hashCode; a class whose superclass is a stub (Equatable) + // reaches here with nothing declaring it. + if (name.equals("hashCode") && tt != null && !isClassRef(tt)) { + ctx.importClass("dart.runtime.DartRuntime"); + return new Out("DartRuntime.hashCodeOf(" + target.code + ")", TypeRef.INT); + } + // A collection method torn off as a callback -- `stream.listen(seen.add)`, + // `ids.where(allowed.contains)`. The runtime collections declare these under the + // same names, so a method reference has the shape the target interface wants. + if ((tt.is("List") || tt.is("Set")) && COLLECTION_TEAR_OFFS.contains(name) + || tt.is("Map") && (name.equals("containsKey") || name.equals("containsValue") + || name.equals("remove"))) { + return new Out("(" + target.code + ")::" + name, new TypeRef("Function")); + } diags.error(posNode, "E0132", "Cannot resolve member '" + name + "' on type " + tt + ". Confirm the file passes `dart analyze`."); return new Out(target.code + "." + name, TypeRef.DYNAMIC, true); } + private static final Set COLLECTION_TEAR_OFFS = new HashSet( + java.util.Arrays.asList("add", "addAll", "remove", "contains")); + /** * For a Dart {@code List}/{@code List} with a non-nullable primitive element, the * primitive-list kind ("Long"/"Double") whose getLong/setLong/addLong methods avoid boxing; @@ -5515,6 +6209,11 @@ private Out emitIndexGetOn(Out target, IndexGet ig, Ctx ctx) { om.returnType == null || om.returnType.is("var") ? TypeRef.DYNAMIC : om.returnType); } } + if (tt.is("dynamic") && !target.fromError) { + ctx.importClass("dart.runtime.Dyn"); + dynNames.add("[]"); + return new Out("Dyn.index(" + target.code + ", " + boxIfPrimitive(idx, ctx) + ")", TypeRef.DYNAMIC); + } if (tt.is("String")) { ctx.importClass("dart.core.DString"); return new Out("DString.idx(" + target.code + ", " + idx.code + ")", TypeRef.STRING); @@ -5762,6 +6461,96 @@ private Out emitNullAwareAssignValue(Assign a, Ctx ctx) { return new Out("(" + chain + " && " + cur + " != null ? " + cur + " : (" + write.code + "))", rt); } + /** + * {@code d.x = v}, {@code d[k] = v} and their compound forms on a {@code dynamic} + * receiver, through dart.runtime.Dyn; null when the receiver is statically typed. + * A compound write pins the receiver and key in temps so each is evaluated once. + */ + private Out emitDynamicMemberAssign(Assign a, Ctx ctx) { + return emitDynamicMemberAssign(a, null, ctx); + } + + /** + * As above; {@code pre} is the receiver when the caller has already emitted it (a + * receiver whose type only emission reveals as dynamic, such as a call). + */ + private Out emitDynamicMemberAssign(Assign a, Out pre, Ctx ctx) { + Expr recv = a.lhs instanceof PropertyGet ? ((PropertyGet) a.lhs).target + : a.lhs instanceof IndexGet ? ((IndexGet) a.lhs).target : null; + if (recv == null || a.op.equals("??=")) { + return null; + } + Out t = pre; + if (t == null) { + TypeRef rt = inferExprTypeQuiet(recv, ctx); + if (rt == null || !rt.is("dynamic")) { + return null; + } + t = emitExpr(recv, null, ctx); + } + if (!t.type.is("dynamic") || t.fromError) { + return null; + } + ctx.importClass("dart.runtime.Dyn"); + String key = a.lhs instanceof PropertyGet ? "\"" + ((PropertyGet) a.lhs).name + "\"" + : boxIfPrimitive(emitExpr(((IndexGet) a.lhs).index, null, ctx), ctx); + boolean prop = a.lhs instanceof PropertyGet; + String recvCode = t.code; + if (!a.op.equals("=")) { + // `d[i++] += 1` / `f().x += 1`: the receiver and the key are read and then + // written, so each is pinned in a temp and evaluated once, ahead of the value. + recvCode = ctx.newTemp(); + ctx.writer().line("Object " + recvCode + " = " + t.code + ";"); + if (!prop) { + String kTmp = ctx.newTemp(); + ctx.writer().line("Object " + kTmp + " = " + key + ";"); + key = kTmp; + } + } + String value = boxIfPrimitive(emitExpr(a.rhs, null, ctx), ctx); + if (prop) { + dynNames.add(((PropertyGet) a.lhs).name); + } else { + dynNames.add("[]="); + dynNames.add("[]"); + } + if (!a.op.equals("=")) { + ctx.importClass("dart.runtime.DartRuntime"); + String cur = prop ? "Dyn.get(" + recvCode + ", " + key + ")" : "Dyn.index(" + recvCode + ", " + key + ")"; + if (postfixValue(a, ctx)) { + // `d[i]++` as a value: the old value. + String oldTmp = ctx.newTemp(); + ctx.writer().line("Object " + oldTmp + " = " + cur + ";"); + cur = oldTmp; + value = "DartRuntime.dynBinary(\"" + a.op.substring(0, a.op.length() - 1) + "\", " + cur + ", " + value + ")"; + ctx.writer().line((prop ? "Dyn.set(" : "Dyn.indexSet(") + recvCode + ", " + key + ", " + value + ");"); + return new Out(oldTmp, TypeRef.DYNAMIC); + } + value = "DartRuntime.dynBinary(\"" + a.op.substring(0, a.op.length() - 1) + "\", " + cur + ", " + value + ")"; + } + return new Out((prop ? "Dyn.set(" : "Dyn.indexSet(") + recvCode + ", " + key + ", " + value + ")", + TypeRef.DYNAMIC); + } + + /** {@code a[i]++} used as a value: it yields the element read before the write. */ + private static boolean postfixValue(Assign a, Ctx ctx) { + return ctx.postfixAssign == a && ctx.statementExpr != a; + } + + /** + * The value form of a postfix {@code a[i]++}: the old element is read once into a + * temp, the incremented value written through {@code write}, and the temp returned. + * The receiver and index are already temps (the compound path pins them). + */ + private Out postfixIndex(String read, TypeRef t, Assign a, Ctx ctx, + java.util.function.Function write) { + String jt = declarableType(t, false, ctx); + String old = ctx.newTemp(); + ctx.writer().line((jt != null ? jt : "var") + " " + old + " = " + read + ";"); + ctx.writer().line(write.apply(compoundValue(old, t, a, ctx)) + ";"); + return new Out(old, t); + } + private Out emitAssign(Assign a, Ctx ctx) { Out shorted = emitShortedAssign(a, ctx); if (shorted != null) { @@ -5774,6 +6563,10 @@ private Out emitAssign(Assign a, Ctx ctx) { } return emitNullAwareAssignValue(a, ctx); } + Out dynWrite = emitDynamicMemberAssign(a, ctx); + if (dynWrite != null) { + return dynWrite; + } // ??= on a variable: a local or field is a valid Java assignment target. if (a.op.equals("??=")) { Out lhs = emitExpr(a.lhs, null, ctx); @@ -5814,6 +6607,14 @@ private Out emitAssign(Assign a, Ctx ctx) { if (a.lhs instanceof IndexGet) { IndexGet ig = (IndexGet) a.lhs; Out target = emitExpr(ig.target, null, ctx); + if (target.type.is("dynamic") && !target.fromError) { + // `f()[k] += 1` where f returns dynamic: inference did not see it, the + // emitted receiver does. Index through Dyn, not a typed `.idx`. + Out dyn = emitDynamicMemberAssign(a, target, ctx); + if (dyn != null) { + return dyn; + } + } Out idx = emitExpr(ig.index, null, ctx); boolean compound = !a.op.equals("="); ClassDecl idxOpClass = program.classes.get(target.type.name); @@ -5838,6 +6639,11 @@ private Out emitAssign(Assign a, Ctx ctx) { // dynamic arithmetic passed to an int parameter. ivt = readOp.returnType; } + if (postfixValue(a, ctx)) { + final String tc = target.code, ic = idx.code; + return postfixIndex(tc + ".$index(" + ic + ")", ivt, a, ctx, + v -> tc + ".$indexSet(" + ic + ", " + v + ")"); + } Out rhsOut = compound ? null : emitExpr(a.rhs, null, ctx); String rhsCode = compound ? compoundValue(target.code + ".$index(" + idx.code + ")", ivt, a, ctx) @@ -5863,6 +6669,11 @@ private Out emitAssign(Assign a, Ctx ctx) { TypeRef vt = target.type.is("Map") ? target.type.arg(1) : target.type.arg(0); String pk = primitiveListKind(target.type); if (pk != null) { + if (postfixValue(a, ctx)) { + final String tc = target.code, ic = idx.code; + return postfixIndex(tc + ".get" + pk + "(" + ic + ")", vt, a, ctx, + v -> tc + ".set" + pk + "(" + ic + ", " + v + ")"); + } String rhsCode = compound ? compoundValue(target.code + ".get" + pk + "(" + idx.code + ")", vt, a, ctx) : coerce(emitExpr(a.rhs, vt, ctx), vt, ctx); @@ -5873,12 +6684,22 @@ private Out emitAssign(Assign a, Ctx ctx) { // The key is coerced like the value: a dynamic one (an untyped forEach // parameter, say) did not compile against putLong(long, long). String key = coerce(idx, TypeRef.INT, ctx); + if (postfixValue(a, ctx)) { + final String tc = target.code; + return postfixIndex(tc + ".idxLong(" + key + ")", vt, a, ctx, + v -> tc + ".putLong(" + key + ", " + v + ")"); + } String rhsCode = compound ? compoundValue(target.code + ".idxLong(" + key + ")", vt, a, ctx) : coerce(emitExpr(a.rhs, vt, ctx), vt, ctx); return new Out(target.code + ".putLong(" + key + ", " + rhsCode + ")", vt); } String key = target.type.is("Map") ? boxIfPrimitive(idx, ctx) : idx.code; + if (postfixValue(a, ctx)) { + final String tc = target.code; + return postfixIndex(tc + ".idx(" + key + ")", vt, a, ctx, + v -> tc + ".idxSet(" + key + ", " + v + ")"); + } String rhsCode = compound ? compoundValue(target.code + ".idx(" + key + ")", vt, a, ctx) : coerce(emitExpr(a.rhs, vt, ctx), vt, ctx); @@ -6651,7 +7472,20 @@ private Out emitBinary(Binary b, Ctx ctx) { return new Out(logicalOperand(l, ctx) + " || " + logicalOperand(r.out, ctx), TypeRef.BOOL); } Out l = emitExpr(b.left, null, ctx); + ctx.pushWriter(ctx.currentIndent()); Out r = emitExpr(b.right, null, ctx); + String rLifted = ctx.popWriter(); + if (!rLifted.isEmpty() && !(b.left instanceof IntLit || b.left instanceof StringLit + || b.left instanceof BoolLit || b.left instanceof NullLit)) { + // `f() + await g()`: the right operand lifted statements, which would run + // before the left operand's expression. Dart evaluates left first, so the + // left value is pinned in a temp ahead of them. + String jt = declarableType(l.type, false, ctx); + String lt = ctx.newTemp(); + ctx.writer().line((jt != null ? jt : "var") + " " + lt + " = " + l.code + ";"); + l = new Out(lt, l.type); + } + ctx.writer().raw(rLifted); boolean numeric = isNumeric(l.type) && isNumeric(r.type); // user-defined operators on program classes ClassDecl opClass = program.classes.get(l.type.name); @@ -6976,6 +7810,7 @@ private void emitChangeNotifierState(TypeRef mixin, StringBuilder body) { CORE_ERRORS.put("RangeError", "dart.core.RangeError"); CORE_ERRORS.put("ConcurrentModificationError", "dart.core.ConcurrentModificationError"); CORE_ERRORS.put("TypeError", "dart.core.TypeError"); + CORE_ERRORS.put("NoSuchMethodError", "dart.core.NoSuchMethodError"); } /** @@ -7145,6 +7980,10 @@ static String javaIdent(String dartName) { /** Whether the most recently emitted lambda had a {@code void}-typed expression body. */ private boolean lastLambdaVoid; + /// The last block lambda was emitted with a `return null;` tail (see emitLambda), so + /// it is value-shaped in Java whatever its Dart body returns; a caller choosing the + /// lambda's SAM by its body shape must pick the value form. + private boolean lastLambdaNullTail; /** * What the most recently emitted lambda yields: its arrow body's type, or the value * types of its block body's returns (empty when it returns nothing). @@ -7171,6 +8010,7 @@ static String javaIdent(String dartName) { private Out emitLambda(Lambda l, TypeRef expected, Ctx ctx) { lastLambdaVoid = false; + lastLambdaNullTail = false; lastLambdaExprIsStatementShaped = false; // typedef-typed target position gives untyped lambda params real types TypeRef[] sigTypes = expected != null ? typedefSig(expected.name) : null; @@ -7186,6 +8026,8 @@ private Out emitLambda(Lambda l, TypeRef expected, Ctx ctx) { expected.funcReturn != null ? expected.funcReturn : TypeRef.DYNAMIC; } boolean outerAsync = ctx.inAsyncBody; + boolean outerBareNull = ctx.bareReturnIsNull; + ctx.bareReturnIsNull = false; // a nested lambda's `return;` is its own boolean outerAsyncLambda = ctx.asyncLambda; TypeRef outerReturn = ctx.methodReturnType; boolean outerNarrowInt = ctx.narrowReturnToInt; @@ -7241,15 +8083,70 @@ private Out emitLambda(Lambda l, TypeRef expected, Ctx ctx) { List outerReturns = ctx.lambdaReturns; List returns = new ArrayList(); TypeRef exprType = null; - if (asyncValue) { + if ((l.isAsyncStar || l.isSyncStar) && l.body != null) { + // A generator closure: the lambda returns the generator, whose body is a nested + // lambda fed the AsyncStar.Sink (async*) or the result list (sync*), as a + // generator method's helper is. + boolean async = l.isAsyncStar; + TypeRef gen = lambdaReturn; + TypeRef elem = gen != null && (gen.is(async ? "Stream" : "Iterable")) && !gen.args.isEmpty() + ? gen.arg(0) : TypeRef.DYNAMIC; + String ej = javaType(elem, true, ctx); + String target = ctx.newTemp(); + String savedSink = ctx.asyncStarSink; + String savedList = ctx.syncStarList; + TypeRef savedElem = ctx.syncStarElem; + ctx.asyncStarSink = async ? target : null; + ctx.syncStarList = async ? null : target; + ctx.syncStarElem = elem; + ctx.inAsyncBody = async; + ctx.methodReturnType = TypeRef.VOID; + ctx.pushWriter(ctx.currentIndent() + 2); + ctx.lambdaReturns = returns; + emitStatements(l.body, ctx); + ctx.lambdaReturns = outerReturns; + String body = ctx.popWriter(); + ctx.asyncStarSink = savedSink; + ctx.syncStarList = savedList; + ctx.syncStarElem = savedElem; + String ind = indentStr(ctx.currentIndent()); + // What an untyped closure local infers its SAM from: the generator's type. + exprType = TypeRef.of(async ? "Stream" : "Iterable", elem); + if (async) { + ctx.importClass("dart.async.AsyncStar"); + code = head + " -> AsyncStar.<" + ej + ">asyncStar((AsyncStar.Sink<" + ej + "> " + target + + ") -> {\n" + body + ind + " })"; + } else { + ctx.importClass("dart.core.DartList"); + ctx.importClass("dart.core.DartIterable"); + code = head + " -> DartIterable.syncStar((DartList<" + ej + "> " + target + ") -> {\n" + + body + ind + " })"; + } + } else if (asyncValue) { code = head + " -> {\n" + emitAsyncLambdaBody(l, lambdaReturn, ctx) + indentStr(ctx.currentIndent()) + "}"; } else if (l.body != null) { - Ctx.Writer w = ctx.pushWriter(ctx.currentIndent() + 1); + // `on((event, emit) { emit(s); })`: a block that returns nothing, passed where + // the function type returns dynamic -- a value-returning Funcs.FuncN in Java. + // Its `return;` and its end both answer null. The body sits under `if (true)`, + // which javac treats as able to complete normally, so the trailing return is + // never "unreachable" after a body that always returns or throws. + boolean nullTail = sigTypes != null && sigTypes.length > 0 + && sigTypes[sigTypes.length - 1] != null && sigTypes[sigTypes.length - 1].is("dynamic") + && !returnsValue(l.body); + boolean outerNullReturn = ctx.bareReturnIsNull; + ctx.bareReturnIsNull = nullTail; + Ctx.Writer w = ctx.pushWriter(ctx.currentIndent() + (nullTail ? 2 : 1)); ctx.lambdaReturns = returns; emitStatements(l.body, ctx); ctx.lambdaReturns = outerReturns; String body = ctx.popWriter(); + ctx.bareReturnIsNull = outerNullReturn; + lastLambdaNullTail = nullTail; + if (nullTail) { + String in = indentStr(ctx.currentIndent() + 1); + body = in + "if (true) {\n" + body + in + "}\n" + in + "return null;\n"; + } code = head + " -> {\n" + body + indentStr(ctx.currentIndent()) + "}"; } else { Ctx.Writer w = ctx.pushWriter(ctx.currentIndent() + 1); @@ -7288,6 +8185,7 @@ private Out emitLambda(Lambda l, TypeRef expected, Ctx ctx) { ctx.breakTargets.clear(); ctx.breakTargets.addAll(savedBreaks); ctx.inAsyncBody = outerAsync; + ctx.bareReturnIsNull = outerBareNull; ctx.asyncLambda = outerAsyncLambda; ctx.methodReturnType = outerReturn; ctx.narrowReturnToInt = outerNarrowInt; @@ -7652,6 +8550,9 @@ private Out emitBareCall(Call c, Ctx ctx) { String stubSuper = nearestStubSuper(cc); if (stubSuper != null) { Ast.MethodDecl sm = stubs.findMethod(stubSuper, n, false); + if (sm != null && hasWitnessParam(sm)) { + return new Out("this." + n + "(" + witnessArgs(sm, c, ctx) + ")", sm.returnType); + } if (sm != null) { return new Out("this." + n + "(" + stubMethodArgs(sm, c.args, ctx) + ")", sm.returnType); } @@ -7679,6 +8580,12 @@ private Out emitBareCall(Call c, Ctx ctx) { funcResultType(ff.type)); } } + if (cc != null) { + Out onSelf = mixinOnSelf(cc, n, false, ctx); + if (onSelf != null) { + return emitMethodCallOn(onSelf, c, ctx); + } + } // top-level function (user code) FunctionDecl fn = program.functions.get(n); if (fn != null) { @@ -7715,8 +8622,24 @@ private Out emitBareCall(Call c, Ctx ctx) { } ctx.importClass(cls); String simple = cls.substring(cls.lastIndexOf('.') + 1); - return new Out(simple + "." + method + "(" + methodArgs(sf.params, c.args, ctx) + ")", - sf.returnType); + String call = simple + "." + method + "(" + methodArgs(sf.params, c.args, ctx) + ")"; + if (!sf.typeParams.isEmpty() && sf.returnType != null) { + // showDialog(...): bind the function's own type parameters -- to the + // call's type arguments, else dynamic -- and cast the erased Java result to + // match, as a generic stub method's call is. + List bound = new ArrayList(); + for (int i = 0; i < sf.typeParams.size(); i++) { + bound.add(i < c.typeArgs.size() && !isTypeVariable(c.typeArgs.get(i), ctx) + ? c.typeArgs.get(i) : TypeRef.DYNAMIC); + } + TypeRef rt = substituteTypeParam(sf.returnType, sf.typeParams, bound); + String jt = javaType(rt, true, ctx); + if (jt != null && !jt.equals("Object")) { + return new Out("((" + jt + ") (Object) " + call + ")", rt); + } + return new Out(call, rt); + } + return new Out(call, sf.returnType); } diags.error(c, "E0135", "Cannot resolve function or constructor '" + n + "'. Confirm the file passes `dart analyze`, or the API may be unsupported in M1."); @@ -7739,6 +8662,13 @@ private Out emitMethodCallOn(Out target, Call c, Ctx ctx) { return new Out(target.code + "." + n + "(" + plainArgs(c.args, ctx) + ")", TypeRef.DYNAMIC, true); } + // Genuine `dynamic` receiver: dispatch at runtime through dart.runtime.Dyn. + if (tt != null && tt.is("dynamic")) { + ctx.importClass("dart.runtime.Dyn"); + dynNames.add(n); + return new Out("Dyn.call(" + target.code + ", \"" + n + "\", " + dynArgs(c.args, ctx) + ")", + TypeRef.DYNAMIC); + } // static method on a class reference if (isClassRef(tt)) { String cls = tt.arg(0).name; @@ -7830,15 +8760,23 @@ private Out emitMethodCallOn(Out target, Call c, Ctx ctx) { } ClassDecl pc = program.classes.get(cls); if (pc != null) { + // The Java name, which is library-prefixed when the class shares its name + // with a stub (an app's own User beside firebase_auth's): the raw Dart name + // then named the stub, or nothing at all. + ClassDecl visible = program.resolveClass(cls, ctx.library()); + if (visible != null) { + pc = visible; + } + String jcls = javaClassName(pc); MethodDecl m = pc.method(n); if (m != null && m.isStatic) { - return new Out(cls + "." + n + "(" + methodArgs(m.params, c.args, ctx) + ")", + return new Out(jcls + "." + n + "(" + methodArgs(m.params, c.args, ctx) + ")", m.returnType == null ? TypeRef.DYNAMIC : m.returnType); } CtorDecl named = pc.namedCtor(n); if (named != null) { // named (or named factory) constructor -> static factory - return new Out(cls + "." + n + "(" + canonicalArgs(named, c.args, ctx) + ")", + return new Out(jcls + "." + javaIdent(n) + "(" + canonicalArgs(named, c.args, ctx) + ")", new TypeRef(cls)); } diags.error(c, "E0136", "Cannot resolve static member or constructor '" + cls + "." + n + "'"); @@ -7958,7 +8896,7 @@ private Out emitMethodCallOn(Out target, Call c, Ctx ctx) { // Narrow a generic method result (declared `T evaluate(...)`) to the // concrete type argument the receiver instantiates. TypeRef sub = stubMemberReturnType(tt, n, false); - return stubCallOut(m, c, target.code + "." + n, ctx, sub); + return stubCallOut(withReceiverCallbackTypes(m, tt), c, target.code + "." + n, ctx, sub); } } // State lifecycle overrides not present on the minimal State stub surface @@ -7975,6 +8913,24 @@ private Out emitMethodCallOn(Out target, Call c, Ctx ctx) { + (rest.isEmpty() ? "" : ", " + rest) + ")", em.returnType == null || em.returnType.is("var") ? TypeRef.DYNAMIC : em.returnType); } + // extension method contributed by a stub (go_router's `extension GoRouterHelper on + // BuildContext { void go(String location, {Object? extra}); }`): a static method of + // the extension's @JavaName class taking the receiver first, then the call's + // arguments laid out as for any stub method. + Ast.ClassDecl stubExtM = stubs.findExtension(tt.name, n, false); + if (stubExtM != null) { + Ast.MethodDecl em = extensionMember(stubExtM, n, false); + if (em != null) { + String callee = stubExtensionSimpleName(stubExtM, ctx) + "." + n; + Out o = stubCallOut(em, c, callee, ctx); + String prefix = callee + "("; + if (o.code.startsWith(prefix)) { + String rest = o.code.substring(prefix.length()); + return new Out(prefix + target.code + (rest.startsWith(")") ? "" : ", ") + rest, + o.type, o.fromError); + } + } + } if (tt.is("Stopwatch")) { // start / stop / reset are void no-arg controls return new Out(target.code + "." + n + "()", TypeRef.VOID); @@ -8007,6 +8963,22 @@ private Out intrinsicCall(Out target, Call c, Ctx ctx) { TypeRef tt = target.type; String n = c.name; List pos = c.args.positional; + if (tt.is("Stream") && n.equals("cast") && pos.isEmpty() && c.typeArgs.size() == 1) { + // Stream.cast() checks each event, so the runtime takes R as a witness the + // way whereType does; an erased type variable has none and stays unchecked. + TypeRef sub = c.typeArgs.get(0); + // Generic code (a library's cast()) is common, so a type variable is not + // diagnosed as whereType's is: it keeps the old unchecked behaviour. + boolean dyn = sub.is("dynamic") || sub.is("var") || sub.is("Object") || isTypeVariable(sub, ctx); + String jt = dyn ? null : javaType(sub, true, ctx); + if (jt == null) { + return new Out(target.code + ".cast()", TypeRef.of("Stream", sub)); + } + // A class literal names the raw type: List is checked as a List. + String raw = jt.indexOf('<') >= 0 ? jt.substring(0, jt.indexOf('<')) : jt; + return new Out(target.code + ".<" + jt + ">cast(" + raw + ".class, " + sub.nullable + ")", + TypeRef.of("Stream", sub)); + } if (tt.is("String")) { ctx.importClass("dart.core.DString"); if (n.equals("substring")) { @@ -8142,6 +9114,17 @@ private Out intrinsicCall(Out target, Call c, Ctx ctx) { return new Out("DartRuntime.clamp(" + target.code + ", " + lo + ", " + hi + ")", TypeRef.INT); } } + if (tt.is("num") && pos.isEmpty()) { + // A value typed only `num` -- `(json['id'] as num).toInt()`, the shape + // json_serializable generates -- is a boxed Long or Double here. + ctx.importClass("dart.runtime.DartRuntime"); + if (n.equals("toInt") || n.equals("truncate")) { + return new Out("DartRuntime.numToInt(" + target.code + ")", TypeRef.INT); + } + if (n.equals("toDouble")) { + return new Out("DartRuntime.numToDouble(" + target.code + ")", TypeRef.DOUBLE); + } + } if (tt.is("double")) { ctx.importClass("dart.runtime.DartRuntime"); if (n.equals("toString")) { @@ -8532,11 +9515,24 @@ private Out intrinsicCall(Out target, Call c, Ctx ctx) { if (tt.is("Future")) { ctx.importClass("dart.async.Future"); if (n.equals("then")) { - Out cb = emitExpr(pos.get(0), null, ctx); + // `getUrl(u).then((r) => r.close())`: an untyped callback parameter takes + // the future's value type, as Dart infers it, so members of a runtime class + // resolve statically -- through dynamic dispatch they could not resolve. + TypeRef valueType = tt.args.size() == 1 ? tt.arg(0) : null; + Expr cbExpr = pos.get(0); + boolean typedCb = valueType != null && !isDynamicType(valueType) && isFullyConcrete(valueType) + && !valueType.is("int") && !valueType.is("double") && !valueType.is("bool") + && !valueType.is("num") && !valueType.is("void") && !valueType.is("Null") + && cbExpr instanceof Lambda && ((Lambda) cbExpr).params.size() == 1 + && ((Lambda) cbExpr).typeParams.isEmpty() + && (((Lambda) cbExpr).params.get(0).type == null || ((Lambda) cbExpr).params.get(0).type.is("var")); + Out cb = emitExpr(cbExpr, typedCb ? inlineFuncType(valueType, TypeRef.DYNAMIC) : null, ctx); + String vj = typedCb ? javaType(boxType(valueType), true, ctx) : null; // A lambda fits BOTH then overloads (Func1 / VoidFunc1) when it is void- or // throw-bodied, which javac reports as ambiguous; pin those to VoidFunc1. - String cbCode = pinnedHandler(pos.get(0), cb, false, "dart.runtime.Funcs.Func1", - "dart.runtime.Funcs.VoidFunc1"); + String cbCode = pinnedHandler(cbExpr, cb, typedCb, + typedCb ? "dart.runtime.Funcs.Func1<" + vj + ", Object>" : "dart.runtime.Funcs.Func1", + typedCb ? "dart.runtime.Funcs.VoidFunc1<" + vj + ">" : "dart.runtime.Funcs.VoidFunc1"); // then(onValue, onError: handler): the handler was dropped, so a failure // skipped it and the chain stayed failed. for (NamedArg na : c.args.named) { @@ -8554,7 +9550,7 @@ private Out intrinsicCall(Out target, Call c, Ctx ctx) { String cbCode = pinnedHandler(pos.get(0), cb, false, "dart.runtime.Funcs.Func1", "dart.runtime.Funcs.VoidFunc1"); boolean valueHandler = pos.get(0) instanceof Lambda - && (((Lambda) pos.get(0)).exprBody != null ? !lastLambdaVoid : lambdaReturnsValue(pos.get(0))); + && (((Lambda) pos.get(0)).exprBody != null ? !lastLambdaVoid : lastLambdaNullTail || lambdaReturnsValue(pos.get(0))); // `test` is a NAMED parameter in Dart; reading it positionally meant it was // always null, so the handler caught failures its predicate rejected. The // runtime takes it as an Object, so a lambda is given its predicate type. @@ -8582,7 +9578,7 @@ private Out intrinsicCall(Out target, Call c, Ctx ctx) { Out cb = emitExpr(action, null, ctx); boolean value; if (action instanceof Lambda) { - value = ((Lambda) action).exprBody != null ? !lastLambdaVoid : lambdaReturnsValue(action); + value = ((Lambda) action).exprBody != null ? !lastLambdaVoid : lastLambdaNullTail || lambdaReturnsValue(action); } else { // A tear-off is typed a bare Function, so its declared return is read // from the method or function it names; unresolved stays on the void form. @@ -8847,7 +9843,10 @@ private Out emitCtorCall(String className, Args args, Node posNode, Ctx ctx) { * matched nothing and the page came up blank.

*/ private static boolean readsProvidedValueByType(String className) { - return className.equals("Selector") || className.equals("Consumer"); + return className.equals("Selector") || className.equals("Consumer") + // flutter_bloc's widgets look their bloc up by B the same way. + || className.equals("BlocBuilder") || className.equals("BlocListener") + || className.equals("BlocConsumer") || className.equals("BlocSelector"); } /** @@ -8873,7 +9872,8 @@ private void emitProvidedTypeToken(String tmp, String className, List t * arrives; without it the lookup went past the provider as if it were not there. */ private static boolean publishesValueByType(String className) { - return className.equals("Provider") || className.equals("ChangeNotifierProvider"); + return className.equals("Provider") || className.equals("ChangeNotifierProvider") + || className.equals("BlocProvider") || className.equals("RepositoryProvider"); } /** {@code A.class} for the first type argument when it is a plain class, else null. */ @@ -9680,6 +10680,46 @@ private String methodArgs(List params, Args args, Ctx ctx) { } /** Stub method calls: canonical positional per stub declaration order. */ + /** + * A stub method whose LAST parameter is {@code Type? witness} wants the call's first + * explicit type argument as a class literal there -- bloc's {@code on(handler)}, + * which returns void and so cannot use the return-type witness above. The Dart caller + * never passes it; a missing or type-variable {@code } passes null (match anything). + */ + private static boolean hasWitnessParam(Ast.MethodDecl m) { + if (m.params.isEmpty()) { + return false; + } + Param last = m.params.get(m.params.size() - 1); + return "witness".equals(last.name) && last.type != null && last.type.is("Type"); + } + + private String witnessArgs(Ast.MethodDecl m, Call c, Ctx ctx) { + CtorDecl fake = new CtorDecl(); + fake.params = new ArrayList(m.params.subList(0, m.params.size() - 1)); + String args = canonicalArgs(fake, c.args, ctx); + String token = "null"; + TypeRef witness = c.typeArgs.isEmpty() ? null : c.typeArgs.get(0); + if (witness == null && !c.args.positional.isEmpty() + && c.args.positional.get(0) instanceof Ast.Lambda) { + // `context.select((PetsCubit c) => c.state.pets)`: is inferred from the + // selector's declared parameter type, so take the witness from there. + Ast.Lambda l = (Ast.Lambda) c.args.positional.get(0); + if (!l.params.isEmpty() && l.params.get(0).type != null + && !l.params.get(0).type.is("var") && !l.params.get(0).type.is("dynamic")) { + witness = l.params.get(0).type; + } + } + if (witness != null && !isTypeVariable(witness, ctx)) { + String javaName = javaType(witness, true, ctx); + if (javaName != null) { + int lt = javaName.indexOf('<'); + token = (lt >= 0 ? javaName.substring(0, lt) : javaName) + ".class"; + } + } + return args.isEmpty() ? token : args + ", " + token; + } + private String stubMethodArgs(Ast.MethodDecl m, Args args, Ctx ctx) { CtorDecl fake = new CtorDecl(); fake.params = m.params; @@ -9771,6 +10811,10 @@ private Out stubCallOut(Ast.MethodDecl m, Call c, String callee, Ctx ctx) { * explicit-{@code }-witness path still keys off the raw declared return type. */ private Out stubCallOut(Ast.MethodDecl m, Call c, String callee, Ctx ctx, TypeRef substReturn) { + if (hasWitnessParam(m)) { + return new Out(callee + "(" + witnessArgs(m, c, ctx) + ")", + bindMethodTypeParams(substReturn != null ? substReturn : m.returnType, m, c, ctx)); + } String args = stubMethodArgs(m, c.args, ctx); TypeRef rt = m.returnType; if (rt != null && !isConcreteType(rt)) { @@ -9817,7 +10861,46 @@ private Out stubCallOut(Ast.MethodDecl m, Call c, String callee, Ctx ctx, TypeRe // declares (javac then rejects the local it types). Resolve any of the method's // own generics that still appear anywhere in the result. result = bindMethodTypeParams(result, m, c, ctx); - return new Out(callee + "(" + args + ")", result); + String typed = explicitTypeArgs(callee, m, c, result, ctx); + if (!typed.equals(callee)) { + // javac ignores type arguments on a method that is not generic in Java -- + // Navigator's push answers Future -- so the result is also cast + // through Object to what the Dart signature promises. An erased cast: the + // Java runtime checks only the raw class, which the method already returns. + String jt = javaType(result, true, ctx); + if (jt != null && !jt.equals("Object")) { + return new Out("((" + jt + ") (Object) " + typed + "(" + args + "))", result); + } + } + return new Out(typed + "(" + args + ")", result); + } + + /** + * {@code dio.get>(path)}: the call's explicit type arguments, handed + * to the Java method as {@code dio.>get(path)}. Without them javac + * infers Object for a type parameter that only appears in the result, and the + * {@code var} the result lands in loses the type the Dart code relies on. Only when + * the arguments line up one to one with the method's own and the result mentions + * one of them; a non-generic Java method ignores explicit type arguments. + */ + private String explicitTypeArgs(String callee, Ast.MethodDecl m, Call c, TypeRef result, Ctx ctx) { + int dot = callee.lastIndexOf('.'); + if (dot <= 0 || c.typeArgs.isEmpty() || c.typeArgs.size() != m.typeParams.size() + || m.returnType == null || !mentionsAnyTypeParam(m.returnType, m.typeParams)) { + return callee; + } + StringBuilder sb = new StringBuilder(); + for (TypeRef t : c.typeArgs) { + if (isTypeVariable(t, ctx)) { + return callee; + } + String jt = javaType(t, true, ctx); + if (jt == null) { + return callee; + } + sb.append(sb.length() == 0 ? "" : ", ").append(jt); + } + return callee.substring(0, dot + 1) + "<" + sb + ">" + callee.substring(dot + 1); } /** @@ -9985,6 +11068,10 @@ private boolean isConcreteType(TypeRef t) { * Whether {@code sub} is (transitively) a subtype of {@code sup} across the program and stub * class hierarchies. Used to decide when a generic cross-type assignment needs an erasing cast. */ + /** Runtime superclasses whose subclasses are plain model data (see emitDynamicMembers). */ + private static final java.util.Set MODEL_BASES = + new java.util.HashSet(java.util.Arrays.asList("Equatable")); + private boolean isSubtypeName(String sub, String sup) { if (sub == null || sup == null) { return false; @@ -10230,7 +11317,7 @@ private String pinnedHandler(Expr src, Out o, boolean forceCast, String asValue, if (!(src instanceof Lambda)) { return o.code; } - boolean value = ((Lambda) src).exprBody != null ? !lastLambdaVoid : lambdaReturnsValue(src); + boolean value = ((Lambda) src).exprBody != null ? !lastLambdaVoid : lastLambdaNullTail || lambdaReturnsValue(src); boolean ambiguous = ((Lambda) src).exprBody != null && lastLambdaExprIsStatementShaped; if (value && !forceCast && !ambiguous) { return o.code; @@ -10260,6 +11347,48 @@ private boolean isFullyConcrete(TypeRef t) { return true; } + /** + * Arguments of a call through a dynamic receiver: positional ones as an Object[], named + * ones as a DartMap. A function literal has no target type there, so it is typed as a + * function of its own arity over dynamic. + */ + private String dynArgs(Args args, Ctx ctx) { + StringBuilder sb = new StringBuilder("new Object[] {"); + for (int i = 0; i < args.positional.size(); i++) { + if (i > 0) { + sb.append(", "); + } + Expr a = args.positional.get(i); + TypeRef expect = null; + if (a instanceof Lambda) { + expect = new TypeRef("Function"); + expect.funcParams = new ArrayList(); + for (int k = 0; k < ((Lambda) a).params.size(); k++) { + expect.funcParams.add(TypeRef.DYNAMIC); + } + expect.funcReturn = TypeRef.DYNAMIC; + } + Out o = emitExpr(a, expect, ctx); + sb.append(expect != null ? funcCast(o, expect, ctx) : boxIfPrimitive(o, ctx)); + } + sb.append("}"); + if (!args.named.isEmpty()) { + ctx.importClass("dart.core.DartMap"); + sb.append(", DartMap.of("); + boolean first = true; + for (NamedArg na : args.named) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append("\"").append(na.name).append("\", ") + .append(boxIfPrimitive(emitExpr(na.value, null, ctx), ctx)); + } + sb.append(")"); + } + return sb.toString(); + } + private String plainArgs(Args args, Ctx ctx) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < args.positional.size(); i++) { @@ -10691,6 +11820,10 @@ private String javaType(TypeRef t, boolean boxed, Ctx ctx) { } return simple; } + Ast.EnumDecl own = visibleProgramEnum(t.name, ctx); + if (own != null) { + return javaEnumName(own); + } Ast.EnumDecl se = stubs.enums.get(t.name); if (se != null && se.javaName != null) { ctx.importClass(se.javaName); @@ -10807,6 +11940,9 @@ private String stubExtensionSimpleName(Ast.ClassDecl ext, Ctx ctx) { } private void importEnum(String dartName, Ctx ctx) { + if (visibleProgramEnum(dartName, ctx) != null) { + return; + } Ast.EnumDecl se = stubs.enums.get(dartName); if (se != null && se.javaName != null) { ctx.importClass(se.javaName); @@ -10814,6 +11950,10 @@ private void importEnum(String dartName, Ctx ctx) { } private String simpleEnumName(String dartName, Ctx ctx) { + Ast.EnumDecl own = visibleProgramEnum(dartName, ctx); + if (own != null) { + return javaEnumName(own); + } Ast.EnumDecl se = stubs.enums.get(dartName); if (se != null && se.javaName != null) { return se.javaName.substring(se.javaName.lastIndexOf('.') + 1); @@ -10822,6 +11962,32 @@ private String simpleEnumName(String dartName, Ctx ctx) { return pe != null ? javaEnumName(pe) : dartName; } + /** + * A user enum the emitting library declares or imports directly, which wins over a stub + * enum of the same name: stubs are global, so an app's own {@code Priority} or + * {@code ResponseType} used to be replaced by flutter_local_notifications' or dio's, + * whatever it imported. Null when no such enum is in scope. + */ + private Ast.EnumDecl visibleProgramEnum(String name, Ctx ctx) { + Library from = ctx != null ? ctx.library() : null; + if (from == null || !stubs.isStubEnum(name)) { + return null; + } + Ast.EnumDecl pe = program.resolveEnum(name, from); + if (pe == null || pe.ownerLibrary == null) { + return null; + } + if (pe.ownerLibrary == from) { + return pe; + } + for (String uri : from.imports) { + if (program.resolveImportedLibrary(from, uri) == pe.ownerLibrary) { + return pe; + } + } + return null; + } + /** A user enum by simple name, preferring the one the code being emitted can see. */ private Ast.EnumDecl programEnum(String name, Ctx ctx) { return program.resolveEnum(name, ctx != null ? ctx.library() : null); @@ -11063,6 +12229,13 @@ && isFullyConcrete(target) // Java's own inference and must not be pinned by a cast. return "(" + javaType(copyNonNull(target), false, ctx) + ") (Object) " + paren(o.code); } + // A dynamic value (a Dyn.get member read) landing in a concrete slot -- a typed + // local, a switch expression's result temp -- is Dart's implicit downcast. Only for + // a fully concrete target: a type variable is left to Java's own inference. + if (o.type != null && isDynamicType(o.type) && !isDynamicType(target) && isFullyConcrete(target) + && !target.is("void") && o.code.startsWith("Dyn.")) { + return "(" + javaType(copyNonNull(target), false, ctx) + ") " + paren(o.code); + } return o.code; } @@ -11212,6 +12385,19 @@ private String nearestStubSuper(ClassDecl c) { return null; } + private static boolean stubTakesPositionals(Ast.ClassDecl stub) { + for (Ast.CtorDecl k : stub.ctors) { + if (k.name == null) { + for (Param p : k.params) { + if (!p.named) { + return true; + } + } + } + } + return false; + } + private Ast.ClassDecl stubClassOf(TypeRef t) { return t == null ? null : stubs.classes.get(t.name); } @@ -11278,6 +12464,88 @@ private static java.util.Map paramMap(java.util.List pa * / {@code Animatable}) to {@code Color}. Returns {@code null} when the member * is not found on the stub chain (leaving the caller's default type in place). */ + /** + * {@code m} with its callback parameters' class type variables replaced by the + * receiver's type arguments: {@code Stream>.listen} expects a + * {@code void Function(List)}, so the closure passed to it types its + * parameter and {@code xs.contains(1)} is a typed call. Without it the parameter + * was dynamic and every member read went through {@code Dyn}, whose untyped + * result javac then rejected. Unbound variables (a raw receiver) become dynamic. + * Only callbacks change; value parameters keep their declared types. + */ + private Ast.MethodDecl withReceiverCallbackTypes(Ast.MethodDecl m, TypeRef receiver) { + Ast.ClassDecl c = receiver == null ? null : stubs.classes.get(receiver.name); + if (c == null || c.typeParams.isEmpty() || !c.methods.contains(m)) { + return m; + } + java.util.Map subst = new java.util.HashMap(); + for (int i = 0; i < c.typeParams.size(); i++) { + // A raw receiver comes back as the stub's own `Stream`; that T, like any + // type variable, is no type the closure can declare, so it stays dynamic. + TypeRef a = i < receiver.args.size() ? receiver.args.get(i) : null; + subst.put(c.typeParams.get(i), a != null && isConcreteType(a) ? a : TypeRef.DYNAMIC); + } + List params = new ArrayList(); + boolean changed = false; + for (Param p : m.params) { + TypeRef nt = p.type != null && p.type.funcParams != null ? substFunctionType(p.type, subst) : p.type; + if (nt != p.type) { + Param q = new Param(); + q.type = nt; + q.name = p.name; + q.named = p.named; + q.required = p.required; + q.optional = p.optional; + q.isThis = p.isThis; + q.isSuper = p.isSuper; + q.defaultValue = p.defaultValue; + params.add(q); + changed = true; + } else { + params.add(p); + } + } + if (!changed) { + return m; + } + Ast.MethodDecl copy = new Ast.MethodDecl(); + copy.returnType = m.returnType; + copy.name = m.name; + copy.typeParams = m.typeParams; + copy.params = params; + copy.isStatic = m.isStatic; + copy.isGetter = m.isGetter; + copy.isSetter = m.isSetter; + copy.isAbstract = m.isAbstract; + copy.isAsync = m.isAsync; + return copy; + } + + /** {@link #substTypeVars}, reaching into an inline function type's signature too. */ + private TypeRef substFunctionType(TypeRef t, java.util.Map subst) { + if (t == null || t.funcParams == null) { + return substTypeVars(t, subst); + } + boolean changed = false; + List fps = new ArrayList(); + for (TypeRef fp : t.funcParams) { + TypeRef nfp = substFunctionType(fp, subst); + changed |= nfp != fp; + fps.add(nfp); + } + TypeRef ret = substFunctionType(t.funcReturn, subst); + changed |= ret != t.funcReturn; + if (!changed) { + return t; + } + TypeRef nt = new TypeRef(t.name); + nt.args.addAll(t.args); + nt.nullable = t.nullable; + nt.funcParams = fps; + nt.funcReturn = ret; + return nt; + } + private TypeRef stubMemberReturnType(TypeRef receiver, String member, boolean getter) { if (receiver == null) { return null; @@ -11405,7 +12673,11 @@ private final class Ctx { String nullCheckedTemp; /** Inside a hashCode/compareTo body: narrow each `return` value to Java int. */ boolean narrowReturnToInt; + /// A bare `return;` answers null: the enclosing block lambda targets a + /// dynamic-returning (value) SAM but returns no value of its own. + boolean bareReturnIsNull; String syncStarList; // non-null inside a sync* body: the result-list temp + String asyncStarSink; // non-null inside an async* body: the AsyncStar.Sink temp /** * The expression being emitted as a statement, whose value nobody reads (see * {@link #emitStatementExpr}); an assignment through a void setter only needs a diff --git a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/parser/AstBuilder.java b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/parser/AstBuilder.java index caf5e019840..f9d9dcb1ee4 100644 --- a/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/parser/AstBuilder.java +++ b/maven/dart-transpiler/src/main/java/com/codename1/dart/transpiler/parser/AstBuilder.java @@ -177,7 +177,7 @@ private void buildLibrary(Dart2Parser.LibraryDeclarationContext ctx, Library lib * a platform without that library. */ private static final java.util.Set SUPPORTED_DART_LIBRARIES = new java.util.HashSet( - java.util.Arrays.asList("core", "async", "collection", "math", "typed_data", "ui")); + java.util.Arrays.asList("core", "async", "collection", "convert", "io", "math", "typed_data", "ui")); /** * Picks the uri a configurable import/export names on this platform: the first @@ -242,6 +242,11 @@ private void buildTopLevel(Dart2Parser.TopLevelDeclarationContext ctx, pos(cd, mx); cd.name = mx.typeIdentifier().getText(); cd.isMixin = true; + if (mx.ON_() != null && mx.typeNotVoidList() != null) { + for (Dart2Parser.TypeNotVoidContext t : mx.typeNotVoidList().typeNotVoid()) { + cd.mixinOn.add(buildTypeNotVoid(t)); + } + } // `mixin Holder`: dropped, the interface named an undeclared H and every // class applying the mixin failed to compile. if (mx.typeParameters() != null) { @@ -421,8 +426,7 @@ private void fillFunctionTypedef(TypedefDecl td, Dart2Parser.FunctionTypeContext private ClassDecl buildClass(Dart2Parser.ClassDeclarationContext ctx) { if (ctx.mixinApplicationClass() != null) { - unsupported(ctx, "E0104", "Mixin application classes are not supported yet (M4)"); - return null; + return buildMixinApplicationClass(ctx, ctx.mixinApplicationClass()); } ClassDecl cd = new ClassDecl(); pos(cd, ctx); @@ -464,6 +468,41 @@ private ClassDecl buildClass(Dart2Parser.ClassDeclarationContext ctx) { return cd; } + /** + * {@code class C = S with M1, M2 implements I;} is the class {@code C extends S with + * M1, M2 implements I {}} whose constructors forward to S's. The forwarding constructors + * need S's declaration, so Program synthesizes them once every library is registered. + */ + private ClassDecl buildMixinApplicationClass(Dart2Parser.ClassDeclarationContext outer, + Dart2Parser.MixinApplicationClassContext ctx) { + ClassDecl cd = new ClassDecl(); + pos(cd, ctx); + cd.name = ctx.identifier().getText(); + Dart2Parser.ClassModifiersContext mods = outer.classModifiers(); + cd.isAbstract = mods != null && !mods.ABSTRACT_().isEmpty(); + cd.isSealed = mods != null && !mods.SEALED_().isEmpty(); + if (cd.isSealed) { + cd.isAbstract = true; + } + if (ctx.typeParameters() != null) { + for (Dart2Parser.TypeParameterContext tp : ctx.typeParameters().typeParameter()) { + cd.typeParams.add(tp.identifier().getText()); + } + } + Dart2Parser.MixinApplicationContext app = ctx.mixinApplication(); + cd.superclass = buildTypeNotVoid(app.typeNotVoid()); + for (Dart2Parser.TypeNotVoidContext t : app.mixins().typeNotVoidList().typeNotVoid()) { + cd.mixins.add(buildTypeNotVoid(t)); + } + if (app.interfaces() != null) { + for (Dart2Parser.TypeNotVoidContext t : app.interfaces().typeNotVoidList().typeNotVoid()) { + cd.interfaces.add(buildTypeNotVoid(t)); + } + } + cd.forwardSuperCtors = true; + return cd; + } + private EnumDecl buildEnum(Dart2Parser.EnumTypeContext ctx) { EnumDecl ed = new EnumDecl(); pos(ed, ctx); @@ -522,13 +561,18 @@ private void buildMember(Dart2Parser.ClassMemberDeclarationContext ctx, buildInitializers(d.initializers(), ctor); } if (d.redirection() != null) { - unsupported(d, "E0106", "Redirecting constructors are not supported yet (M2)"); + Dart2Parser.RedirectionContext r = d.redirection(); + SuperInit target = new SuperInit(); + pos(target, r); + target.namedCtor = r.identifier() != null ? r.identifier().getText() : null; + buildArgs(r.arguments(), target.args); + ctor.redirect = target; } cd.ctors.add(ctor); return; } if (d.redirectingFactoryConstructorSignature() != null) { - unsupported(d, "E0106", "Redirecting factory constructors are not supported yet"); + cd.ctors.add(buildRedirectingFactory(d.redirectingFactoryConstructorSignature(), cd)); return; } if (d.factoryConstructorSignature() != null) { @@ -693,6 +737,15 @@ private void buildMethodWithBody(Dart2Parser.MethodSignatureContext sig, buildCtorName(sig.factoryConstructorSignature().constructorName(), ctor, cd); buildParams(sig.factoryConstructorSignature().formalParameterList(), ctor.params); ctor.body = buildBodyBlock(body); + // `factory X.f(...) => expr;` returns expr; a generative constructor's arrow + // body is a statement, which is how buildBodyBlock reads every arrow body. + if (body != null && body.expr() != null && ctor.body != null + && ctor.body.statements.size() == 1 && ctor.body.statements.get(0) instanceof ExprStmt) { + ReturnStmt r = new ReturnStmt(); + pos(r, body); + r.value = ((ExprStmt) ctor.body.statements.get(0)).expr; + ctor.body.statements.set(0, r); + } cd.ctors.add(ctor); return; } @@ -784,6 +837,8 @@ private void buildFunctionSignature(Dart2Parser.FunctionSignatureContext sig, Fu private void buildFunctionBodyInto(Dart2Parser.FunctionBodyContext body, FunctionDecl fn) { if (body.SYNC_() != null && body.ST() != null) { fn.isSyncStar = true; + } else if (body.ASYNC_() != null && body.ST() != null && body.NATIVE_() == null) { + fn.isAsyncStar = true; } else { fn.isAsync = checkBodyModifiers(body); } @@ -798,6 +853,9 @@ private void buildFunctionBodyIntoMethod(Dart2Parser.FunctionBodyContext body, M if (body.SYNC_() != null && body.ST() != null) { // sync* generator: lowered by the emitter to a list-collecting body. m.isSyncStar = true; + } else if (body.ASYNC_() != null && body.ST() != null && body.NATIVE_() == null) { + // async* generator: lowered by the emitter onto dart.async.AsyncStar. + m.isAsyncStar = true; } else { m.isAsync = checkBodyModifiers(body); } @@ -853,6 +911,7 @@ private void buildParams(Dart2Parser.FormalParameterListContext list, List(); + List params = new ArrayList(); + buildParams(part.formalParameterList(), params); + for (Param p : params) { + if (!p.named && p.defaultValue == null) { + t.funcParams.add(p.type == null || p.type == TypeRef.VAR ? TypeRef.DYNAMIC : p.type); + } + } + return t; + } + private TypeRef buildFinalConstVarOrType(Dart2Parser.FinalConstVarOrTypeContext ctx) { if (ctx.type() != null) { return buildType(ctx.type()); @@ -1152,8 +1236,28 @@ private Stmt buildLocalFunction(Dart2Parser.LocalFunctionDeclarationContext ctx) lf.returnType = sig.type() != null ? buildType(sig.type()) : TypeRef.VAR; lf.name = sig.identifier().getText(); buildParams(sig.formalParameterPart().formalParameterList(), lf.params); + if (sig.formalParameterPart().typeParameters() != null) { + // `T id(T t) => t;` is a local bound to a Funcs.* lambda, which cannot be + // generic: its type parameters are erased to dynamic, as a generic closure's + // are (buildLambda), and the call site coerces the dynamic result. + List tps = new ArrayList(); + for (Dart2Parser.TypeParameterContext tp + : sig.formalParameterPart().typeParameters().typeParameter()) { + tps.add(tp.identifier().getText()); + } + for (Param p : lf.params) { + p.type = eraseTypeParams(p.type, tps); + } + lf.returnType = eraseTypeParams(lf.returnType, tps); + } Dart2Parser.FunctionBodyContext body = ctx.functionBody(); - lf.isAsync = checkBodyModifiers(body); + if (body.ST() != null && body.block() != null && body.NATIVE_() == null) { + // A local generator is lowered like a generator closure (see buildLambda). + lf.isSyncStar = body.SYNC_() != null; + lf.isAsyncStar = body.SYNC_() == null; + } else { + lf.isAsync = checkBodyModifiers(body); + } if (body.block() != null) { lf.body = buildBlock(body.block()); } else if (body.expr() != null) { @@ -1490,14 +1594,15 @@ private Stmt buildLocalVar(Dart2Parser.LocalVariableDeclarationContext ctx) { } private Stmt buildFor(Dart2Parser.ForStatementContext ctx) { - if (ctx.AWAIT_() != null) { - unsupported(ctx, "E0302", "await for is not supported yet (M3)"); + Dart2Parser.ForLoopPartsContext parts = ctx.forLoopParts(); + if (ctx.AWAIT_() != null && parts.IN_() == null) { + unsupported(ctx, "E0302", "await is only valid on a for-in loop"); return null; } - Dart2Parser.ForLoopPartsContext parts = ctx.forLoopParts(); if (parts.IN_() != null) { ForInStmt fi = new ForInStmt(); pos(fi, ctx); + fi.isAwait = ctx.AWAIT_() != null; if (parts.pattern() != null) { // Dart 3 pattern for-in: `for (final (a, b) in xs)` / `for (var [x] in xs)` fi.pattern = buildPattern(parts.pattern()); @@ -1686,7 +1791,12 @@ private Expr buildExprWithoutCascade(Dart2Parser.ExpressionWithoutCascadeContext if (ctx.conditionalExpression() != null) { return buildConditional(ctx.conditionalExpression()); } - unsupported(ctx, "E0118", "throw expressions are not supported yet (M2)"); + if (ctx.throwExpressionWithoutCascade() != null) { + ThrowExpr t = new ThrowExpr(); + pos(t, ctx); + t.value = buildExprWithoutCascade(ctx.throwExpressionWithoutCascade().expressionWithoutCascade()); + return t; + } return errExpr(ctx); } @@ -1698,8 +1808,10 @@ private Expr buildAssignable(Dart2Parser.AssignableExpressionContext ctx) { return id; } if (ctx.SUPER_() != null) { - unsupported(ctx, "E0119", "Assignment through 'super' is not supported"); - return errExpr(ctx); + // `super.x = v` / `super[i] = v`: the inherited setter or index operator. + SuperExpr sup = new SuperExpr(); + pos(sup, ctx); + return applyUnconditional(sup, ctx.unconditionalAssignableSelector()); } // primary assignableSelectorPart : selector* assignableSelector Expr base = buildPrimary(ctx.primary()); @@ -2117,18 +2229,106 @@ private String withoutImportPrefix(String text) { return text; } + /** {@code t} with every reference to one of {@code names} replaced by dynamic. */ + private static TypeRef eraseTypeParams(TypeRef t, List names) { + if (t == null) { + return null; + } + if (t.args.isEmpty() && t.funcParams == null && names.contains(t.name)) { + return TypeRef.DYNAMIC; + } + for (int i = 0; i < t.args.size(); i++) { + t.args.set(i, eraseTypeParams(t.args.get(i), names)); + } + if (t.funcParams != null) { + for (int i = 0; i < t.funcParams.size(); i++) { + t.funcParams.set(i, eraseTypeParams(t.funcParams.get(i), names)); + } + t.funcReturn = eraseTypeParams(t.funcReturn, names); + } + return t; + } + + /** + * {@code factory C.x(params) = D.y;} is a factory whose body is + * {@code return D.y(params);}, every parameter passed on as it was declared: positional + * ones in order, named ones by name. freezed generates one per model class. + */ + private CtorDecl buildRedirectingFactory(Dart2Parser.RedirectingFactoryConstructorSignatureContext sig, + ClassDecl cd) { + CtorDecl ctor = new CtorDecl(); + pos(ctor, sig); + ctor.isFactory = true; + ctor.isConst = sig.CONST_() != null; + buildCtorName(sig.constructorName(), ctor, cd); + buildParams(sig.formalParameterList(), ctor.params); + Dart2Parser.ConstructorDesignationContext des = sig.constructorDesignation(); + CtorCall call = new CtorCall(); + pos(call, des); + String className; + if (des.typeName() != null) { + className = withoutImportPrefix(des.typeName().getText()); + call.ctorName = des.identifier() != null ? des.identifier().getText() : null; + } else { + String text = withoutImportPrefix(des.getText()); + int dot = text.indexOf('.'); + className = dot < 0 ? text : text.substring(0, dot); + call.ctorName = dot < 0 ? null : text.substring(dot + 1); + } + call.type = new TypeRef(className); + pos(call.type, des); + if (des.typeArguments() != null) { + for (Dart2Parser.TypeContext a : des.typeArguments().typeList().type()) { + call.type.args.add(buildType(a)); + } + } + for (Param p : ctor.params) { + Ident id = new Ident(); + pos(id, des); + id.name = p.name; + if (p.named) { + NamedArg na = new NamedArg(); + na.name = p.name; + na.value = id; + call.args.named.add(na); + } else { + call.args.positional.add(id); + } + } + ReturnStmt ret = new ReturnStmt(); + pos(ret, des); + ret.value = call; + ctor.body = new Block(); + pos(ctor.body, des); + ctor.body.statements.add(ret); + return ctor; + } + private Expr buildLambda(Dart2Parser.FunctionExpressionContext ctx) { Lambda l = new Lambda(); pos(l, ctx); + buildParams(ctx.formalParameterPart().formalParameterList(), l.params); if (ctx.formalParameterPart().typeParameters() != null) { - unsupported(ctx, "E0112", "Generic closures are not supported"); + // A generic closure (`(T x) => ...`) cannot be a generic Java lambda; its type + // parameters are erased to dynamic, which is what an uninstantiated call sees. + for (Dart2Parser.TypeParameterContext tp : ctx.formalParameterPart().typeParameters().typeParameter()) { + l.typeParams.add(tp.identifier().getText()); + } + for (Param p : l.params) { + p.type = eraseTypeParams(p.type, l.typeParams); + } } - buildParams(ctx.formalParameterPart().formalParameterList(), l.params); Dart2Parser.FunctionExpressionBodyContext body = ctx.functionExpressionBody(); - if (body.SYNC_() != null || body.ST() != null) { - unsupported(body, "E0303", "Generator closures are not supported"); + if (body.ST() != null) { + if (body.block() == null) { + unsupported(body, "E0303", "A generator closure needs a block body"); + } else if (body.SYNC_() != null) { + l.isSyncStar = true; + } else { + l.isAsyncStar = true; + } } - l.isAsync = body.ASYNC_() != null; + l.isAsync = body.ASYNC_() != null && body.ST() == null; if (body.block() != null) { l.body = buildBlock(body.block()); } else if (body.expr() != null) { @@ -2696,6 +2896,9 @@ public static String mangleOperator(String op) { if (op.equals(">>")) { return "$shr"; } + if (op.equals(">>>")) { + return "$ushr"; + } return null; } diff --git a/maven/dart-transpiler/src/main/resources/com/codename1/dart/stubs/flutter_material.dart b/maven/dart-transpiler/src/main/resources/com/codename1/dart/stubs/flutter_material.dart index c5a14859cb2..d04f8aab747 100644 --- a/maven/dart-transpiler/src/main/resources/com/codename1/dart/stubs/flutter_material.dart +++ b/maven/dart-transpiler/src/main/resources/com/codename1/dart/stubs/flutter_material.dart @@ -404,7 +404,7 @@ abstract class Navigator { } @JavaName('com.codename1.flutter.material.Dialogs.showDialog') -external Future showDialog({BuildContext context, WidgetBuilder builder}); +external Future showDialog({BuildContext context, WidgetBuilder builder}); @JavaName('com.codename1.flutter.material.AlertDialog') class AlertDialog extends Widget { diff --git a/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/LibraryStructureTest.java b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/LibraryStructureTest.java index 210a84ff4e8..b2209281fc8 100644 --- a/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/LibraryStructureTest.java +++ b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/LibraryStructureTest.java @@ -77,10 +77,10 @@ public void aPartOfByLibraryNameJoinsThatLibrary() { @Test public void aConditionalExportRecordsOnlyTheSelectedUri() { Ast.Library lib = new AstBuilder(new Diagnostics()).parse("main.dart", - "export 'x_stub.dart' if (dart.library.io) 'x_io.dart' if (dart.library.async) 'x_async.dart';\n" + "export 'x_stub.dart' if (dart.library.ffi) 'x_ffi.dart' if (dart.library.async) 'x_async.dart';\n" + "import 'y_stub.dart' if (dart.library.html) 'y_web.dart';\n" + "void main() {}\n"); - // dart:io is not provided here, dart:async is: the first true clause wins. + // dart:ffi is not provided here, dart:async is: the first true clause wins. assertEquals("x_async.dart", lib.exportDirectives.get(0).uri); assertEquals("y_stub.dart", lib.imports.get(0)); } diff --git a/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/api/PluginPacksTest.java b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/api/PluginPacksTest.java new file mode 100644 index 00000000000..e292d7528c8 --- /dev/null +++ b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/api/PluginPacksTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.dart.transpiler.api; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/// The transpiler copies a plugin pack into the app only when something names it. +class PluginPacksTest { + private static void write(File f, String s) throws IOException { + f.getParentFile().mkdirs(); + Files.write(f.toPath(), s.getBytes(StandardCharsets.UTF_8)); + } + + /// A runtime class directory with a stub that names one pack, and three packs: + /// share (which names crossfile), crossfile, and geolocator. + private static File runtime(File dir) throws IOException { + write(new File(dir, "META-INF/dart/plugin_share.dart"), + "@JavaName('com.codename1.flutter.plugins.share.Share')\n" + + "class Share {\n external static void share(String text);\n}\n"); + String root = "META-INF/dart/java/com/codename1/flutter/plugins/"; + write(new File(dir, root + "share/Share.java"), + "package com.codename1.flutter.plugins.share;\n" + + "public class Share {\n" + + " public static void share(String t) { com.codename1.flutter.plugins.crossfile.XFile.x(); }\n" + + "}\n"); + write(new File(dir, root + "crossfile/XFile.java"), + "package com.codename1.flutter.plugins.crossfile;\n" + + "public class XFile {\n public static void x() { }\n}\n"); + write(new File(dir, root + "geolocator/Geolocator.java"), + "package com.codename1.flutter.plugins.geolocator;\n" + + "public class Geolocator {\n public static void g() { }\n}\n"); + return dir; + } + + private static File out(File work, String pack, String cls) { + return new File(work, "out/com/codename1/flutter/plugins/" + pack + "/" + cls + ".java"); + } + + @Test + void copiesOnlyNamedPacksAndWhatTheyName() throws IOException { + File work = Files.createTempDirectory("packs").toFile(); + File rt = runtime(new File(work, "rt")); + File src = new File(work, "dart"); + write(new File(src, "main.dart"), "void main() { print('hi'); }\n"); + TranspileRequest req = new TranspileRequest().sourceRoot(src).outputDir(new File(work, "out")) + .stubClasspathEntry(rt).stateFile(new File(work, "state")); + assertFalse(new DartTranspiler().transpile(req).hasErrors()); + assertFalse(new File(work, "out/com/codename1/flutter/plugins").exists(), + "a program that names no pack gets none"); + + write(new File(src, "main.dart"), + "import 'package:share_plus/share_plus.dart';\nvoid main() { Share.share('x'); }\n"); + assertFalse(new DartTranspiler().transpile(req).hasErrors()); + assertTrue(out(work, "share", "Share").isFile()); + assertTrue(out(work, "crossfile", "XFile").isFile(), "share names crossfile"); + assertFalse(out(work, "geolocator", "Geolocator").exists()); + + // The app stops using share: its packs go, rather than linger with their classes. + write(new File(src, "main.dart"), "void main() { print('bye'); }\n"); + assertFalse(new DartTranspiler().transpile(req).hasErrors()); + assertFalse(out(work, "share", "Share").exists()); + assertFalse(out(work, "crossfile", "XFile").exists()); + } + + @Test + void theAppsOwnJavaSelectsAPack() throws IOException { + File work = Files.createTempDirectory("packs").toFile(); + File rt = runtime(new File(work, "rt")); + File src = new File(work, "dart"); + write(new File(src, "main.dart"), "void main() { print('hi'); }\n"); + File java = new File(work, "java"); + TranspileRequest req = new TranspileRequest().sourceRoot(src).outputDir(new File(work, "out")) + .stubClasspathEntry(rt).javaSourceRoot(java).stateFile(new File(work, "state")); + assertFalse(new DartTranspiler().transpile(req).hasErrors()); + assertFalse(out(work, "geolocator", "Geolocator").exists()); + + // Only Java changed, so the Dart digest alone would call this up to date. + write(new File(java, "app/Main.java"), "package app;\nclass Main {\n" + + " void f() { com.codename1.flutter.plugins.geolocator.Geolocator.g(); }\n}\n"); + TranspileResult r = new DartTranspiler().transpile(req); + assertFalse(r.hasErrors()); + assertFalse(r.isUpToDate()); + assertTrue(out(work, "geolocator", "Geolocator").isFile()); + + // A copied pack file deleted by hand fails the cache check, not the compile. + assertTrue(out(work, "geolocator", "Geolocator").delete()); + r = new DartTranspiler().transpile(req); + assertFalse(r.isUpToDate()); + assertTrue(out(work, "geolocator", "Geolocator").isFile()); + } + + @Test + void namedReadsWholeIdentifiers() { + assertEquals("[firebase, share]", PluginPacks.named( + "import com.codename1.flutter.plugins.firebase.FirebaseAuth;\n" + + "x = com.codename1.flutter.plugins.share.Share.share(y);").toString()); + } +} diff --git a/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/BehaviorTest.java b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/BehaviorTest.java index bf13edb1ab8..d9fa30d1811 100644 --- a/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/BehaviorTest.java +++ b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/BehaviorTest.java @@ -54,9 +54,12 @@ public class BehaviorTest { public List behaviorCases() { List tests = new ArrayList(); File[] cases = BEHAVIOR_ROOT.listFiles(); + // A local convenience: CN1_BEHAVIOR_ONLY=plugin_bloc runs just the cases whose + // directory name starts with it. CI never sets it. + String only = System.getenv("CN1_BEHAVIOR_ONLY"); if (cases != null) { for (File dir : cases) { - if (dir.isDirectory()) { + if (dir.isDirectory() && (only == null || dir.getName().startsWith(only))) { tests.add(DynamicTest.dynamicTest(dir.getName(), () -> runCase(dir))); } } @@ -124,9 +127,22 @@ private void runCase(File dir) throws Exception { TestSupport.write(out, gf.content); javacArgs.add(out.getAbsolutePath()); } + List packRoots = new ArrayList(); + for (GeneratedFile gf : r.files) { + packRoots.add(gf.content); + } for (File javaFile : javaSupportFiles) { File out = TestSupport.writeJavaSupportFile(srcRoot, javaFile); javacArgs.add(out.getAbsolutePath()); + packRoots.add(TestSupport.read(javaFile)); + } + // The runtime jar carries no plugin pack classes: the packs the program names are + // compiled with it, exactly as the transpiler copies them into an app. + for (java.util.Map.Entry e : com.codename1.dart.transpiler.api.DartTranspiler + .pluginPackSources(stubEntries, packRoots).entrySet()) { + File out = new File(srcRoot, e.getKey()); + TestSupport.write(out, e.getValue()); + javacArgs.add(out.getAbsolutePath()); } File runner = new File(work, "src/Runner.java"); TestSupport.write(runner, "public class Runner {\n" diff --git a/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/TestSupport.java b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/TestSupport.java index 12011d7bd26..12581a645ae 100644 --- a/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/TestSupport.java +++ b/maven/dart-transpiler/src/test/java/com/codename1/dart/transpiler/harness/TestSupport.java @@ -204,19 +204,49 @@ private static String moduleDir(String artifactId) { return artifactId; } - /** Runs a process, returns [exitCode, stdout+stderr]. */ + /** + * Runs a process, returns [exitCode, stdout+stderr]. A process still running after + * CN1_DART_PROCESS_TIMEOUT_SECONDS (default 600) is killed and reported as exit -1: + * a generated program that deadlocks (an await nothing completes) otherwise held the + * whole suite, and CI's job, for hours with no output naming the case. + */ public static Object[] run(List cmd, File dir) throws IOException, InterruptedException { ProcessBuilder pb = new ProcessBuilder(cmd); pb.directory(dir); pb.redirectErrorStream(true); - Process p = pb.start(); - java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); - byte[] chunk = new byte[8192]; - int n; - while ((n = p.getInputStream().read(chunk)) > 0) { - out.write(chunk, 0, n); + final Process p = pb.start(); + final java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + Thread reader = new Thread(() -> { + byte[] chunk = new byte[8192]; + int n; + try { + while ((n = p.getInputStream().read(chunk)) > 0) { + synchronized (out) { + out.write(chunk, 0, n); + } + } + } catch (IOException ignored) { + // the process was killed; what was read so far is reported + } + }); + reader.setDaemon(true); + reader.start(); + long limit = 600; + String env = System.getenv("CN1_DART_PROCESS_TIMEOUT_SECONDS"); + if (env != null && env.matches("[0-9]+")) { + limit = Long.parseLong(env); + } + if (!p.waitFor(limit, java.util.concurrent.TimeUnit.SECONDS)) { + p.destroyForcibly(); + reader.join(5000); + synchronized (out) { + return new Object[] {-1, new String(out.toByteArray(), StandardCharsets.UTF_8) + + "\n[TIMED OUT after " + limit + "s: " + String.join(" ", cmd) + "]"}; + } + } + reader.join(); + synchronized (out) { + return new Object[] {p.exitValue(), new String(out.toByteArray(), StandardCharsets.UTF_8)}; } - int code = p.waitFor(); - return new Object[] {code, new String(out.toByteArray(), StandardCharsets.UTF_8)}; } } diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_block_callback_then/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_block_callback_then/expect.txt new file mode 100644 index 00000000000..3f84b748889 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_block_callback_then/expect.txt @@ -0,0 +1,3 @@ +a +null +caught bad diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_block_callback_then/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dart_block_callback_then/main.dart new file mode 100644 index 00000000000..c2cff115eca --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_block_callback_then/main.dart @@ -0,0 +1,22 @@ +class User { + String name = 'a'; +} + +Future load() async => User(); + +Future main() async { + await load().then((u) { + print(u.name); + }); + final n = await Future.value(2).then((v) { + if (v > 1) { + return; + } + print('small'); + }); + print(n); + await Future.error('bad').catchError((e) { + print('caught $e'); + return 'x'; + }); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_call_defaults/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_call_defaults/expect.txt new file mode 100644 index 00000000000..99d162f9f6c --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_call_defaults/expect.txt @@ -0,0 +1,5 @@ +1 +6 +hello world +hello dart +hello world! diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_call_defaults/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_call_defaults/main.dart new file mode 100644 index 00000000000..e61a8faf445 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_call_defaults/main.dart @@ -0,0 +1,18 @@ +class Counter { + int total = 0; + int add([int count = 1]) { + total += count; + return total; + } + + String greet({String name = 'world', String? suffix}) => 'hello $name${suffix ?? ''}'; +} + +void main() { + dynamic c = Counter(); + print(c.add()); + print(c.add(5)); + print(c.greet()); + print(c.greet(name: 'dart')); + print(c.greet(suffix: '!')); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_setters_generics/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_setters_generics/expect.txt new file mode 100644 index 00000000000..563150ab7db --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_setters_generics/expect.txt @@ -0,0 +1,5 @@ +6 +y +[7, 7] +TypeError +TypeError diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_setters_generics/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_setters_generics/main.dart new file mode 100644 index 00000000000..934b43390bb --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_dynamic_setters_generics/main.dart @@ -0,0 +1,33 @@ +class Box { + int _count = 0; + int get count => _count; + set count(int v) { + _count = v * 2; + } + + T pick(T a, T b, bool first) => first ? a : b; + + List pair(T a) => [a, a]; + + int add(int a, int b) => a + b; +} + +void main() { + dynamic b = Box(); + b.count = 3; + print(b.count); + print(b.pick('x', 'y', false)); + print(b.pair(7)); + try { + b.add('1', 2); + print('no error'); + } on TypeError { + print('TypeError'); + } + try { + b.count = 'nine'; + print('no error'); + } on TypeError { + print('TypeError'); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_eval_order_unary_generic_local/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_eval_order_unary_generic_local/expect.txt new file mode 100644 index 00000000000..4bdd6223bbd --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_eval_order_unary_generic_local/expect.txt @@ -0,0 +1,6 @@ +5 [0, 5] +-2 +20 +-4 +4 +1 diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_eval_order_unary_generic_local/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dart_eval_order_unary_generic_local/main.dart new file mode 100644 index 00000000000..8ef8059d1b7 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_eval_order_unary_generic_local/main.dart @@ -0,0 +1,32 @@ +int n = 0; +int next() { n++; return n; } + +class G { + final List v = [0, 0]; + int operator [](int i) => v[i]; + void operator []=(int i, int x) { v[i] = x; } +} + +class V { + final int x; + const V(this.x); + V operator -() => V(-x); + V operator ~() => V(x * 10); + V operator +(V o) => V(x + o.x); +} + +void main() { + var g = G(); + int y = n + (g[next()] = 5); + print('$y ${g.v}'); + + var v = const V(2); + print((-v).x); + print((~v).x); + print((-(v + v)).x); + + T id(T t) => t; + print(id(3) + 1); + print(id('s').length); + +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_index_incdec/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_index_incdec/expect.txt new file mode 100644 index 00000000000..9c41bebd960 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_index_incdec/expect.txt @@ -0,0 +1,8 @@ +2 +3 +4 +[1, 3, 3] +[11, 20, 30] 1 +20 +[11, 21, 30] +2 1 diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_index_incdec/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dart_index_incdec/main.dart new file mode 100644 index 00000000000..6c2685fd80f --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_index_incdec/main.dart @@ -0,0 +1,27 @@ +void main() { + var a = [1, 2, 3]; + var i = 0; + a[i]++; + print(a[i]--); + print(++a[1]); + var m = {'k': 5}; + m['k']--; + print(m['k']); + print(a); + dyn(); +} + +var calls = 0; +dynamic box = {'x': 1}; +dynamic getBox() { calls++; return box; } + +void dyn() { + dynamic d = [10, 20, 30]; + var i = 0; + d[i++] += 1; + print('$d $i'); + print(d[1]++); + print(d); + getBox()['x'] += 1; + print('${box['x']} $calls'); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_local_generators/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_local_generators/expect.txt new file mode 100644 index 00000000000..dc817022826 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_local_generators/expect.txt @@ -0,0 +1,3 @@ +[0, 2, 4] +a +b diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_local_generators/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dart_local_generators/main.dart new file mode 100644 index 00000000000..4fba840e3d7 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_local_generators/main.dart @@ -0,0 +1,17 @@ +Future main() async { + Iterable evens(int n) sync* { + for (var i = 0; i < n; i++) { + if (i.isEven) yield i; + } + } + + Stream words() async* { + yield 'a'; + yield 'b'; + } + + print(evens(6).toList()); + await for (final w in words()) { + print(w); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_review_round30/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_review_round30/expect.txt index b787044217b..beedb333578 100644 --- a/maven/dart-transpiler/src/test/resources/behavior/dart_review_round30/expect.txt +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_review_round30/expect.txt @@ -6,7 +6,7 @@ [3, 1] [7] [4, 6] -stub +io B part:p! 42 diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_stream_transform_errors/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dart_stream_transform_errors/expect.txt new file mode 100644 index 00000000000..375bee92604 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_stream_transform_errors/expect.txt @@ -0,0 +1,4 @@ +data 1, error replaced boom, data 2 +1, err cmp, 3 +source cancelled: true +1, err boom, 2, 3 diff --git a/maven/dart-transpiler/src/test/resources/behavior/dart_stream_transform_errors/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dart_stream_transform_errors/main.dart new file mode 100644 index 00000000000..3a3d3d3cfb9 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dart_stream_transform_errors/main.dart @@ -0,0 +1,60 @@ +import 'dart:async'; + +Future main() async { + // handleError's own throw is emitted downstream + final c = StreamController(); + final seen = []; + final done = Completer(); + void Function(Object) replace = (e) => throw StateError('replaced $e'); + c.stream.handleError(replace).listen( + (v) => seen.add('data $v'), + onError: (e) => seen.add('error ${e is StateError ? e.message : e}'), + onDone: () => done.complete()); + c.add(1); + c.addError('boom'); + c.add(2); + await c.close(); + await done.future; + print(seen.join(', ')); + + // a throwing distinct equality turns one event into an error and goes on + final out = []; + final d = Completer(); + Stream.fromIterable([1, 1, 2, 3, 3]).distinct((a, b) { + if (b == 2) throw 'cmp'; + return a == b; + }).listen((v) => out.add('$v'), onError: (e) => out.add('err $e'), onDone: () => d.complete()); + await d.future; + print(out.join(', ')); + + // cancelling a derived stream cancels its quiet source + var cancelled = false; + void Function() markCancelled = () { + cancelled = true; + }; + final src = StreamController(onCancel: markCancelled); + final sub = src.stream.distinct().listen((_) {}); + await Future.delayed(Duration.zero); + await sub.cancel(); + await Future.delayed(Duration.zero); + print('source cancelled: $cancelled'); + + // a derived operator forwards a source error and keeps reading after it + final after = []; + final a = Completer(); + // fed from onListen: the derived stream subscribes to its source asynchronously, + // and a controller here does not buffer events added before anyone listens + late StreamController ctl; + void Function() feed = () { + ctl.add(1); + ctl.addError('boom'); + ctl.add(2); + ctl.add(3); + ctl.add(4); + }; + ctl = StreamController(onListen: feed); + ctl.stream.take(3).listen((v) => after.add('$v'), onError: (e) => after.add('err $e'), + onDone: () => a.complete()); + await a.future; + print(after.join(', ')); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/dynamic_runtime_superclass/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/dynamic_runtime_superclass/expect.txt new file mode 100644 index 00000000000..dd16c7b231e --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dynamic_runtime_superclass/expect.txt @@ -0,0 +1,5 @@ +Miso +hi Miso +true +true +1 diff --git a/maven/dart-transpiler/src/test/resources/behavior/dynamic_runtime_superclass/main.dart b/maven/dart-transpiler/src/test/resources/behavior/dynamic_runtime_superclass/main.dart new file mode 100644 index 00000000000..49ced5114b0 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/dynamic_runtime_superclass/main.dart @@ -0,0 +1,27 @@ +// A model class whose superclass comes from a runtime stub (Equatable) is still +// model data: read through `dynamic`, its fields and methods must resolve. It was +// left without dynamic members and threw NoSuchMethodError. Equal props with 1 and +// 1.0 must also hash alike, since Equatable compares them equal. +import 'package:equatable/equatable.dart'; + +class Pet extends Equatable { + final String name; + final num age; + const Pet(this.name, this.age); + + String greet() => 'hi $name'; + + @override + List get props => [name, age]; +} + +void main() { + dynamic pet = Pet('Miso', 2); + print(pet.name); + print(pet.greet()); + final a = Pet('A', 1); + final b = Pet('A', 1.0); + print(a == b); + print(a.hashCode == b.hashCode); + print({a, b}.length); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_async_star/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_async_star/expect.txt new file mode 100644 index 00000000000..e5e2e1cf65a --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_async_star/expect.txt @@ -0,0 +1,9 @@ +sum 10 +start +v2 +v4 +end +break at 3 +got 1 +error boom +done diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_async_star/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_async_star/main.dart new file mode 100644 index 00000000000..ebe361d1090 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_async_star/main.dart @@ -0,0 +1,53 @@ +import 'dart:async'; + +Stream countTo(int n) async* { + for (var i = 1; i <= n; i++) { + await Future.delayed(Duration.zero); + yield i; + } +} + +Stream doubled(Stream source) async* { + await for (final v in source) { + yield v * 2; + } +} + +Stream labels() async* { + yield 'start'; + yield* doubled(countTo(2)).map((v) => 'v$v'); + yield 'end'; +} + +Stream failing() async* { + yield 1; + throw StateError('boom'); +} + +Future main() async { + var sum = 0; + await for (final v in countTo(4)) { + sum += v; + } + print('sum $sum'); + + await for (final s in labels()) { + print(s); + } + + await for (final v in countTo(100)) { + if (v == 3) { + print('break at $v'); + break; + } + } + + try { + await for (final v in failing()) { + print('got $v'); + } + } on StateError catch (e) { + print('error ${e.message}'); + } + print('done'); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_dynamic/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_dynamic/expect.txt new file mode 100644 index 00000000000..0af48a6427f --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_dynamic/expect.txt @@ -0,0 +1,11 @@ +3 +2 +TEXT +true +[2, 4, 6] +rex +rex/3 +woof +hi ann from rex? +7 +no fly diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_dynamic/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_dynamic/main.dart new file mode 100644 index 00000000000..986c4abcc2b --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_dynamic/main.dart @@ -0,0 +1,33 @@ +class Pet { + String name; + int age; + Pet(this.name, this.age); + String get label => '$name/$age'; + String greet(String who, {String punct = '!'}) => 'hi $who from $name$punct'; +} + +class Dog extends Pet { + Dog(String n) : super(n, 3); + String bark() => 'woof'; +} + +void main() { + dynamic m = {'a': [1, 2, 3], 'b': 'text'}; + print(m['a'].length); + print(m['a'][1]); + print(m['b'].toUpperCase()); + print(m.containsKey('a')); + print(m['a'].map((x) => x * 2).toList()); + dynamic d = Dog('rex'); + print(d.name); + print(d.label); + print(d.bark()); + print(d.greet('ann', punct: '?')); + d.age = 7; + print(d.age); + try { + d.fly(); + } on NoSuchMethodError { + print('no fly'); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_function_typed_params/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_function_typed_params/expect.txt new file mode 100644 index 00000000000..5f4024454c8 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_function_typed_params/expect.txt @@ -0,0 +1,5 @@ +42 +0:a +1:b +[x] +null diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_function_typed_params/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_function_typed_params/main.dart new file mode 100644 index 00000000000..f33329a24f0 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_function_typed_params/main.dart @@ -0,0 +1,16 @@ +int apply(int value, int transform(int v)) => transform(value); + +void each(List items, void visit(String item, int index)) { + for (var i = 0; i < items.length; i++) { + visit(items[i], i); + } +} + +String? maybe(String s, String format(String x)?) => format == null ? null : format(s); + +void main() { + print(apply(20, (v) => v * 2 + 2)); + each(['a', 'b'], (item, index) => print('$index:$item')); + print(maybe('x', (x) => '[$x]')); + print(maybe('x', null)); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_generator_closures/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_generator_closures/expect.txt new file mode 100644 index 00000000000..dd37ddd1107 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_generator_closures/expect.txt @@ -0,0 +1,8 @@ +sq 0 +sq 1 +sq 4 +sq 9 +a,b +7 +seven +1 diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_generator_closures/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_generator_closures/main.dart new file mode 100644 index 00000000000..5b0755d803a --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_generator_closures/main.dart @@ -0,0 +1,26 @@ +import 'dart:async'; + +typedef Gen = Stream Function(int n); + +Stream run(Gen g, int n) => g(n); + +Iterable pick(Iterable Function() make) => make(); + + +Future main() async { + await for (final v in run((n) async* { + for (var i = 0; i < n; i++) { + yield i * i; + } + }, 4)) { + print('sq $v'); + } + print(pick(() sync* { + yield 'a'; + yield 'b'; + }).join(',')); + final echo = (T x) => x; + print(echo(7)); + print(echo('seven')); + print(1 >>> 0); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_mixin_application/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_mixin_application/expect.txt new file mode 100644 index 00000000000..7729282d0d5 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_mixin_application/expect.txt @@ -0,0 +1,4 @@ +BELL/3 +2 +mouse/0 +true diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_mixin_application/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_mixin_application/main.dart new file mode 100644 index 00000000000..8b70927cb9b --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_mixin_application/main.dart @@ -0,0 +1,29 @@ +class Base { + final String name; + final int size; + Base(this.name, {this.size = 1}); + Base.small(String n) : this(n, size: 0); + String describe() => '$name/$size'; +} + +mixin Loud on Base { + String shout() => describe().toUpperCase(); +} + +mixin Counted { + int count = 0; + void tick() => count++; +} + +class LoudBase = Base with Loud, Counted; + +void main() { + final a = LoudBase('bell', size: 3); + a.tick(); + a.tick(); + print(a.shout()); + print(a.count); + final b = LoudBase.small('mouse'); + print(b.describe()); + print(b is Base); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_redirecting_factory/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_redirecting_factory/expect.txt new file mode 100644 index 00000000000..cabf9ec001a --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_redirecting_factory/expect.txt @@ -0,0 +1,4 @@ +12.0 +9.0 +_Box(5) +true diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_redirecting_factory/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_redirecting_factory/main.dart new file mode 100644 index 00000000000..c0d691e5cf6 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_redirecting_factory/main.dart @@ -0,0 +1,39 @@ +abstract class Shape { + const factory Shape.circle(double r) = Circle; + const factory Shape.square({required double side}) = Square._; + double get area; +} + +class Circle implements Shape { + final double r; + const Circle(this.r); + @override + double get area => 3 * r * r; +} + +class Square implements Shape { + final double side; + const Square._({required this.side}); + @override + double get area => side * side; +} + +class Box { + final T value; + Box(this.value); + factory Box.of(T v) = _Box; + String show() => 'Box($value)'; +} + +class _Box extends Box { + _Box(T v) : super(v); + @override + String show() => '_Box($value)'; +} + +void main() { + print(Shape.circle(2).area); + print(Shape.square(side: 3).area); + print(Box.of(5).show()); + print(Shape.circle(1) is Circle); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_super_assign/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_super_assign/expect.txt new file mode 100644 index 00000000000..a9186304b4a --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_super_assign/expect.txt @@ -0,0 +1,3 @@ +0 +7 +2 diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_super_assign/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_super_assign/main.dart new file mode 100644 index 00000000000..b0befd09aa2 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_super_assign/main.dart @@ -0,0 +1,25 @@ +class Base { + int _v = 0; + int count = 0; + int get value => _v; + set value(int v) { + _v = v; + } +} + +class Clamped extends Base { + @override + set value(int v) { + super.value = v < 0 ? 0 : v; + super.count += 1; + } +} + +void main() { + final c = Clamped(); + c.value = -5; + print(c.value); + c.value = 7; + print(c.value); + print(c.count); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_throw_expr/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lang_throw_expr/expect.txt new file mode 100644 index 00000000000..8b9f9537eb6 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_throw_expr/expect.txt @@ -0,0 +1,4 @@ +42 +caught: Invalid argument(s): null input +ok +state: missing diff --git a/maven/dart-transpiler/src/test/resources/behavior/lang_throw_expr/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lang_throw_expr/main.dart new file mode 100644 index 00000000000..e822a119ca0 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lang_throw_expr/main.dart @@ -0,0 +1,18 @@ +int parse(String? s) => s == null ? throw ArgumentError('null input') : int.parse(s); + +String need(String? v) => v ?? (throw StateError('missing')); + +void main() { + print(parse('42')); + try { + parse(null); + } catch (e) { + print('caught: $e'); + } + print(need('ok')); + try { + need(null); + } on StateError catch (e) { + print('state: ${e.message}'); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lib_convert/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lib_convert/expect.txt new file mode 100644 index 00000000000..9359c93154f --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lib_convert/expect.txt @@ -0,0 +1,17 @@ +Ada 36 2 +9.5 +true +null +{"name":"Ada","age":36,"tags":["math","code"]} +[1,2.5,"a\"b",null,{"k":false}] +{ + "a": [ + 1 + ] +} +3 +hé +aGVsbG8= +hello +3 +format error diff --git a/maven/dart-transpiler/src/test/resources/behavior/lib_convert/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lib_convert/main.dart new file mode 100644 index 00000000000..b29b0a754fc --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lib_convert/main.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +class User { + final String name; + final int age; + final List tags; + User(this.name, this.age, this.tags); + + factory User.fromJson(Map json) => User( + json['name'] as String, + json['age'] as int, + (json['tags'] as List).map((e) => e as String).toList(), + ); + + Map toJson() => {'name': name, 'age': age, 'tags': tags}; +} + +void main() { + final raw = '{"name":"Ada","age":36,"tags":["math","code"],"score":9.5,"ok":true,"none":null}'; + final map = jsonDecode(raw) as Map; + final u = User.fromJson(map); + print('${u.name} ${u.age} ${u.tags.length}'); + print(map['score']); + print(map['ok']); + print(map['none']); + print(jsonEncode(u)); + print(jsonEncode([1, 2.5, 'a"b', null, {'k': false}])); + print(const JsonEncoder.withIndent(' ').convert({'a': [1]})); + final bytes = utf8.encode('hé'); + print(bytes.length); + print(utf8.decode(bytes)); + final b64 = base64Encode(utf8.encode('hello')); + print(b64); + print(utf8.decode(base64Decode(b64))); + print(LineSplitter.split('a\nb\r\nc').length); + try { + jsonDecode('{bad'); + } on FormatException catch (e) { + print('format error'); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/lib_io/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/lib_io/expect.txt new file mode 100644 index 00000000000..5491512be39 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lib_io/expect.txt @@ -0,0 +1,21 @@ +true +line one +line two +3 +28 +3 +b.bin,notes.txt +false +fs error +true +true +content-type +404 +application/json +utf-8 +[2, 4] +a-b +[hello, world] +6 +5 +offline diff --git a/maven/dart-transpiler/src/test/resources/behavior/lib_io/main.dart b/maven/dart-transpiler/src/test/resources/behavior/lib_io/main.dart new file mode 100644 index 00000000000..6d571a47595 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/lib_io/main.dart @@ -0,0 +1,49 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +Future main() async { + final dir = Directory('/data/app'); + await dir.create(recursive: true); + print(dir.existsSync()); + final f = File('${dir.path}/notes.txt'); + await f.writeAsString('line one\nline two'); + print(await f.readAsString()); + await f.writeAsString('\nline three', mode: FileMode.append); + final lines = await f.readAsLines(); + print(lines.length); + print(await f.length()); + File('${dir.path}/b.bin').writeAsBytesSync([1, 2, 3]); + print(File('${dir.path}/b.bin').readAsBytesSync().length); + final names = dir.listSync().map((e) => e.path.split('/').last).toList()..sort(); + print(names.join(',')); + await File('${dir.path}/b.bin').delete(); + print(File('${dir.path}/b.bin').existsSync()); + try { + await File('/nope/x.txt').readAsString(); + } on FileSystemException catch (e) { + print('fs error'); + } + print(Platform.operatingSystem.isNotEmpty); + print(Platform.isIOS || Platform.isAndroid || Platform.isLinux || Platform.isMacOS || Platform.isWindows); + print(HttpHeaders.contentTypeHeader); + print(HttpStatus.notFound); + print(ContentType.json.mimeType); + print(ContentType.parse('text/html; charset=utf-8').charset); + + final s = Stream.fromIterable([1, 2, 3, 4]); + print(await s.where((x) => x.isEven).toList()); + print(await Stream.fromIterable(['a', 'b']).join('-')); + final text = await Stream.fromIterable([utf8.encode('he'), utf8.encode('llo\nworld')]) + .transform(utf8.decoder) + .transform(const LineSplitter()) + .toList(); + print(text); + print(await Stream.fromIterable([1, 2, 3]).fold(0, (a, b) => a + b)); + print(await Stream.fromIterable([5, 6]).first); + try { + await HttpClient().getUrl(Uri.parse('http://example.com')).then((r) => r.close()); + } on SocketException { + print('offline'); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_bloc/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/plugin_bloc/expect.txt new file mode 100644 index 00000000000..3d42910ccac --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_bloc/expect.txt @@ -0,0 +1,16 @@ +cubit 2 seen [1, 2] +bloc 2 +after reset 10 +closed true +emit after close: Cannot emit new states after calling close +CounterCubit 0->1 +CounterCubit 1->2 +transition Incremented +CounterBloc 0->1 +transition Incremented +CounterBloc 1->2 +transition Reset +CounterBloc 2->10 +feed 0 listening false +add returned after a throwing handler +after close listening false diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_bloc/main.dart b/maven/dart-transpiler/src/test/resources/behavior/plugin_bloc/main.dart new file mode 100644 index 00000000000..d9192afd19c --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_bloc/main.dart @@ -0,0 +1,119 @@ +// bloc and flutter_bloc's logic side, headless: a Cubit and a Bloc driven the way +// apps drive them, with an observer, typed event handlers (on must only see +// its own E) and Equatable-free states compared by ==. +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CounterCubit extends Cubit { + CounterCubit() : super(0); + + void increment() => emit(state + 1); + void same() => emit(state); +} + +abstract class CounterEvent {} + +class Incremented extends CounterEvent {} + +class Reset extends CounterEvent { + Reset(this.to); + final int to; +} + +class CounterBloc extends Bloc { + CounterBloc() : super(0) { + on((event, emit) => emit(state + 1)); + on(_onReset); + } + + Future _onReset(Reset event, Emitter emit) async { + await Future.delayed(const Duration(milliseconds: 5)); + emit(event.to); + } +} + +class Log extends BlocObserver { + final List lines = []; + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + lines.add('${bloc.runtimeType} ${change.currentState}->${change.nextState}'); + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + lines.add('transition ${transition.event.runtimeType}'); + } +} + +abstract class FeedEvent {} + +class Follow extends FeedEvent {} + +class Boom extends FeedEvent {} + +class FeedBloc extends Bloc { + FeedBloc(this.source) : super(0) { + // A block handler returning nothing. Its forEach is not awaited, so when the + // handler returns bloc completes the emitter, which disposes the subscription + // (release behaviour; a debug build also asserts) -- nothing leaks past it. + on((event, emit) { + emit.forEach(source.stream, onData: (i) => i); + return; + }); + on((event, emit) => throw StateError('boom')); + } + + final StreamController source; +} + +Future feed() async { + final source = StreamController(); + final bloc = FeedBloc(source); + bloc.add(Follow()); + source.add(7); + await Future.delayed(const Duration(milliseconds: 5)); + print('feed ${bloc.state} listening ${source.hasListener}'); + bloc.add(Boom()); + print('add returned after a throwing handler'); + await bloc.close(); + print('after close listening ${source.hasListener}'); +} + +Future main() async { + final log = Log(); + Bloc.observer = log; + + final cubit = CounterCubit(); + final seen = []; + final sub = cubit.stream.listen(seen.add); + cubit.increment(); + cubit.increment(); + cubit.same(); + await Future.delayed(Duration.zero); + print('cubit ${cubit.state} seen $seen'); + await sub.cancel(); + + final bloc = CounterBloc(); + bloc.add(Incremented()); + bloc.add(Incremented()); + print('bloc ${bloc.state}'); + bloc.add(Reset(10)); + await Future.delayed(const Duration(milliseconds: 50)); + print('after reset ${bloc.state}'); + + await cubit.close(); + print('closed ${cubit.isClosed}'); + try { + cubit.increment(); + } on StateError catch (e) { + print('emit after close: ${e.message}'); + } + for (final l in log.lines) { + print(l); + } + Bloc.observer = BlocObserver(); + await feed(); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_device_packs/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/plugin_device_packs/expect.txt new file mode 100644 index 00000000000..6e11fb5820e --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_device_packs/expect.txt @@ -0,0 +1,13 @@ +berlin-paris 878 km +north 0 +Pets 1.2.3+45 +offline true +a.txt text/plain hello 5 +details true +platform impl true +before init: no-app +apps 1 p +headless: network-request-failed +user null +home 10.0 -170.0 Home +camera 1.0 12.0 diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_device_packs/main.dart b/maven/dart-transpiler/src/test/resources/behavior/plugin_device_packs/main.dart new file mode 100644 index 00000000000..2ae645280e5 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_device_packs/main.dart @@ -0,0 +1,64 @@ +// The device plugin packs, headless: the parts that answer without a platform -- +// math, mocks, in-memory files, option holders and Firebase's failure paths. +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:cross_file/cross_file.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +Future main() async { + try { await run(); } catch (e) { print('ERR $e'); } +} + +Future run() async { + final km = Geolocator.distanceBetween(52.5200, 13.4050, 48.8566, 2.3522) / 1000; + print('berlin-paris ${km.round()} km'); + final bearing = Geolocator.bearingBetween(0, 0, 1, 0); + print('north ${bearing.round()}'); + + PackageInfo.setMockInitialValues(appName: 'Pets', packageName: 'com.example.pets', + version: '1.2.3', buildNumber: '45', buildSignature: ''); + final info = await PackageInfo.fromPlatform(); + print('${info.appName} ${info.version}+${info.buildNumber}'); + + final net = await Connectivity().checkConnectivity(); + print('offline ${net.contains(ConnectivityResult.none)}'); + + final file = XFile.fromData(Uint8List.fromList(utf8.encode('hello')), name: 'a.txt', mimeType: 'text/plain'); + print('${file.name} ${file.mimeType} ${await file.readAsString()} ${await file.length()}'); + + const details = NotificationDetails( + android: AndroidNotificationDetails('pets', 'Pets', importance: Importance.high, priority: Priority.high)); + print('details ${details != null}'); + final plugin = FlutterLocalNotificationsPlugin(); + final android = plugin.resolvePlatformSpecificImplementation(); + print('platform impl ${android == null}'); + + try { + await FirebaseAuth.instance.signInWithEmailAndPassword(email: 'a@b.c', password: 'x'); + } on FirebaseAuthException catch (e) { + print('before init: ${e.code}'); + } + await Firebase.initializeApp( + options: const FirebaseOptions(apiKey: 'k', appId: 'a', messagingSenderId: 'm', projectId: 'p')); + print('apps ${Firebase.apps.length} ${Firebase.app().options.projectId}'); + try { + await FirebaseAuth.instance.signInAnonymously(); + } on FirebaseAuthException catch (e) { + print('headless: ${e.code}'); + } + print('user ${FirebaseAuth.instance.currentUser}'); + + final m = Marker(markerId: const MarkerId('home'), position: const LatLng(10, 190), + infoWindow: const InfoWindow(title: 'Home')); + print('${m.markerId.value} ${m.position.latitude} ${m.position.longitude} ${m.infoWindow.title}'); + final cam = CameraPosition(target: const LatLng(1, 2), zoom: 12); + print('camera ${cam.target.latitude} ${cam.zoom}'); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_dio/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/plugin_dio/expect.txt new file mode 100644 index 00000000000..b380c56d498 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_dio/expect.txt @@ -0,0 +1,7 @@ +caught DioExceptionType.connectionError /pets +auth GET https://example.com/api/pets?species=dog&page=2 +headers Bearer t0k3n application/json +error connectionError +recovered 200 {cached: true} POST +201 mock /hello +legacy name catches cancel diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_dio/main.dart b/maven/dart-transpiler/src/test/resources/behavior/plugin_dio/main.dart new file mode 100644 index 00000000000..e11e0061128 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_dio/main.dart @@ -0,0 +1,71 @@ +// dio headless: there is no network, so every request fails as a connectionError +// -- which is exactly an app's offline path -- and the interceptor chain runs as +// dio runs it: request hooks in order, then the error hooks, and an error hook can +// recover with a response. Options merge from the instance down to the request. +import 'package:dio/dio.dart'; + +class AuthInterceptor extends Interceptor { + AuthInterceptor(this.log); + final List log; + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + options.headers['Authorization'] = 'Bearer t0k3n'; + log.add('auth ${options.method} ${options.uri}'); + handler.next(options); + } +} + +Future main() async { + final log = []; + final dio = Dio(BaseOptions( + baseUrl: 'https://example.com/api', + connectTimeout: const Duration(seconds: 5), + headers: {'Accept': 'application/json'}, + )); + dio.interceptors.add(AuthInterceptor(log)); + dio.interceptors.add(InterceptorsWrapper( + onRequest: (options, handler) { + log.add('headers ${options.headers['Authorization']} ${options.headers['Accept']}'); + handler.next(options); + }, + onError: (e, handler) { + log.add('error ${e.type.name}'); + handler.next(e); + }, + )); + + try { + await dio.get>('/pets', queryParameters: {'species': 'dog', 'page': 2}); + print('unexpected success'); + } on DioException catch (e) { + print('caught ${e.type} ${e.requestOptions.path}'); + } + for (final l in log) { + print(l); + } + + // An error interceptor that answers from a cache instead. + final cached = Dio(); + cached.interceptors.add(InterceptorsWrapper(onError: (e, handler) { + handler.resolve(Response(requestOptions: e.requestOptions, data: {'cached': true}, statusCode: 200)); + })); + final r = await cached.post>('https://example.com/x', data: {'a': 1}); + print('recovered ${r.statusCode} ${r.data} ${r.requestOptions.method}'); + + // A request interceptor that answers without sending. + final mocked = Dio(); + mocked.interceptors.add(InterceptorsWrapper(onRequest: (o, handler) { + handler.resolve(Response(requestOptions: o, data: 'mock ${o.path}', statusCode: 201)); + })); + final m = await mocked.get('/hello'); + print('${m.statusCode} ${m.data}'); + + final token = CancelToken(); + token.cancel('bye'); + try { + await Dio().get('https://example.com', cancelToken: token); + } on DioError catch (e) { + print('legacy name catches ${e.type.name}'); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/expect.txt new file mode 100644 index 00000000000..ebb80262926 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/expect.txt @@ -0,0 +1,8 @@ +equal true hash true +identical false +Pet(7, Rex, Species.dog, [a, b], 2024-03-05 10:20:30.000) +{id: 7, name: Rex, species: dog, tags: [a, b], born: 2024-03-05T10:20:30.000} +differs false +2 +[1, 2, 30] +bad enum: `fish` is not one of the supported values: dog, cat diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/main.dart b/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/main.dart new file mode 100644 index 00000000000..3b6e4afee10 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/main.dart @@ -0,0 +1,48 @@ +// equatable value equality and the code json_serializable generates, as an app +// commits it: `part` file, $enumDecode, `(json['x'] as num).toInt()`, DateTime +// round trips, and the `int.parse` tear-off. +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'pet.g.dart'; + +enum Species { dog, cat } + +@JsonSerializable() +class Pet extends Equatable { + const Pet({required this.id, required this.name, required this.species, required this.tags, required this.born}); + + final int id; + final String name; + final Species species; + final List tags; + final DateTime born; + + factory Pet.fromJson(Map json) => _$PetFromJson(json); + Map toJson() => _$PetToJson(this); + + @override + List get props => [id, name, species, tags, born]; +} + +void main() { + final json = jsonDecode('{"id": 7, "name": "Rex", "species": "dog", "tags": ["a", "b"], ' + '"born": "2024-03-05T10:20:30.000"}') as Map; + final a = Pet.fromJson(json); + final b = Pet.fromJson(jsonDecode(jsonEncode(a.toJson())) as Map); + print('equal ${a == b} hash ${a.hashCode == b.hashCode}'); + print('identical ${identical(a, b)}'); + print(a); + print(a.toJson()); + final c = Pet(id: 7, name: 'Rex', species: Species.cat, tags: const ['a', 'b'], born: a.born); + print('differs ${a == c}'); + print({a, b, c}.length); + print(['1', '2', '30'].map(int.parse).toList()); + try { + $enumDecode(_$SpeciesEnumMap, 'fish'); + } on ArgumentError catch (e) { + print('bad enum: ${e.message}'); + } +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/pet.g.dart b/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/pet.g.dart new file mode 100644 index 00000000000..1336124d080 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_equatable_json/pet.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'main.dart'; + +Pet _$PetFromJson(Map json) => Pet( + id: (json['id'] as num).toInt(), + name: json['name'] as String, + species: $enumDecode(_$SpeciesEnumMap, json['species']), + tags: (json['tags'] as List).map((e) => e as String).toList(), + born: DateTime.parse(json['born'] as String), + ); + +Map _$PetToJson(Pet instance) => { + 'id': instance.id, + 'name': instance.name, + 'species': _$SpeciesEnumMap[instance.species]!, + 'tags': instance.tags, + 'born': instance.born.toIso8601String(), + }; + +const _$SpeciesEnumMap = { + Species.dog: 'dog', + Species.cat: 'cat', +}; diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_go_router/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/plugin_go_router/expect.txt new file mode 100644 index 00000000000..2e878abaf19 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_go_router/expect.txt @@ -0,0 +1,9 @@ +push /pets/42 pet {id: 42} {sort: age} biscuit +true +false +/pets/a%20b?q=1 +named /pets/7 pet {id: 7} {} null +guarded /login null {} {} null +redirected /account/profile null {} {} null +error Exception: no routes for location: /nowhere +false diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_go_router/main.dart b/maven/dart-transpiler/src/test/resources/behavior/plugin_go_router/main.dart new file mode 100644 index 00000000000..a899d2a08eb --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_go_router/main.dart @@ -0,0 +1,46 @@ +// go_router, headless: locations match nested patterns and capture their +// parameters, named routes build their locations, and redirects apply in order. +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +bool signedIn = false; + +final router = GoRouter( + initialLocation: '/', + redirect: (context, state) => + !signedIn && state.matchedLocation.startsWith('/account') ? '/login' : null, + routes: [ + GoRoute(path: '/', name: 'home', builder: (context, state) => const Text('home'), routes: [ + GoRoute(path: 'pets/:id', name: 'pet', builder: (context, state) => Text('pet ${state.pathParameters['id']}')), + ]), + GoRoute(path: '/login', builder: (context, state) => const Text('login')), + GoRoute(path: '/account', redirect: (context, state) => '/account/profile', routes: [ + GoRoute(path: 'profile', builder: (context, state) => const Text('profile')), + ]), + ], +); + +void show(String label) { + final s = router.state!; + print('$label ${s.matchedLocation} ${s.name} ${s.pathParameters} ${s.uri.queryParameters} ${s.extra}'); +} + +Future main() async { + router.push('/pets/42?sort=age', extra: 'biscuit'); + show('push'); + print(router.canPop()); + router.pop(); + print(router.canPop()); + print(router.namedLocation('pet', pathParameters: {'id': 'a b'}, queryParameters: {'q': '1'})); + router.pushNamed('pet', pathParameters: {'id': '7'}); + show('named'); + router.go('/account'); + show('guarded'); + signedIn = true; + router.go('/account'); + show('redirected'); + router.go('/nowhere'); + print('error ${router.state!.error}'); + router.go('/'); + print(router.canPop()); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_storage/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/plugin_storage/expect.txt new file mode 100644 index 00000000000..b4a6dbb587b --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_storage/expect.txt @@ -0,0 +1,9 @@ +seen true +Biscuit 3 0.5 [1, 2] +wrong type null missing null +keys count,favorites,name,ratio,seen +count 4 true +removed null false +docs file hello +support exists true +tmp exists true diff --git a/maven/dart-transpiler/src/test/resources/behavior/plugin_storage/main.dart b/maven/dart-transpiler/src/test/resources/behavior/plugin_storage/main.dart new file mode 100644 index 00000000000..d7d36e6d883 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/plugin_storage/main.dart @@ -0,0 +1,36 @@ +// shared_preferences and path_provider, headless: values keep their types, a +// missing or differently typed key reads null, and the app directories exist and +// can hold files. +import 'dart:io'; + +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future main() async { + SharedPreferences.setMockInitialValues({'seen': true}); + final prefs = await SharedPreferences.getInstance(); + print('seen ${prefs.getBool('seen')}'); + await prefs.setString('name', 'Biscuit'); + await prefs.setInt('count', 3); + await prefs.setDouble('ratio', 0.5); + await prefs.setStringList('favorites', ['1', '2']); + final again = await SharedPreferences.getInstance(); + print('${again.getString('name')} ${again.getInt('count')} ${again.getDouble('ratio')} ' + '${again.getStringList('favorites')}'); + print('wrong type ${again.getInt('name')} missing ${again.getString('nope')}'); + print('keys ${(again.getKeys().toList()..sort()).join(',')}'); + final count = (again.getInt('count') ?? 0) + 1; + await again.setInt('count', count); + print('count $count ${again.containsKey('count')}'); + await again.remove('name'); + print('removed ${again.getString('name')} ${again.containsKey('name')}'); + + final docs = await getApplicationDocumentsDirectory(); + final file = File('${docs.path}/notes.txt'); + await file.writeAsString('hello'); + print('docs file ${await file.readAsString()}'); + final support = await getApplicationSupportDirectory(); + print('support exists ${await support.exists()}'); + final tmp = await getTemporaryDirectory(); + print('tmp exists ${tmp.existsSync()}'); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/stream_callbacks_typed/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/stream_callbacks_typed/expect.txt new file mode 100644 index 00000000000..6f874fe963f --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/stream_callbacks_typed/expect.txt @@ -0,0 +1,3 @@ +length 2 +saw one true +offline false diff --git a/maven/dart-transpiler/src/test/resources/behavior/stream_callbacks_typed/main.dart b/maven/dart-transpiler/src/test/resources/behavior/stream_callbacks_typed/main.dart new file mode 100644 index 00000000000..bd0610e6ae2 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/stream_callbacks_typed/main.dart @@ -0,0 +1,33 @@ +// A closure handed to a Stream callback types its parameter from the stream: +// xs is a List, so xs.contains is a typed call and its bool lands in a bool. +// The callbacks were typed Object, the parameter was dynamic, and javac rejected +// the untyped result. +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; + +Future main() async { + final c = StreamController>(); + bool sawOne = false; + final done = Completer(); + c.stream.where((xs) => xs.isNotEmpty).map((xs) => xs.length).listen((n) { + print('length ${n + 0}'); + }); + final c2 = StreamController>(); + c2.stream.listen((xs) { + sawOne = xs.contains(1); + }, onDone: () => done.complete()); + c.add([]); + c.add([1, 2]); + c2.add([3, 1]); + await c2.close(); + await done.future; + print('saw one $sawOne'); + + bool offline = false; + final sub = Connectivity().onConnectivityChanged.listen((results) { + offline = results.contains(ConnectivityResult.none); + }); + await sub.cancel(); + print('offline $offline'); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/stub_name_shadowing/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/stub_name_shadowing/expect.txt new file mode 100644 index 00000000000..677195013e0 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/stub_name_shadowing/expect.txt @@ -0,0 +1,3 @@ +hi ann +3 +urgent diff --git a/maven/dart-transpiler/src/test/resources/behavior/stub_name_shadowing/main.dart b/maven/dart-transpiler/src/test/resources/behavior/stub_name_shadowing/main.dart new file mode 100644 index 00000000000..6aa9de0e02f --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/stub_name_shadowing/main.dart @@ -0,0 +1,20 @@ +// An app's own User, Position and Priority win over the plugin stubs of the same +// names, which are global: a user enum used to be replaced by the stub's. +class User { + final String name; + User(this.name); + String greet() => 'hi $name'; +} + +class Position { + final int x; + const Position(this.x); +} + +enum Priority { low, urgent } + +void main() { + print(User('ann').greet()); + print(const Position(3).x); + print(Priority.urgent.name); +} diff --git a/maven/dart-transpiler/src/test/resources/behavior/widgets_pop_dismiss/expect.txt b/maven/dart-transpiler/src/test/resources/behavior/widgets_pop_dismiss/expect.txt new file mode 100644 index 00000000000..033cb67e2cf --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/widgets_pop_dismiss/expect.txt @@ -0,0 +1,2 @@ +true +0 diff --git a/maven/dart-transpiler/src/test/resources/behavior/widgets_pop_dismiss/main.dart b/maven/dart-transpiler/src/test/resources/behavior/widgets_pop_dismiss/main.dart new file mode 100644 index 00000000000..db0d52f63e6 --- /dev/null +++ b/maven/dart-transpiler/src/test/resources/behavior/widgets_pop_dismiss/main.dart @@ -0,0 +1,22 @@ +// PopScope, WillPopScope and Dismissible compile with typed callbacks: a callback +// parameter typed dynamic could not reach direction.name at run time. +import 'package:flutter/material.dart'; + +void main() { + final log = []; + final page = PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) => log.add('pop $didPop $result'), + child: WillPopScope( + onWillPop: () async => log.isEmpty, + child: Dismissible( + key: const ValueKey('row'), + onDismissed: (direction) => log.add('gone ${direction.name}'), + confirmDismiss: (direction) async => direction == DismissDirection.endToStart, + child: const Text('row'), + ), + ), + ); + print(page is PopScope); + print(log.length); +} diff --git a/maven/dart-transpiler/src/test/resources/golden/counter/expected/_MyHomePageState.java b/maven/dart-transpiler/src/test/resources/golden/counter/expected/_MyHomePageState.java index 354fdf50aea..afa7f8e1c1d 100644 --- a/maven/dart-transpiler/src/test/resources/golden/counter/expected/_MyHomePageState.java +++ b/maven/dart-transpiler/src/test/resources/golden/counter/expected/_MyHomePageState.java @@ -51,7 +51,7 @@ public Widget build(BuildContext context) { var $t5 = new FloatingActionButton(); $t5.onPressed(this::_incrementCounter); $t5.tooltip("Increment"); - $t5.child(new Icon(Icons.add)); + $t5.child(new Icon(Icons.add())); $t0.floatingActionButton($t5); return $t0; } diff --git a/maven/dart-transpiler/src/test/resources/golden/m2demo/expected/_DemoPageState.java b/maven/dart-transpiler/src/test/resources/golden/m2demo/expected/_DemoPageState.java index 1f2d34917c7..e191fc8b8f5 100644 --- a/maven/dart-transpiler/src/test/resources/golden/m2demo/expected/_DemoPageState.java +++ b/maven/dart-transpiler/src/test/resources/golden/m2demo/expected/_DemoPageState.java @@ -76,7 +76,7 @@ public Widget build(BuildContext context) { $t12.width(8.0); var $t13 = new Expanded(); $t13.child(new Text(this._items.idx(index))); - $t11.children(DartList.of(new Icon(Icons.favorite), $t12, $t13, new Text("#" + DartRuntime.str(index)))); + $t11.children(DartList.of(new Icon(Icons.favorite()), $t12, $t13, new Text("#" + DartRuntime.str(index)))); $t10.child($t11); $t9.child($t10); return $t9; diff --git a/maven/flutter-runtime/pom.xml b/maven/flutter-runtime/pom.xml index 7b0583f3dfb..9629baa2edb 100644 --- a/maven/flutter-runtime/pom.xml +++ b/maven/flutter-runtime/pom.xml @@ -31,7 +31,41 @@ + + + src/main/resources + + + + src/main/plugins + META-INF/dart/java + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + plugin-pack-sources + generate-test-sources + + add-test-source + + + + src/main/plugins + + + + + maven-compiler-plugin diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/BuildContext.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/BuildContext.java index 15e977be427..a8f578f063b 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/BuildContext.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/BuildContext.java @@ -84,6 +84,16 @@ default T findAncestorStateOfType(Class type) { */ T read(Class type); + /** + * provider's {@code context.select((T value) => ...)}: the selector applied to + * the value {@link #watch} answers. The dependency is on the whole value, so this + * context rebuilds on every change to it, not only when the selected part changes. + */ + default R select(dart.runtime.Funcs.Func1 selector, Class type) { + T value = watch(type); + return selector.call(value); + } + /** * The nearest value published by an ancestor {@link InheritedValueProvider} * (Provider / ScopedModel) that is assignable to {@code type}, or null. diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/Icons.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/Icons.java index 7dcac18c683..e65e23ee862 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/Icons.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/Icons.java @@ -27,101 +27,6548 @@ /** * Material icons, named as in Flutter's {@code Icons} class and backed by - * the CN1 material icon font codepoints. The set covers every {@code Icons.*} - * constant referenced by the Flutter new_gallery integration app. + * the CN1 material icon font codepoints. Every + * {@code FontImage.MATERIAL_*} glyph is here under Flutter's lower-case name. */ +/// +/// Each icon is a method, not a static final field: one class initializer that built +/// all 2,176 IconData objects was kept whole by ParparVM's dead-code removal and grew +/// every native executable ~20%. A method the app never calls is dropped instead. +/// IconData compares by code point, so a fresh instance per read is still == in Dart. public final class Icons { private Icons() { } - public static final IconData access_alarm = new IconData(FontImage.MATERIAL_ACCESS_ALARM); - public static final IconData access_time = new IconData(FontImage.MATERIAL_ACCESS_TIME); - public static final IconData account_circle = new IconData(FontImage.MATERIAL_ACCOUNT_CIRCLE); - public static final IconData add = new IconData(FontImage.MATERIAL_ADD); - public static final IconData add_circle = new IconData(FontImage.MATERIAL_ADD_CIRCLE); - public static final IconData add_circle_outline = new IconData(FontImage.MATERIAL_ADD_CIRCLE_OUTLINE); - public static final IconData add_comment = new IconData(FontImage.MATERIAL_ADD_COMMENT); - public static final IconData add_shopping_cart = new IconData(FontImage.MATERIAL_ADD_SHOPPING_CART); - public static final IconData airplanemode_active = new IconData(FontImage.MATERIAL_AIRPLANEMODE_ACTIVE); - public static final IconData alarm_on = new IconData(FontImage.MATERIAL_ALARM_ON); - public static final IconData arrow_back = new IconData(FontImage.MATERIAL_ARROW_BACK); - public static final IconData arrow_back_ios = new IconData(FontImage.MATERIAL_ARROW_BACK_IOS); - public static final IconData arrow_drop_down = new IconData(FontImage.MATERIAL_ARROW_DROP_DOWN); - public static final IconData arrow_drop_up = new IconData(FontImage.MATERIAL_ARROW_DROP_UP); - public static final IconData arrow_forward = new IconData(FontImage.MATERIAL_ARROW_FORWARD); - public static final IconData arrow_forward_ios = new IconData(FontImage.MATERIAL_ARROW_FORWARD_IOS); - public static final IconData arrow_left = new IconData(FontImage.MATERIAL_ARROW_LEFT); - public static final IconData attach_money = new IconData(FontImage.MATERIAL_ATTACH_MONEY); - public static final IconData book = new IconData(FontImage.MATERIAL_BOOK); - public static final IconData bookmark_border = new IconData(FontImage.MATERIAL_BOOKMARK_BORDER); - public static final IconData brightness_5 = new IconData(FontImage.MATERIAL_BRIGHTNESS_5); - public static final IconData calendar_today = new IconData(FontImage.MATERIAL_CALENDAR_TODAY); - public static final IconData camera_enhance = new IconData(FontImage.MATERIAL_CAMERA_ENHANCE); - public static final IconData check = new IconData(FontImage.MATERIAL_CHECK); - public static final IconData check_circle = new IconData(FontImage.MATERIAL_CHECK_CIRCLE); - public static final IconData check_circle_outline = new IconData(FontImage.MATERIAL_CHECK_CIRCLE_OUTLINE); - public static final IconData chevron_left = new IconData(FontImage.MATERIAL_CHEVRON_LEFT); - public static final IconData chevron_right = new IconData(FontImage.MATERIAL_CHEVRON_RIGHT); - public static final IconData close = new IconData(FontImage.MATERIAL_CLOSE); - public static final IconData code = new IconData(FontImage.MATERIAL_CODE); - public static final IconData comment = new IconData(FontImage.MATERIAL_COMMENT); - public static final IconData create = new IconData(FontImage.MATERIAL_CREATE); - public static final IconData credit_card = new IconData(FontImage.MATERIAL_CREDIT_CARD); - public static final IconData date_range = new IconData(FontImage.MATERIAL_DATE_RANGE); - public static final IconData delete = new IconData(FontImage.MATERIAL_DELETE); - public static final IconData directions_bike = new IconData(FontImage.MATERIAL_DIRECTIONS_BIKE); - public static final IconData edit = new IconData(FontImage.MATERIAL_EDIT); - public static final IconData email = new IconData(FontImage.MATERIAL_EMAIL); - public static final IconData favorite = new IconData(FontImage.MATERIAL_FAVORITE); - public static final IconData favorite_border = new IconData(FontImage.MATERIAL_FAVORITE_BORDER); - public static final IconData feedback = new IconData(FontImage.MATERIAL_FEEDBACK); - public static final IconData format_bold = new IconData(FontImage.MATERIAL_FORMAT_BOLD); - public static final IconData format_italic = new IconData(FontImage.MATERIAL_FORMAT_ITALIC); - public static final IconData format_underline = new IconData(FontImage.MATERIAL_FORMAT_UNDERLINE); - public static final IconData fullscreen = new IconData(FontImage.MATERIAL_FULLSCREEN); - public static final IconData help = new IconData(FontImage.MATERIAL_HELP); - public static final IconData home = new IconData(FontImage.MATERIAL_HOME); - public static final IconData hotel = new IconData(FontImage.MATERIAL_HOTEL); - public static final IconData info = new IconData(FontImage.MATERIAL_INFO); - public static final IconData info_outline = new IconData(FontImage.MATERIAL_INFO_OUTLINE); - public static final IconData keyboard_arrow_down = new IconData(FontImage.MATERIAL_KEYBOARD_ARROW_DOWN); - public static final IconData keyboard_arrow_up = new IconData(FontImage.MATERIAL_KEYBOARD_ARROW_UP); - public static final IconData library_books = new IconData(FontImage.MATERIAL_LIBRARY_BOOKS); - public static final IconData link = new IconData(FontImage.MATERIAL_LINK); - public static final IconData lock = new IconData(FontImage.MATERIAL_LOCK); - public static final IconData menu = new IconData(FontImage.MATERIAL_MENU); - public static final IconData mic = new IconData(FontImage.MATERIAL_MIC); - public static final IconData money_off = new IconData(FontImage.MATERIAL_MONEY_OFF); - public static final IconData more_vert = new IconData(FontImage.MATERIAL_MORE_VERT); - public static final IconData more_horiz = new IconData(FontImage.MATERIAL_MORE_HORIZ); - public static final IconData not_interested = new IconData(FontImage.MATERIAL_NOT_INTERESTED); - public static final IconData notifications = new IconData(FontImage.MATERIAL_NOTIFICATIONS); - public static final IconData person = new IconData(FontImage.MATERIAL_PERSON); - public static final IconData person_add = new IconData(FontImage.MATERIAL_PERSON_ADD); - public static final IconData phone = new IconData(FontImage.MATERIAL_PHONE); - public static final IconData photo = new IconData(FontImage.MATERIAL_PHOTO); - public static final IconData photo_library = new IconData(FontImage.MATERIAL_PHOTO_LIBRARY); - public static final IconData pie_chart = new IconData(FontImage.MATERIAL_PIE_CHART); - public static final IconData place = new IconData(FontImage.MATERIAL_PLACE); - public static final IconData remove = new IconData(FontImage.MATERIAL_REMOVE); - public static final IconData remove_circle_outline = new IconData(FontImage.MATERIAL_REMOVE_CIRCLE_OUTLINE); - public static final IconData refresh = new IconData(FontImage.MATERIAL_REFRESH); - public static final IconData replay = new IconData(FontImage.MATERIAL_REPLAY); - public static final IconData reply_all = new IconData(FontImage.MATERIAL_REPLY_ALL); - public static final IconData restaurant_menu = new IconData(FontImage.MATERIAL_RESTAURANT_MENU); - public static final IconData search = new IconData(FontImage.MATERIAL_SEARCH); - public static final IconData security = new IconData(FontImage.MATERIAL_SECURITY); - public static final IconData settings = new IconData(FontImage.MATERIAL_SETTINGS); - public static final IconData share = new IconData(FontImage.MATERIAL_SHARE); - public static final IconData shopping_cart = new IconData(FontImage.MATERIAL_SHOPPING_CART); - public static final IconData sort = new IconData(FontImage.MATERIAL_SORT); - public static final IconData star = new IconData(FontImage.MATERIAL_STAR); - public static final IconData star_border = new IconData(FontImage.MATERIAL_STAR_BORDER); - public static final IconData table_chart = new IconData(FontImage.MATERIAL_TABLE_CHART); - public static final IconData tune = new IconData(FontImage.MATERIAL_TUNE); - public static final IconData vertical_split = new IconData(FontImage.MATERIAL_VERTICAL_SPLIT); - public static final IconData visibility = new IconData(FontImage.MATERIAL_VISIBILITY); - public static final IconData visibility_off = new IconData(FontImage.MATERIAL_VISIBILITY_OFF); - public static final IconData web_asset = new IconData(FontImage.MATERIAL_WEB_ASSET); + public static IconData access_alarm() { + return new IconData(FontImage.MATERIAL_ACCESS_ALARM); + } + public static IconData access_time() { + return new IconData(FontImage.MATERIAL_ACCESS_TIME); + } + public static IconData account_circle() { + return new IconData(FontImage.MATERIAL_ACCOUNT_CIRCLE); + } + public static IconData add() { + return new IconData(FontImage.MATERIAL_ADD); + } + public static IconData add_circle() { + return new IconData(FontImage.MATERIAL_ADD_CIRCLE); + } + public static IconData add_circle_outline() { + return new IconData(FontImage.MATERIAL_ADD_CIRCLE_OUTLINE); + } + public static IconData add_comment() { + return new IconData(FontImage.MATERIAL_ADD_COMMENT); + } + public static IconData add_shopping_cart() { + return new IconData(FontImage.MATERIAL_ADD_SHOPPING_CART); + } + public static IconData airplanemode_active() { + return new IconData(FontImage.MATERIAL_AIRPLANEMODE_ACTIVE); + } + public static IconData alarm_on() { + return new IconData(FontImage.MATERIAL_ALARM_ON); + } + public static IconData arrow_back() { + return new IconData(FontImage.MATERIAL_ARROW_BACK); + } + public static IconData arrow_back_ios() { + return new IconData(FontImage.MATERIAL_ARROW_BACK_IOS); + } + public static IconData arrow_drop_down() { + return new IconData(FontImage.MATERIAL_ARROW_DROP_DOWN); + } + public static IconData arrow_drop_up() { + return new IconData(FontImage.MATERIAL_ARROW_DROP_UP); + } + public static IconData arrow_forward() { + return new IconData(FontImage.MATERIAL_ARROW_FORWARD); + } + public static IconData arrow_forward_ios() { + return new IconData(FontImage.MATERIAL_ARROW_FORWARD_IOS); + } + public static IconData arrow_left() { + return new IconData(FontImage.MATERIAL_ARROW_LEFT); + } + public static IconData attach_money() { + return new IconData(FontImage.MATERIAL_ATTACH_MONEY); + } + public static IconData book() { + return new IconData(FontImage.MATERIAL_BOOK); + } + public static IconData bookmark_border() { + return new IconData(FontImage.MATERIAL_BOOKMARK_BORDER); + } + public static IconData brightness_5() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_5); + } + public static IconData calendar_today() { + return new IconData(FontImage.MATERIAL_CALENDAR_TODAY); + } + public static IconData camera_enhance() { + return new IconData(FontImage.MATERIAL_CAMERA_ENHANCE); + } + public static IconData check() { + return new IconData(FontImage.MATERIAL_CHECK); + } + public static IconData check_circle() { + return new IconData(FontImage.MATERIAL_CHECK_CIRCLE); + } + public static IconData check_circle_outline() { + return new IconData(FontImage.MATERIAL_CHECK_CIRCLE_OUTLINE); + } + public static IconData chevron_left() { + return new IconData(FontImage.MATERIAL_CHEVRON_LEFT); + } + public static IconData chevron_right() { + return new IconData(FontImage.MATERIAL_CHEVRON_RIGHT); + } + public static IconData close() { + return new IconData(FontImage.MATERIAL_CLOSE); + } + public static IconData code() { + return new IconData(FontImage.MATERIAL_CODE); + } + public static IconData comment() { + return new IconData(FontImage.MATERIAL_COMMENT); + } + public static IconData create() { + return new IconData(FontImage.MATERIAL_CREATE); + } + public static IconData credit_card() { + return new IconData(FontImage.MATERIAL_CREDIT_CARD); + } + public static IconData date_range() { + return new IconData(FontImage.MATERIAL_DATE_RANGE); + } + public static IconData delete() { + return new IconData(FontImage.MATERIAL_DELETE); + } + public static IconData directions_bike() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_BIKE); + } + public static IconData edit() { + return new IconData(FontImage.MATERIAL_EDIT); + } + public static IconData email() { + return new IconData(FontImage.MATERIAL_EMAIL); + } + public static IconData favorite() { + return new IconData(FontImage.MATERIAL_FAVORITE); + } + public static IconData favorite_border() { + return new IconData(FontImage.MATERIAL_FAVORITE_BORDER); + } + public static IconData feedback() { + return new IconData(FontImage.MATERIAL_FEEDBACK); + } + public static IconData format_bold() { + return new IconData(FontImage.MATERIAL_FORMAT_BOLD); + } + public static IconData format_italic() { + return new IconData(FontImage.MATERIAL_FORMAT_ITALIC); + } + public static IconData format_underline() { + return new IconData(FontImage.MATERIAL_FORMAT_UNDERLINE); + } + public static IconData fullscreen() { + return new IconData(FontImage.MATERIAL_FULLSCREEN); + } + public static IconData help() { + return new IconData(FontImage.MATERIAL_HELP); + } + public static IconData home() { + return new IconData(FontImage.MATERIAL_HOME); + } + public static IconData hotel() { + return new IconData(FontImage.MATERIAL_HOTEL); + } + public static IconData info() { + return new IconData(FontImage.MATERIAL_INFO); + } + public static IconData info_outline() { + return new IconData(FontImage.MATERIAL_INFO_OUTLINE); + } + public static IconData keyboard_arrow_down() { + return new IconData(FontImage.MATERIAL_KEYBOARD_ARROW_DOWN); + } + public static IconData keyboard_arrow_up() { + return new IconData(FontImage.MATERIAL_KEYBOARD_ARROW_UP); + } + public static IconData library_books() { + return new IconData(FontImage.MATERIAL_LIBRARY_BOOKS); + } + public static IconData link() { + return new IconData(FontImage.MATERIAL_LINK); + } + public static IconData lock() { + return new IconData(FontImage.MATERIAL_LOCK); + } + public static IconData menu() { + return new IconData(FontImage.MATERIAL_MENU); + } + public static IconData mic() { + return new IconData(FontImage.MATERIAL_MIC); + } + public static IconData money_off() { + return new IconData(FontImage.MATERIAL_MONEY_OFF); + } + public static IconData more_vert() { + return new IconData(FontImage.MATERIAL_MORE_VERT); + } + public static IconData more_horiz() { + return new IconData(FontImage.MATERIAL_MORE_HORIZ); + } + public static IconData not_interested() { + return new IconData(FontImage.MATERIAL_NOT_INTERESTED); + } + public static IconData notifications() { + return new IconData(FontImage.MATERIAL_NOTIFICATIONS); + } + public static IconData person() { + return new IconData(FontImage.MATERIAL_PERSON); + } + public static IconData person_add() { + return new IconData(FontImage.MATERIAL_PERSON_ADD); + } + public static IconData phone() { + return new IconData(FontImage.MATERIAL_PHONE); + } + public static IconData photo() { + return new IconData(FontImage.MATERIAL_PHOTO); + } + public static IconData photo_library() { + return new IconData(FontImage.MATERIAL_PHOTO_LIBRARY); + } + public static IconData pie_chart() { + return new IconData(FontImage.MATERIAL_PIE_CHART); + } + public static IconData place() { + return new IconData(FontImage.MATERIAL_PLACE); + } + public static IconData remove() { + return new IconData(FontImage.MATERIAL_REMOVE); + } + public static IconData remove_circle_outline() { + return new IconData(FontImage.MATERIAL_REMOVE_CIRCLE_OUTLINE); + } + public static IconData refresh() { + return new IconData(FontImage.MATERIAL_REFRESH); + } + public static IconData replay() { + return new IconData(FontImage.MATERIAL_REPLAY); + } + public static IconData reply_all() { + return new IconData(FontImage.MATERIAL_REPLY_ALL); + } + public static IconData restaurant_menu() { + return new IconData(FontImage.MATERIAL_RESTAURANT_MENU); + } + public static IconData search() { + return new IconData(FontImage.MATERIAL_SEARCH); + } + public static IconData security() { + return new IconData(FontImage.MATERIAL_SECURITY); + } + public static IconData settings() { + return new IconData(FontImage.MATERIAL_SETTINGS); + } + public static IconData share() { + return new IconData(FontImage.MATERIAL_SHARE); + } + public static IconData shopping_cart() { + return new IconData(FontImage.MATERIAL_SHOPPING_CART); + } + public static IconData sort() { + return new IconData(FontImage.MATERIAL_SORT); + } + public static IconData star() { + return new IconData(FontImage.MATERIAL_STAR); + } + public static IconData star_border() { + return new IconData(FontImage.MATERIAL_STAR_BORDER); + } + public static IconData table_chart() { + return new IconData(FontImage.MATERIAL_TABLE_CHART); + } + public static IconData tune() { + return new IconData(FontImage.MATERIAL_TUNE); + } + public static IconData vertical_split() { + return new IconData(FontImage.MATERIAL_VERTICAL_SPLIT); + } + public static IconData visibility() { + return new IconData(FontImage.MATERIAL_VISIBILITY); + } + public static IconData visibility_off() { + return new IconData(FontImage.MATERIAL_VISIBILITY_OFF); + } + public static IconData web_asset() { + return new IconData(FontImage.MATERIAL_WEB_ASSET); + } + + // The rest of the Material set, one constant per FontImage.MATERIAL_* glyph under + // Flutter's lower-case name. + public static IconData abc() { + return new IconData(FontImage.MATERIAL_ABC); + } + public static IconData ac_unit() { + return new IconData(FontImage.MATERIAL_AC_UNIT); + } + public static IconData access_alarms() { + return new IconData(FontImage.MATERIAL_ACCESS_ALARMS); + } + public static IconData access_time_filled() { + return new IconData(FontImage.MATERIAL_ACCESS_TIME_FILLED); + } + public static IconData accessibility() { + return new IconData(FontImage.MATERIAL_ACCESSIBILITY); + } + public static IconData accessibility_new() { + return new IconData(FontImage.MATERIAL_ACCESSIBILITY_NEW); + } + public static IconData accessible() { + return new IconData(FontImage.MATERIAL_ACCESSIBLE); + } + public static IconData accessible_forward() { + return new IconData(FontImage.MATERIAL_ACCESSIBLE_FORWARD); + } + public static IconData account_balance() { + return new IconData(FontImage.MATERIAL_ACCOUNT_BALANCE); + } + public static IconData account_balance_wallet() { + return new IconData(FontImage.MATERIAL_ACCOUNT_BALANCE_WALLET); + } + public static IconData account_box() { + return new IconData(FontImage.MATERIAL_ACCOUNT_BOX); + } + public static IconData account_tree() { + return new IconData(FontImage.MATERIAL_ACCOUNT_TREE); + } + public static IconData ad_units() { + return new IconData(FontImage.MATERIAL_AD_UNITS); + } + public static IconData adb() { + return new IconData(FontImage.MATERIAL_ADB); + } + public static IconData add_a_photo() { + return new IconData(FontImage.MATERIAL_ADD_A_PHOTO); + } + public static IconData add_alarm() { + return new IconData(FontImage.MATERIAL_ADD_ALARM); + } + public static IconData add_alert() { + return new IconData(FontImage.MATERIAL_ADD_ALERT); + } + public static IconData add_box() { + return new IconData(FontImage.MATERIAL_ADD_BOX); + } + public static IconData add_business() { + return new IconData(FontImage.MATERIAL_ADD_BUSINESS); + } + public static IconData add_call() { + return new IconData(FontImage.MATERIAL_ADD_CALL); + } + public static IconData add_card() { + return new IconData(FontImage.MATERIAL_ADD_CARD); + } + public static IconData add_chart() { + return new IconData(FontImage.MATERIAL_ADD_CHART); + } + public static IconData add_home() { + return new IconData(FontImage.MATERIAL_ADD_HOME); + } + public static IconData add_home_work() { + return new IconData(FontImage.MATERIAL_ADD_HOME_WORK); + } + public static IconData add_ic_call() { + return new IconData(FontImage.MATERIAL_ADD_IC_CALL); + } + public static IconData add_link() { + return new IconData(FontImage.MATERIAL_ADD_LINK); + } + public static IconData add_location() { + return new IconData(FontImage.MATERIAL_ADD_LOCATION); + } + public static IconData add_location_alt() { + return new IconData(FontImage.MATERIAL_ADD_LOCATION_ALT); + } + public static IconData add_moderator() { + return new IconData(FontImage.MATERIAL_ADD_MODERATOR); + } + public static IconData add_photo_alternate() { + return new IconData(FontImage.MATERIAL_ADD_PHOTO_ALTERNATE); + } + public static IconData add_reaction() { + return new IconData(FontImage.MATERIAL_ADD_REACTION); + } + public static IconData add_road() { + return new IconData(FontImage.MATERIAL_ADD_ROAD); + } + public static IconData add_task() { + return new IconData(FontImage.MATERIAL_ADD_TASK); + } + public static IconData add_to_drive() { + return new IconData(FontImage.MATERIAL_ADD_TO_DRIVE); + } + public static IconData add_to_home_screen() { + return new IconData(FontImage.MATERIAL_ADD_TO_HOME_SCREEN); + } + public static IconData add_to_photos() { + return new IconData(FontImage.MATERIAL_ADD_TO_PHOTOS); + } + public static IconData add_to_queue() { + return new IconData(FontImage.MATERIAL_ADD_TO_QUEUE); + } + public static IconData addchart() { + return new IconData(FontImage.MATERIAL_ADDCHART); + } + public static IconData adf_scanner() { + return new IconData(FontImage.MATERIAL_ADF_SCANNER); + } + public static IconData adjust() { + return new IconData(FontImage.MATERIAL_ADJUST); + } + public static IconData admin_panel_settings() { + return new IconData(FontImage.MATERIAL_ADMIN_PANEL_SETTINGS); + } + public static IconData adobe() { + return new IconData(FontImage.MATERIAL_ADOBE); + } + public static IconData ads_click() { + return new IconData(FontImage.MATERIAL_ADS_CLICK); + } + public static IconData agriculture() { + return new IconData(FontImage.MATERIAL_AGRICULTURE); + } + public static IconData air() { + return new IconData(FontImage.MATERIAL_AIR); + } + public static IconData airline_seat_flat() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_FLAT); + } + public static IconData airline_seat_flat_angled() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_FLAT_ANGLED); + } + public static IconData airline_seat_individual_suite() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_INDIVIDUAL_SUITE); + } + public static IconData airline_seat_legroom_extra() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_LEGROOM_EXTRA); + } + public static IconData airline_seat_legroom_normal() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_LEGROOM_NORMAL); + } + public static IconData airline_seat_legroom_reduced() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_LEGROOM_REDUCED); + } + public static IconData airline_seat_recline_extra() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_RECLINE_EXTRA); + } + public static IconData airline_seat_recline_normal() { + return new IconData(FontImage.MATERIAL_AIRLINE_SEAT_RECLINE_NORMAL); + } + public static IconData airline_stops() { + return new IconData(FontImage.MATERIAL_AIRLINE_STOPS); + } + public static IconData airlines() { + return new IconData(FontImage.MATERIAL_AIRLINES); + } + public static IconData airplane_ticket() { + return new IconData(FontImage.MATERIAL_AIRPLANE_TICKET); + } + public static IconData airplanemode_inactive() { + return new IconData(FontImage.MATERIAL_AIRPLANEMODE_INACTIVE); + } + public static IconData airplanemode_off() { + return new IconData(FontImage.MATERIAL_AIRPLANEMODE_OFF); + } + public static IconData airplanemode_on() { + return new IconData(FontImage.MATERIAL_AIRPLANEMODE_ON); + } + public static IconData airplay() { + return new IconData(FontImage.MATERIAL_AIRPLAY); + } + public static IconData airport_shuttle() { + return new IconData(FontImage.MATERIAL_AIRPORT_SHUTTLE); + } + public static IconData alarm() { + return new IconData(FontImage.MATERIAL_ALARM); + } + public static IconData alarm_add() { + return new IconData(FontImage.MATERIAL_ALARM_ADD); + } + public static IconData alarm_off() { + return new IconData(FontImage.MATERIAL_ALARM_OFF); + } + public static IconData album() { + return new IconData(FontImage.MATERIAL_ALBUM); + } + public static IconData align_horizontal_center() { + return new IconData(FontImage.MATERIAL_ALIGN_HORIZONTAL_CENTER); + } + public static IconData align_horizontal_left() { + return new IconData(FontImage.MATERIAL_ALIGN_HORIZONTAL_LEFT); + } + public static IconData align_horizontal_right() { + return new IconData(FontImage.MATERIAL_ALIGN_HORIZONTAL_RIGHT); + } + public static IconData align_vertical_bottom() { + return new IconData(FontImage.MATERIAL_ALIGN_VERTICAL_BOTTOM); + } + public static IconData align_vertical_center() { + return new IconData(FontImage.MATERIAL_ALIGN_VERTICAL_CENTER); + } + public static IconData align_vertical_top() { + return new IconData(FontImage.MATERIAL_ALIGN_VERTICAL_TOP); + } + public static IconData all_inbox() { + return new IconData(FontImage.MATERIAL_ALL_INBOX); + } + public static IconData all_inclusive() { + return new IconData(FontImage.MATERIAL_ALL_INCLUSIVE); + } + public static IconData all_out() { + return new IconData(FontImage.MATERIAL_ALL_OUT); + } + public static IconData alt_route() { + return new IconData(FontImage.MATERIAL_ALT_ROUTE); + } + public static IconData alternate_email() { + return new IconData(FontImage.MATERIAL_ALTERNATE_EMAIL); + } + public static IconData amp_stories() { + return new IconData(FontImage.MATERIAL_AMP_STORIES); + } + public static IconData analytics() { + return new IconData(FontImage.MATERIAL_ANALYTICS); + } + public static IconData anchor() { + return new IconData(FontImage.MATERIAL_ANCHOR); + } + public static IconData android() { + return new IconData(FontImage.MATERIAL_ANDROID); + } + public static IconData animation() { + return new IconData(FontImage.MATERIAL_ANIMATION); + } + public static IconData announcement() { + return new IconData(FontImage.MATERIAL_ANNOUNCEMENT); + } + public static IconData aod() { + return new IconData(FontImage.MATERIAL_AOD); + } + public static IconData apartment() { + return new IconData(FontImage.MATERIAL_APARTMENT); + } + public static IconData api() { + return new IconData(FontImage.MATERIAL_API); + } + public static IconData app_blocking() { + return new IconData(FontImage.MATERIAL_APP_BLOCKING); + } + public static IconData app_registration() { + return new IconData(FontImage.MATERIAL_APP_REGISTRATION); + } + public static IconData app_settings_alt() { + return new IconData(FontImage.MATERIAL_APP_SETTINGS_ALT); + } + public static IconData app_shortcut() { + return new IconData(FontImage.MATERIAL_APP_SHORTCUT); + } + public static IconData apple() { + return new IconData(FontImage.MATERIAL_APPLE); + } + public static IconData approval() { + return new IconData(FontImage.MATERIAL_APPROVAL); + } + public static IconData apps() { + return new IconData(FontImage.MATERIAL_APPS); + } + public static IconData apps_outage() { + return new IconData(FontImage.MATERIAL_APPS_OUTAGE); + } + public static IconData architecture() { + return new IconData(FontImage.MATERIAL_ARCHITECTURE); + } + public static IconData archive() { + return new IconData(FontImage.MATERIAL_ARCHIVE); + } + public static IconData area_chart() { + return new IconData(FontImage.MATERIAL_AREA_CHART); + } + public static IconData arrow_back_ios_new() { + return new IconData(FontImage.MATERIAL_ARROW_BACK_IOS_NEW); + } + public static IconData arrow_circle_down() { + return new IconData(FontImage.MATERIAL_ARROW_CIRCLE_DOWN); + } + public static IconData arrow_circle_left() { + return new IconData(FontImage.MATERIAL_ARROW_CIRCLE_LEFT); + } + public static IconData arrow_circle_right() { + return new IconData(FontImage.MATERIAL_ARROW_CIRCLE_RIGHT); + } + public static IconData arrow_circle_up() { + return new IconData(FontImage.MATERIAL_ARROW_CIRCLE_UP); + } + public static IconData arrow_downward() { + return new IconData(FontImage.MATERIAL_ARROW_DOWNWARD); + } + public static IconData arrow_drop_down_circle() { + return new IconData(FontImage.MATERIAL_ARROW_DROP_DOWN_CIRCLE); + } + public static IconData arrow_outward() { + return new IconData(FontImage.MATERIAL_ARROW_OUTWARD); + } + public static IconData arrow_right() { + return new IconData(FontImage.MATERIAL_ARROW_RIGHT); + } + public static IconData arrow_right_alt() { + return new IconData(FontImage.MATERIAL_ARROW_RIGHT_ALT); + } + public static IconData arrow_upward() { + return new IconData(FontImage.MATERIAL_ARROW_UPWARD); + } + public static IconData art_track() { + return new IconData(FontImage.MATERIAL_ART_TRACK); + } + public static IconData article() { + return new IconData(FontImage.MATERIAL_ARTICLE); + } + public static IconData aspect_ratio() { + return new IconData(FontImage.MATERIAL_ASPECT_RATIO); + } + public static IconData assessment() { + return new IconData(FontImage.MATERIAL_ASSESSMENT); + } + public static IconData assignment() { + return new IconData(FontImage.MATERIAL_ASSIGNMENT); + } + public static IconData assignment_add() { + return new IconData(FontImage.MATERIAL_ASSIGNMENT_ADD); + } + public static IconData assignment_ind() { + return new IconData(FontImage.MATERIAL_ASSIGNMENT_IND); + } + public static IconData assignment_late() { + return new IconData(FontImage.MATERIAL_ASSIGNMENT_LATE); + } + public static IconData assignment_return() { + return new IconData(FontImage.MATERIAL_ASSIGNMENT_RETURN); + } + public static IconData assignment_returned() { + return new IconData(FontImage.MATERIAL_ASSIGNMENT_RETURNED); + } + public static IconData assignment_turned_in() { + return new IconData(FontImage.MATERIAL_ASSIGNMENT_TURNED_IN); + } + public static IconData assist_walker() { + return new IconData(FontImage.MATERIAL_ASSIST_WALKER); + } + public static IconData assistant() { + return new IconData(FontImage.MATERIAL_ASSISTANT); + } + public static IconData assistant_direction() { + return new IconData(FontImage.MATERIAL_ASSISTANT_DIRECTION); + } + public static IconData assistant_navigation() { + return new IconData(FontImage.MATERIAL_ASSISTANT_NAVIGATION); + } + public static IconData assistant_photo() { + return new IconData(FontImage.MATERIAL_ASSISTANT_PHOTO); + } + public static IconData assured_workload() { + return new IconData(FontImage.MATERIAL_ASSURED_WORKLOAD); + } + public static IconData atm() { + return new IconData(FontImage.MATERIAL_ATM); + } + public static IconData attach_email() { + return new IconData(FontImage.MATERIAL_ATTACH_EMAIL); + } + public static IconData attach_file() { + return new IconData(FontImage.MATERIAL_ATTACH_FILE); + } + public static IconData attachment() { + return new IconData(FontImage.MATERIAL_ATTACHMENT); + } + public static IconData attractions() { + return new IconData(FontImage.MATERIAL_ATTRACTIONS); + } + public static IconData attribution() { + return new IconData(FontImage.MATERIAL_ATTRIBUTION); + } + public static IconData audio_file() { + return new IconData(FontImage.MATERIAL_AUDIO_FILE); + } + public static IconData audiotrack() { + return new IconData(FontImage.MATERIAL_AUDIOTRACK); + } + public static IconData auto_awesome() { + return new IconData(FontImage.MATERIAL_AUTO_AWESOME); + } + public static IconData auto_awesome_mosaic() { + return new IconData(FontImage.MATERIAL_AUTO_AWESOME_MOSAIC); + } + public static IconData auto_awesome_motion() { + return new IconData(FontImage.MATERIAL_AUTO_AWESOME_MOTION); + } + public static IconData auto_delete() { + return new IconData(FontImage.MATERIAL_AUTO_DELETE); + } + public static IconData auto_fix_high() { + return new IconData(FontImage.MATERIAL_AUTO_FIX_HIGH); + } + public static IconData auto_fix_normal() { + return new IconData(FontImage.MATERIAL_AUTO_FIX_NORMAL); + } + public static IconData auto_fix_off() { + return new IconData(FontImage.MATERIAL_AUTO_FIX_OFF); + } + public static IconData auto_graph() { + return new IconData(FontImage.MATERIAL_AUTO_GRAPH); + } + public static IconData auto_mode() { + return new IconData(FontImage.MATERIAL_AUTO_MODE); + } + public static IconData auto_stories() { + return new IconData(FontImage.MATERIAL_AUTO_STORIES); + } + public static IconData autofps_select() { + return new IconData(FontImage.MATERIAL_AUTOFPS_SELECT); + } + public static IconData autorenew() { + return new IconData(FontImage.MATERIAL_AUTORENEW); + } + public static IconData av_timer() { + return new IconData(FontImage.MATERIAL_AV_TIMER); + } + public static IconData baby_changing_station() { + return new IconData(FontImage.MATERIAL_BABY_CHANGING_STATION); + } + public static IconData back_hand() { + return new IconData(FontImage.MATERIAL_BACK_HAND); + } + public static IconData backpack() { + return new IconData(FontImage.MATERIAL_BACKPACK); + } + public static IconData backspace() { + return new IconData(FontImage.MATERIAL_BACKSPACE); + } + public static IconData backup() { + return new IconData(FontImage.MATERIAL_BACKUP); + } + public static IconData backup_table() { + return new IconData(FontImage.MATERIAL_BACKUP_TABLE); + } + public static IconData badge() { + return new IconData(FontImage.MATERIAL_BADGE); + } + public static IconData bakery_dining() { + return new IconData(FontImage.MATERIAL_BAKERY_DINING); + } + public static IconData balance() { + return new IconData(FontImage.MATERIAL_BALANCE); + } + public static IconData balcony() { + return new IconData(FontImage.MATERIAL_BALCONY); + } + public static IconData ballot() { + return new IconData(FontImage.MATERIAL_BALLOT); + } + public static IconData bar_chart() { + return new IconData(FontImage.MATERIAL_BAR_CHART); + } + public static IconData barcode_reader() { + return new IconData(FontImage.MATERIAL_BARCODE_READER); + } + public static IconData batch_prediction() { + return new IconData(FontImage.MATERIAL_BATCH_PREDICTION); + } + public static IconData bathroom() { + return new IconData(FontImage.MATERIAL_BATHROOM); + } + public static IconData bathtub() { + return new IconData(FontImage.MATERIAL_BATHTUB); + } + public static IconData battery_0_bar() { + return new IconData(FontImage.MATERIAL_BATTERY_0_BAR); + } + public static IconData battery_1_bar() { + return new IconData(FontImage.MATERIAL_BATTERY_1_BAR); + } + public static IconData battery_2_bar() { + return new IconData(FontImage.MATERIAL_BATTERY_2_BAR); + } + public static IconData battery_3_bar() { + return new IconData(FontImage.MATERIAL_BATTERY_3_BAR); + } + public static IconData battery_4_bar() { + return new IconData(FontImage.MATERIAL_BATTERY_4_BAR); + } + public static IconData battery_5_bar() { + return new IconData(FontImage.MATERIAL_BATTERY_5_BAR); + } + public static IconData battery_6_bar() { + return new IconData(FontImage.MATERIAL_BATTERY_6_BAR); + } + public static IconData battery_alert() { + return new IconData(FontImage.MATERIAL_BATTERY_ALERT); + } + public static IconData battery_charging_full() { + return new IconData(FontImage.MATERIAL_BATTERY_CHARGING_FULL); + } + public static IconData battery_full() { + return new IconData(FontImage.MATERIAL_BATTERY_FULL); + } + public static IconData battery_saver() { + return new IconData(FontImage.MATERIAL_BATTERY_SAVER); + } + public static IconData battery_std() { + return new IconData(FontImage.MATERIAL_BATTERY_STD); + } + public static IconData battery_unknown() { + return new IconData(FontImage.MATERIAL_BATTERY_UNKNOWN); + } + public static IconData beach_access() { + return new IconData(FontImage.MATERIAL_BEACH_ACCESS); + } + public static IconData bed() { + return new IconData(FontImage.MATERIAL_BED); + } + public static IconData bedroom_baby() { + return new IconData(FontImage.MATERIAL_BEDROOM_BABY); + } + public static IconData bedroom_child() { + return new IconData(FontImage.MATERIAL_BEDROOM_CHILD); + } + public static IconData bedroom_parent() { + return new IconData(FontImage.MATERIAL_BEDROOM_PARENT); + } + public static IconData bedtime() { + return new IconData(FontImage.MATERIAL_BEDTIME); + } + public static IconData bedtime_off() { + return new IconData(FontImage.MATERIAL_BEDTIME_OFF); + } + public static IconData beenhere() { + return new IconData(FontImage.MATERIAL_BEENHERE); + } + public static IconData bento() { + return new IconData(FontImage.MATERIAL_BENTO); + } + public static IconData bike_scooter() { + return new IconData(FontImage.MATERIAL_BIKE_SCOOTER); + } + public static IconData biotech() { + return new IconData(FontImage.MATERIAL_BIOTECH); + } + public static IconData blender() { + return new IconData(FontImage.MATERIAL_BLENDER); + } + public static IconData blind() { + return new IconData(FontImage.MATERIAL_BLIND); + } + public static IconData blinds() { + return new IconData(FontImage.MATERIAL_BLINDS); + } + public static IconData blinds_closed() { + return new IconData(FontImage.MATERIAL_BLINDS_CLOSED); + } + public static IconData block() { + return new IconData(FontImage.MATERIAL_BLOCK); + } + public static IconData block_flipped() { + return new IconData(FontImage.MATERIAL_BLOCK_FLIPPED); + } + public static IconData bloodtype() { + return new IconData(FontImage.MATERIAL_BLOODTYPE); + } + public static IconData bluetooth() { + return new IconData(FontImage.MATERIAL_BLUETOOTH); + } + public static IconData bluetooth_audio() { + return new IconData(FontImage.MATERIAL_BLUETOOTH_AUDIO); + } + public static IconData bluetooth_connected() { + return new IconData(FontImage.MATERIAL_BLUETOOTH_CONNECTED); + } + public static IconData bluetooth_disabled() { + return new IconData(FontImage.MATERIAL_BLUETOOTH_DISABLED); + } + public static IconData bluetooth_drive() { + return new IconData(FontImage.MATERIAL_BLUETOOTH_DRIVE); + } + public static IconData bluetooth_searching() { + return new IconData(FontImage.MATERIAL_BLUETOOTH_SEARCHING); + } + public static IconData blur_circular() { + return new IconData(FontImage.MATERIAL_BLUR_CIRCULAR); + } + public static IconData blur_linear() { + return new IconData(FontImage.MATERIAL_BLUR_LINEAR); + } + public static IconData blur_off() { + return new IconData(FontImage.MATERIAL_BLUR_OFF); + } + public static IconData blur_on() { + return new IconData(FontImage.MATERIAL_BLUR_ON); + } + public static IconData bolt() { + return new IconData(FontImage.MATERIAL_BOLT); + } + public static IconData book_online() { + return new IconData(FontImage.MATERIAL_BOOK_ONLINE); + } + public static IconData bookmark() { + return new IconData(FontImage.MATERIAL_BOOKMARK); + } + public static IconData bookmark_add() { + return new IconData(FontImage.MATERIAL_BOOKMARK_ADD); + } + public static IconData bookmark_added() { + return new IconData(FontImage.MATERIAL_BOOKMARK_ADDED); + } + public static IconData bookmark_outline() { + return new IconData(FontImage.MATERIAL_BOOKMARK_OUTLINE); + } + public static IconData bookmark_remove() { + return new IconData(FontImage.MATERIAL_BOOKMARK_REMOVE); + } + public static IconData bookmarks() { + return new IconData(FontImage.MATERIAL_BOOKMARKS); + } + public static IconData border_all() { + return new IconData(FontImage.MATERIAL_BORDER_ALL); + } + public static IconData border_bottom() { + return new IconData(FontImage.MATERIAL_BORDER_BOTTOM); + } + public static IconData border_clear() { + return new IconData(FontImage.MATERIAL_BORDER_CLEAR); + } + public static IconData border_color() { + return new IconData(FontImage.MATERIAL_BORDER_COLOR); + } + public static IconData border_horizontal() { + return new IconData(FontImage.MATERIAL_BORDER_HORIZONTAL); + } + public static IconData border_inner() { + return new IconData(FontImage.MATERIAL_BORDER_INNER); + } + public static IconData border_left() { + return new IconData(FontImage.MATERIAL_BORDER_LEFT); + } + public static IconData border_outer() { + return new IconData(FontImage.MATERIAL_BORDER_OUTER); + } + public static IconData border_right() { + return new IconData(FontImage.MATERIAL_BORDER_RIGHT); + } + public static IconData border_style() { + return new IconData(FontImage.MATERIAL_BORDER_STYLE); + } + public static IconData border_top() { + return new IconData(FontImage.MATERIAL_BORDER_TOP); + } + public static IconData border_vertical() { + return new IconData(FontImage.MATERIAL_BORDER_VERTICAL); + } + public static IconData boy() { + return new IconData(FontImage.MATERIAL_BOY); + } + public static IconData branding_watermark() { + return new IconData(FontImage.MATERIAL_BRANDING_WATERMARK); + } + public static IconData breakfast_dining() { + return new IconData(FontImage.MATERIAL_BREAKFAST_DINING); + } + public static IconData brightness_1() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_1); + } + public static IconData brightness_2() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_2); + } + public static IconData brightness_3() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_3); + } + public static IconData brightness_4() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_4); + } + public static IconData brightness_6() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_6); + } + public static IconData brightness_7() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_7); + } + public static IconData brightness_auto() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_AUTO); + } + public static IconData brightness_high() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_HIGH); + } + public static IconData brightness_low() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_LOW); + } + public static IconData brightness_medium() { + return new IconData(FontImage.MATERIAL_BRIGHTNESS_MEDIUM); + } + public static IconData broadcast_on_home() { + return new IconData(FontImage.MATERIAL_BROADCAST_ON_HOME); + } + public static IconData broadcast_on_personal() { + return new IconData(FontImage.MATERIAL_BROADCAST_ON_PERSONAL); + } + public static IconData broken_image() { + return new IconData(FontImage.MATERIAL_BROKEN_IMAGE); + } + public static IconData browse_gallery() { + return new IconData(FontImage.MATERIAL_BROWSE_GALLERY); + } + public static IconData browser_not_supported() { + return new IconData(FontImage.MATERIAL_BROWSER_NOT_SUPPORTED); + } + public static IconData browser_updated() { + return new IconData(FontImage.MATERIAL_BROWSER_UPDATED); + } + public static IconData brunch_dining() { + return new IconData(FontImage.MATERIAL_BRUNCH_DINING); + } + public static IconData brush() { + return new IconData(FontImage.MATERIAL_BRUSH); + } + public static IconData bubble_chart() { + return new IconData(FontImage.MATERIAL_BUBBLE_CHART); + } + public static IconData bug_report() { + return new IconData(FontImage.MATERIAL_BUG_REPORT); + } + public static IconData build() { + return new IconData(FontImage.MATERIAL_BUILD); + } + public static IconData build_circle() { + return new IconData(FontImage.MATERIAL_BUILD_CIRCLE); + } + public static IconData bungalow() { + return new IconData(FontImage.MATERIAL_BUNGALOW); + } + public static IconData burst_mode() { + return new IconData(FontImage.MATERIAL_BURST_MODE); + } + public static IconData bus_alert() { + return new IconData(FontImage.MATERIAL_BUS_ALERT); + } + public static IconData business() { + return new IconData(FontImage.MATERIAL_BUSINESS); + } + public static IconData business_center() { + return new IconData(FontImage.MATERIAL_BUSINESS_CENTER); + } + public static IconData cabin() { + return new IconData(FontImage.MATERIAL_CABIN); + } + public static IconData cable() { + return new IconData(FontImage.MATERIAL_CABLE); + } + public static IconData cached() { + return new IconData(FontImage.MATERIAL_CACHED); + } + public static IconData cake() { + return new IconData(FontImage.MATERIAL_CAKE); + } + public static IconData calculate() { + return new IconData(FontImage.MATERIAL_CALCULATE); + } + public static IconData calendar_month() { + return new IconData(FontImage.MATERIAL_CALENDAR_MONTH); + } + public static IconData calendar_view_day() { + return new IconData(FontImage.MATERIAL_CALENDAR_VIEW_DAY); + } + public static IconData calendar_view_month() { + return new IconData(FontImage.MATERIAL_CALENDAR_VIEW_MONTH); + } + public static IconData calendar_view_week() { + return new IconData(FontImage.MATERIAL_CALENDAR_VIEW_WEEK); + } + public static IconData call() { + return new IconData(FontImage.MATERIAL_CALL); + } + public static IconData call_end() { + return new IconData(FontImage.MATERIAL_CALL_END); + } + public static IconData call_made() { + return new IconData(FontImage.MATERIAL_CALL_MADE); + } + public static IconData call_merge() { + return new IconData(FontImage.MATERIAL_CALL_MERGE); + } + public static IconData call_missed() { + return new IconData(FontImage.MATERIAL_CALL_MISSED); + } + public static IconData call_missed_outgoing() { + return new IconData(FontImage.MATERIAL_CALL_MISSED_OUTGOING); + } + public static IconData call_received() { + return new IconData(FontImage.MATERIAL_CALL_RECEIVED); + } + public static IconData call_split() { + return new IconData(FontImage.MATERIAL_CALL_SPLIT); + } + public static IconData call_to_action() { + return new IconData(FontImage.MATERIAL_CALL_TO_ACTION); + } + public static IconData camera() { + return new IconData(FontImage.MATERIAL_CAMERA); + } + public static IconData camera_alt() { + return new IconData(FontImage.MATERIAL_CAMERA_ALT); + } + public static IconData camera_front() { + return new IconData(FontImage.MATERIAL_CAMERA_FRONT); + } + public static IconData camera_indoor() { + return new IconData(FontImage.MATERIAL_CAMERA_INDOOR); + } + public static IconData camera_outdoor() { + return new IconData(FontImage.MATERIAL_CAMERA_OUTDOOR); + } + public static IconData camera_rear() { + return new IconData(FontImage.MATERIAL_CAMERA_REAR); + } + public static IconData camera_roll() { + return new IconData(FontImage.MATERIAL_CAMERA_ROLL); + } + public static IconData cameraswitch() { + return new IconData(FontImage.MATERIAL_CAMERASWITCH); + } + public static IconData campaign() { + return new IconData(FontImage.MATERIAL_CAMPAIGN); + } + public static IconData cancel() { + return new IconData(FontImage.MATERIAL_CANCEL); + } + public static IconData cancel_presentation() { + return new IconData(FontImage.MATERIAL_CANCEL_PRESENTATION); + } + public static IconData cancel_schedule_send() { + return new IconData(FontImage.MATERIAL_CANCEL_SCHEDULE_SEND); + } + public static IconData candlestick_chart() { + return new IconData(FontImage.MATERIAL_CANDLESTICK_CHART); + } + public static IconData car_crash() { + return new IconData(FontImage.MATERIAL_CAR_CRASH); + } + public static IconData car_rental() { + return new IconData(FontImage.MATERIAL_CAR_RENTAL); + } + public static IconData car_repair() { + return new IconData(FontImage.MATERIAL_CAR_REPAIR); + } + public static IconData card_giftcard() { + return new IconData(FontImage.MATERIAL_CARD_GIFTCARD); + } + public static IconData card_membership() { + return new IconData(FontImage.MATERIAL_CARD_MEMBERSHIP); + } + public static IconData card_travel() { + return new IconData(FontImage.MATERIAL_CARD_TRAVEL); + } + public static IconData carpenter() { + return new IconData(FontImage.MATERIAL_CARPENTER); + } + public static IconData cases() { + return new IconData(FontImage.MATERIAL_CASES); + } + public static IconData casino() { + return new IconData(FontImage.MATERIAL_CASINO); + } + public static IconData cast() { + return new IconData(FontImage.MATERIAL_CAST); + } + public static IconData cast_connected() { + return new IconData(FontImage.MATERIAL_CAST_CONNECTED); + } + public static IconData cast_for_education() { + return new IconData(FontImage.MATERIAL_CAST_FOR_EDUCATION); + } + public static IconData castle() { + return new IconData(FontImage.MATERIAL_CASTLE); + } + public static IconData catching_pokemon() { + return new IconData(FontImage.MATERIAL_CATCHING_POKEMON); + } + public static IconData category() { + return new IconData(FontImage.MATERIAL_CATEGORY); + } + public static IconData celebration() { + return new IconData(FontImage.MATERIAL_CELEBRATION); + } + public static IconData cell_tower() { + return new IconData(FontImage.MATERIAL_CELL_TOWER); + } + public static IconData cell_wifi() { + return new IconData(FontImage.MATERIAL_CELL_WIFI); + } + public static IconData center_focus_strong() { + return new IconData(FontImage.MATERIAL_CENTER_FOCUS_STRONG); + } + public static IconData center_focus_weak() { + return new IconData(FontImage.MATERIAL_CENTER_FOCUS_WEAK); + } + public static IconData chair() { + return new IconData(FontImage.MATERIAL_CHAIR); + } + public static IconData chair_alt() { + return new IconData(FontImage.MATERIAL_CHAIR_ALT); + } + public static IconData chalet() { + return new IconData(FontImage.MATERIAL_CHALET); + } + public static IconData change_circle() { + return new IconData(FontImage.MATERIAL_CHANGE_CIRCLE); + } + public static IconData change_history() { + return new IconData(FontImage.MATERIAL_CHANGE_HISTORY); + } + public static IconData charging_station() { + return new IconData(FontImage.MATERIAL_CHARGING_STATION); + } + public static IconData chat() { + return new IconData(FontImage.MATERIAL_CHAT); + } + public static IconData chat_bubble() { + return new IconData(FontImage.MATERIAL_CHAT_BUBBLE); + } + public static IconData chat_bubble_outline() { + return new IconData(FontImage.MATERIAL_CHAT_BUBBLE_OUTLINE); + } + public static IconData check_box() { + return new IconData(FontImage.MATERIAL_CHECK_BOX); + } + public static IconData check_box_outline_blank() { + return new IconData(FontImage.MATERIAL_CHECK_BOX_OUTLINE_BLANK); + } + public static IconData checklist() { + return new IconData(FontImage.MATERIAL_CHECKLIST); + } + public static IconData checklist_rtl() { + return new IconData(FontImage.MATERIAL_CHECKLIST_RTL); + } + public static IconData checkroom() { + return new IconData(FontImage.MATERIAL_CHECKROOM); + } + public static IconData child_care() { + return new IconData(FontImage.MATERIAL_CHILD_CARE); + } + public static IconData child_friendly() { + return new IconData(FontImage.MATERIAL_CHILD_FRIENDLY); + } + public static IconData chrome_reader_mode() { + return new IconData(FontImage.MATERIAL_CHROME_READER_MODE); + } + public static IconData church() { + return new IconData(FontImage.MATERIAL_CHURCH); + } + public static IconData circle() { + return new IconData(FontImage.MATERIAL_CIRCLE); + } + public static IconData circle_notifications() { + return new IconData(FontImage.MATERIAL_CIRCLE_NOTIFICATIONS); + } + public static IconData class_() { + return new IconData(FontImage.MATERIAL_CLASS); + } + public static IconData clean_hands() { + return new IconData(FontImage.MATERIAL_CLEAN_HANDS); + } + public static IconData cleaning_services() { + return new IconData(FontImage.MATERIAL_CLEANING_SERVICES); + } + public static IconData clear() { + return new IconData(FontImage.MATERIAL_CLEAR); + } + public static IconData clear_all() { + return new IconData(FontImage.MATERIAL_CLEAR_ALL); + } + public static IconData close_fullscreen() { + return new IconData(FontImage.MATERIAL_CLOSE_FULLSCREEN); + } + public static IconData closed_caption() { + return new IconData(FontImage.MATERIAL_CLOSED_CAPTION); + } + public static IconData closed_caption_disabled() { + return new IconData(FontImage.MATERIAL_CLOSED_CAPTION_DISABLED); + } + public static IconData closed_caption_off() { + return new IconData(FontImage.MATERIAL_CLOSED_CAPTION_OFF); + } + public static IconData cloud() { + return new IconData(FontImage.MATERIAL_CLOUD); + } + public static IconData cloud_circle() { + return new IconData(FontImage.MATERIAL_CLOUD_CIRCLE); + } + public static IconData cloud_done() { + return new IconData(FontImage.MATERIAL_CLOUD_DONE); + } + public static IconData cloud_download() { + return new IconData(FontImage.MATERIAL_CLOUD_DOWNLOAD); + } + public static IconData cloud_off() { + return new IconData(FontImage.MATERIAL_CLOUD_OFF); + } + public static IconData cloud_queue() { + return new IconData(FontImage.MATERIAL_CLOUD_QUEUE); + } + public static IconData cloud_sync() { + return new IconData(FontImage.MATERIAL_CLOUD_SYNC); + } + public static IconData cloud_upload() { + return new IconData(FontImage.MATERIAL_CLOUD_UPLOAD); + } + public static IconData cloudy_snowing() { + return new IconData(FontImage.MATERIAL_CLOUDY_SNOWING); + } + public static IconData co2() { + return new IconData(FontImage.MATERIAL_CO2); + } + public static IconData co_present() { + return new IconData(FontImage.MATERIAL_CO_PRESENT); + } + public static IconData code_off() { + return new IconData(FontImage.MATERIAL_CODE_OFF); + } + public static IconData coffee() { + return new IconData(FontImage.MATERIAL_COFFEE); + } + public static IconData coffee_maker() { + return new IconData(FontImage.MATERIAL_COFFEE_MAKER); + } + public static IconData collections() { + return new IconData(FontImage.MATERIAL_COLLECTIONS); + } + public static IconData collections_bookmark() { + return new IconData(FontImage.MATERIAL_COLLECTIONS_BOOKMARK); + } + public static IconData color_lens() { + return new IconData(FontImage.MATERIAL_COLOR_LENS); + } + public static IconData colorize() { + return new IconData(FontImage.MATERIAL_COLORIZE); + } + public static IconData comment_bank() { + return new IconData(FontImage.MATERIAL_COMMENT_BANK); + } + public static IconData comments_disabled() { + return new IconData(FontImage.MATERIAL_COMMENTS_DISABLED); + } + public static IconData commit() { + return new IconData(FontImage.MATERIAL_COMMIT); + } + public static IconData commute() { + return new IconData(FontImage.MATERIAL_COMMUTE); + } + public static IconData compare() { + return new IconData(FontImage.MATERIAL_COMPARE); + } + public static IconData compare_arrows() { + return new IconData(FontImage.MATERIAL_COMPARE_ARROWS); + } + public static IconData compass_calibration() { + return new IconData(FontImage.MATERIAL_COMPASS_CALIBRATION); + } + public static IconData compost() { + return new IconData(FontImage.MATERIAL_COMPOST); + } + public static IconData compress() { + return new IconData(FontImage.MATERIAL_COMPRESS); + } + public static IconData computer() { + return new IconData(FontImage.MATERIAL_COMPUTER); + } + public static IconData confirmation_num() { + return new IconData(FontImage.MATERIAL_CONFIRMATION_NUM); + } + public static IconData confirmation_number() { + return new IconData(FontImage.MATERIAL_CONFIRMATION_NUMBER); + } + public static IconData connect_without_contact() { + return new IconData(FontImage.MATERIAL_CONNECT_WITHOUT_CONTACT); + } + public static IconData connected_tv() { + return new IconData(FontImage.MATERIAL_CONNECTED_TV); + } + public static IconData connecting_airports() { + return new IconData(FontImage.MATERIAL_CONNECTING_AIRPORTS); + } + public static IconData construction() { + return new IconData(FontImage.MATERIAL_CONSTRUCTION); + } + public static IconData contact_emergency() { + return new IconData(FontImage.MATERIAL_CONTACT_EMERGENCY); + } + public static IconData contact_mail() { + return new IconData(FontImage.MATERIAL_CONTACT_MAIL); + } + public static IconData contact_page() { + return new IconData(FontImage.MATERIAL_CONTACT_PAGE); + } + public static IconData contact_phone() { + return new IconData(FontImage.MATERIAL_CONTACT_PHONE); + } + public static IconData contact_support() { + return new IconData(FontImage.MATERIAL_CONTACT_SUPPORT); + } + public static IconData contactless() { + return new IconData(FontImage.MATERIAL_CONTACTLESS); + } + public static IconData contacts() { + return new IconData(FontImage.MATERIAL_CONTACTS); + } + public static IconData content_copy() { + return new IconData(FontImage.MATERIAL_CONTENT_COPY); + } + public static IconData content_cut() { + return new IconData(FontImage.MATERIAL_CONTENT_CUT); + } + public static IconData content_paste() { + return new IconData(FontImage.MATERIAL_CONTENT_PASTE); + } + public static IconData content_paste_go() { + return new IconData(FontImage.MATERIAL_CONTENT_PASTE_GO); + } + public static IconData content_paste_off() { + return new IconData(FontImage.MATERIAL_CONTENT_PASTE_OFF); + } + public static IconData content_paste_search() { + return new IconData(FontImage.MATERIAL_CONTENT_PASTE_SEARCH); + } + public static IconData contrast() { + return new IconData(FontImage.MATERIAL_CONTRAST); + } + public static IconData control_camera() { + return new IconData(FontImage.MATERIAL_CONTROL_CAMERA); + } + public static IconData control_point() { + return new IconData(FontImage.MATERIAL_CONTROL_POINT); + } + public static IconData control_point_duplicate() { + return new IconData(FontImage.MATERIAL_CONTROL_POINT_DUPLICATE); + } + public static IconData conveyor_belt() { + return new IconData(FontImage.MATERIAL_CONVEYOR_BELT); + } + public static IconData cookie() { + return new IconData(FontImage.MATERIAL_COOKIE); + } + public static IconData copy_all() { + return new IconData(FontImage.MATERIAL_COPY_ALL); + } + public static IconData copyright() { + return new IconData(FontImage.MATERIAL_COPYRIGHT); + } + public static IconData coronavirus() { + return new IconData(FontImage.MATERIAL_CORONAVIRUS); + } + public static IconData corporate_fare() { + return new IconData(FontImage.MATERIAL_CORPORATE_FARE); + } + public static IconData cottage() { + return new IconData(FontImage.MATERIAL_COTTAGE); + } + public static IconData countertops() { + return new IconData(FontImage.MATERIAL_COUNTERTOPS); + } + public static IconData create_new_folder() { + return new IconData(FontImage.MATERIAL_CREATE_NEW_FOLDER); + } + public static IconData credit_card_off() { + return new IconData(FontImage.MATERIAL_CREDIT_CARD_OFF); + } + public static IconData credit_score() { + return new IconData(FontImage.MATERIAL_CREDIT_SCORE); + } + public static IconData crib() { + return new IconData(FontImage.MATERIAL_CRIB); + } + public static IconData crisis_alert() { + return new IconData(FontImage.MATERIAL_CRISIS_ALERT); + } + public static IconData crop() { + return new IconData(FontImage.MATERIAL_CROP); + } + public static IconData crop_16_9() { + return new IconData(FontImage.MATERIAL_CROP_16_9); + } + public static IconData crop_3_2() { + return new IconData(FontImage.MATERIAL_CROP_3_2); + } + public static IconData crop_5_4() { + return new IconData(FontImage.MATERIAL_CROP_5_4); + } + public static IconData crop_7_5() { + return new IconData(FontImage.MATERIAL_CROP_7_5); + } + public static IconData crop_din() { + return new IconData(FontImage.MATERIAL_CROP_DIN); + } + public static IconData crop_free() { + return new IconData(FontImage.MATERIAL_CROP_FREE); + } + public static IconData crop_landscape() { + return new IconData(FontImage.MATERIAL_CROP_LANDSCAPE); + } + public static IconData crop_original() { + return new IconData(FontImage.MATERIAL_CROP_ORIGINAL); + } + public static IconData crop_portrait() { + return new IconData(FontImage.MATERIAL_CROP_PORTRAIT); + } + public static IconData crop_rotate() { + return new IconData(FontImage.MATERIAL_CROP_ROTATE); + } + public static IconData crop_square() { + return new IconData(FontImage.MATERIAL_CROP_SQUARE); + } + public static IconData cruelty_free() { + return new IconData(FontImage.MATERIAL_CRUELTY_FREE); + } + public static IconData css() { + return new IconData(FontImage.MATERIAL_CSS); + } + public static IconData currency_bitcoin() { + return new IconData(FontImage.MATERIAL_CURRENCY_BITCOIN); + } + public static IconData currency_exchange() { + return new IconData(FontImage.MATERIAL_CURRENCY_EXCHANGE); + } + public static IconData currency_franc() { + return new IconData(FontImage.MATERIAL_CURRENCY_FRANC); + } + public static IconData currency_lira() { + return new IconData(FontImage.MATERIAL_CURRENCY_LIRA); + } + public static IconData currency_pound() { + return new IconData(FontImage.MATERIAL_CURRENCY_POUND); + } + public static IconData currency_ruble() { + return new IconData(FontImage.MATERIAL_CURRENCY_RUBLE); + } + public static IconData currency_rupee() { + return new IconData(FontImage.MATERIAL_CURRENCY_RUPEE); + } + public static IconData currency_yen() { + return new IconData(FontImage.MATERIAL_CURRENCY_YEN); + } + public static IconData currency_yuan() { + return new IconData(FontImage.MATERIAL_CURRENCY_YUAN); + } + public static IconData curtains() { + return new IconData(FontImage.MATERIAL_CURTAINS); + } + public static IconData curtains_closed() { + return new IconData(FontImage.MATERIAL_CURTAINS_CLOSED); + } + public static IconData cyclone() { + return new IconData(FontImage.MATERIAL_CYCLONE); + } + public static IconData dangerous() { + return new IconData(FontImage.MATERIAL_DANGEROUS); + } + public static IconData dark_mode() { + return new IconData(FontImage.MATERIAL_DARK_MODE); + } + public static IconData dashboard() { + return new IconData(FontImage.MATERIAL_DASHBOARD); + } + public static IconData dashboard_customize() { + return new IconData(FontImage.MATERIAL_DASHBOARD_CUSTOMIZE); + } + public static IconData data_array() { + return new IconData(FontImage.MATERIAL_DATA_ARRAY); + } + public static IconData data_exploration() { + return new IconData(FontImage.MATERIAL_DATA_EXPLORATION); + } + public static IconData data_object() { + return new IconData(FontImage.MATERIAL_DATA_OBJECT); + } + public static IconData data_saver_off() { + return new IconData(FontImage.MATERIAL_DATA_SAVER_OFF); + } + public static IconData data_saver_on() { + return new IconData(FontImage.MATERIAL_DATA_SAVER_ON); + } + public static IconData data_thresholding() { + return new IconData(FontImage.MATERIAL_DATA_THRESHOLDING); + } + public static IconData data_usage() { + return new IconData(FontImage.MATERIAL_DATA_USAGE); + } + public static IconData dataset() { + return new IconData(FontImage.MATERIAL_DATASET); + } + public static IconData dataset_linked() { + return new IconData(FontImage.MATERIAL_DATASET_LINKED); + } + public static IconData deblur() { + return new IconData(FontImage.MATERIAL_DEBLUR); + } + public static IconData deck() { + return new IconData(FontImage.MATERIAL_DECK); + } + public static IconData dehaze() { + return new IconData(FontImage.MATERIAL_DEHAZE); + } + public static IconData delete_forever() { + return new IconData(FontImage.MATERIAL_DELETE_FOREVER); + } + public static IconData delete_outline() { + return new IconData(FontImage.MATERIAL_DELETE_OUTLINE); + } + public static IconData delete_sweep() { + return new IconData(FontImage.MATERIAL_DELETE_SWEEP); + } + public static IconData delivery_dining() { + return new IconData(FontImage.MATERIAL_DELIVERY_DINING); + } + public static IconData density_large() { + return new IconData(FontImage.MATERIAL_DENSITY_LARGE); + } + public static IconData density_medium() { + return new IconData(FontImage.MATERIAL_DENSITY_MEDIUM); + } + public static IconData density_small() { + return new IconData(FontImage.MATERIAL_DENSITY_SMALL); + } + public static IconData departure_board() { + return new IconData(FontImage.MATERIAL_DEPARTURE_BOARD); + } + public static IconData description() { + return new IconData(FontImage.MATERIAL_DESCRIPTION); + } + public static IconData deselect() { + return new IconData(FontImage.MATERIAL_DESELECT); + } + public static IconData design_services() { + return new IconData(FontImage.MATERIAL_DESIGN_SERVICES); + } + public static IconData desk() { + return new IconData(FontImage.MATERIAL_DESK); + } + public static IconData desktop_access_disabled() { + return new IconData(FontImage.MATERIAL_DESKTOP_ACCESS_DISABLED); + } + public static IconData desktop_mac() { + return new IconData(FontImage.MATERIAL_DESKTOP_MAC); + } + public static IconData desktop_windows() { + return new IconData(FontImage.MATERIAL_DESKTOP_WINDOWS); + } + public static IconData details() { + return new IconData(FontImage.MATERIAL_DETAILS); + } + public static IconData developer_board() { + return new IconData(FontImage.MATERIAL_DEVELOPER_BOARD); + } + public static IconData developer_board_off() { + return new IconData(FontImage.MATERIAL_DEVELOPER_BOARD_OFF); + } + public static IconData developer_mode() { + return new IconData(FontImage.MATERIAL_DEVELOPER_MODE); + } + public static IconData device_hub() { + return new IconData(FontImage.MATERIAL_DEVICE_HUB); + } + public static IconData device_thermostat() { + return new IconData(FontImage.MATERIAL_DEVICE_THERMOSTAT); + } + public static IconData device_unknown() { + return new IconData(FontImage.MATERIAL_DEVICE_UNKNOWN); + } + public static IconData devices() { + return new IconData(FontImage.MATERIAL_DEVICES); + } + public static IconData devices_fold() { + return new IconData(FontImage.MATERIAL_DEVICES_FOLD); + } + public static IconData devices_other() { + return new IconData(FontImage.MATERIAL_DEVICES_OTHER); + } + public static IconData dew_point() { + return new IconData(FontImage.MATERIAL_DEW_POINT); + } + public static IconData dialer_sip() { + return new IconData(FontImage.MATERIAL_DIALER_SIP); + } + public static IconData dialpad() { + return new IconData(FontImage.MATERIAL_DIALPAD); + } + public static IconData diamond() { + return new IconData(FontImage.MATERIAL_DIAMOND); + } + public static IconData difference() { + return new IconData(FontImage.MATERIAL_DIFFERENCE); + } + public static IconData dining() { + return new IconData(FontImage.MATERIAL_DINING); + } + public static IconData dinner_dining() { + return new IconData(FontImage.MATERIAL_DINNER_DINING); + } + public static IconData directions() { + return new IconData(FontImage.MATERIAL_DIRECTIONS); + } + public static IconData directions_boat() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_BOAT); + } + public static IconData directions_boat_filled() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_BOAT_FILLED); + } + public static IconData directions_bus() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_BUS); + } + public static IconData directions_bus_filled() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_BUS_FILLED); + } + public static IconData directions_car() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_CAR); + } + public static IconData directions_car_filled() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_CAR_FILLED); + } + public static IconData directions_ferry() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_FERRY); + } + public static IconData directions_off() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_OFF); + } + public static IconData directions_railway() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_RAILWAY); + } + public static IconData directions_railway_filled() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_RAILWAY_FILLED); + } + public static IconData directions_run() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_RUN); + } + public static IconData directions_subway() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_SUBWAY); + } + public static IconData directions_subway_filled() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_SUBWAY_FILLED); + } + public static IconData directions_train() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_TRAIN); + } + public static IconData directions_transit() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_TRANSIT); + } + public static IconData directions_transit_filled() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_TRANSIT_FILLED); + } + public static IconData directions_walk() { + return new IconData(FontImage.MATERIAL_DIRECTIONS_WALK); + } + public static IconData dirty_lens() { + return new IconData(FontImage.MATERIAL_DIRTY_LENS); + } + public static IconData disabled_by_default() { + return new IconData(FontImage.MATERIAL_DISABLED_BY_DEFAULT); + } + public static IconData disabled_visible() { + return new IconData(FontImage.MATERIAL_DISABLED_VISIBLE); + } + public static IconData disc_full() { + return new IconData(FontImage.MATERIAL_DISC_FULL); + } + public static IconData discord() { + return new IconData(FontImage.MATERIAL_DISCORD); + } + public static IconData discount() { + return new IconData(FontImage.MATERIAL_DISCOUNT); + } + public static IconData display_settings() { + return new IconData(FontImage.MATERIAL_DISPLAY_SETTINGS); + } + public static IconData diversity_1() { + return new IconData(FontImage.MATERIAL_DIVERSITY_1); + } + public static IconData diversity_2() { + return new IconData(FontImage.MATERIAL_DIVERSITY_2); + } + public static IconData diversity_3() { + return new IconData(FontImage.MATERIAL_DIVERSITY_3); + } + public static IconData dnd_forwardslash() { + return new IconData(FontImage.MATERIAL_DND_FORWARDSLASH); + } + public static IconData dns() { + return new IconData(FontImage.MATERIAL_DNS); + } + public static IconData do_disturb() { + return new IconData(FontImage.MATERIAL_DO_DISTURB); + } + public static IconData do_disturb_alt() { + return new IconData(FontImage.MATERIAL_DO_DISTURB_ALT); + } + public static IconData do_disturb_off() { + return new IconData(FontImage.MATERIAL_DO_DISTURB_OFF); + } + public static IconData do_disturb_on() { + return new IconData(FontImage.MATERIAL_DO_DISTURB_ON); + } + public static IconData do_not_disturb() { + return new IconData(FontImage.MATERIAL_DO_NOT_DISTURB); + } + public static IconData do_not_disturb_alt() { + return new IconData(FontImage.MATERIAL_DO_NOT_DISTURB_ALT); + } + public static IconData do_not_disturb_off() { + return new IconData(FontImage.MATERIAL_DO_NOT_DISTURB_OFF); + } + public static IconData do_not_disturb_on() { + return new IconData(FontImage.MATERIAL_DO_NOT_DISTURB_ON); + } + public static IconData do_not_disturb_on_total_silence() { + return new IconData(FontImage.MATERIAL_DO_NOT_DISTURB_ON_TOTAL_SILENCE); + } + public static IconData do_not_step() { + return new IconData(FontImage.MATERIAL_DO_NOT_STEP); + } + public static IconData do_not_touch() { + return new IconData(FontImage.MATERIAL_DO_NOT_TOUCH); + } + public static IconData dock() { + return new IconData(FontImage.MATERIAL_DOCK); + } + public static IconData document_scanner() { + return new IconData(FontImage.MATERIAL_DOCUMENT_SCANNER); + } + public static IconData domain() { + return new IconData(FontImage.MATERIAL_DOMAIN); + } + public static IconData domain_add() { + return new IconData(FontImage.MATERIAL_DOMAIN_ADD); + } + public static IconData domain_disabled() { + return new IconData(FontImage.MATERIAL_DOMAIN_DISABLED); + } + public static IconData domain_verification() { + return new IconData(FontImage.MATERIAL_DOMAIN_VERIFICATION); + } + public static IconData done() { + return new IconData(FontImage.MATERIAL_DONE); + } + public static IconData done_all() { + return new IconData(FontImage.MATERIAL_DONE_ALL); + } + public static IconData done_outline() { + return new IconData(FontImage.MATERIAL_DONE_OUTLINE); + } + public static IconData donut_large() { + return new IconData(FontImage.MATERIAL_DONUT_LARGE); + } + public static IconData donut_small() { + return new IconData(FontImage.MATERIAL_DONUT_SMALL); + } + public static IconData door_back() { + return new IconData(FontImage.MATERIAL_DOOR_BACK); + } + public static IconData door_front() { + return new IconData(FontImage.MATERIAL_DOOR_FRONT); + } + public static IconData door_sliding() { + return new IconData(FontImage.MATERIAL_DOOR_SLIDING); + } + public static IconData doorbell() { + return new IconData(FontImage.MATERIAL_DOORBELL); + } + public static IconData double_arrow() { + return new IconData(FontImage.MATERIAL_DOUBLE_ARROW); + } + public static IconData downhill_skiing() { + return new IconData(FontImage.MATERIAL_DOWNHILL_SKIING); + } + public static IconData download() { + return new IconData(FontImage.MATERIAL_DOWNLOAD); + } + public static IconData download_done() { + return new IconData(FontImage.MATERIAL_DOWNLOAD_DONE); + } + public static IconData download_for_offline() { + return new IconData(FontImage.MATERIAL_DOWNLOAD_FOR_OFFLINE); + } + public static IconData downloading() { + return new IconData(FontImage.MATERIAL_DOWNLOADING); + } + public static IconData drafts() { + return new IconData(FontImage.MATERIAL_DRAFTS); + } + public static IconData drag_handle() { + return new IconData(FontImage.MATERIAL_DRAG_HANDLE); + } + public static IconData drag_indicator() { + return new IconData(FontImage.MATERIAL_DRAG_INDICATOR); + } + public static IconData draw() { + return new IconData(FontImage.MATERIAL_DRAW); + } + public static IconData drive_eta() { + return new IconData(FontImage.MATERIAL_DRIVE_ETA); + } + public static IconData drive_file_move() { + return new IconData(FontImage.MATERIAL_DRIVE_FILE_MOVE); + } + public static IconData drive_file_move_outline() { + return new IconData(FontImage.MATERIAL_DRIVE_FILE_MOVE_OUTLINE); + } + public static IconData drive_file_move_rtl() { + return new IconData(FontImage.MATERIAL_DRIVE_FILE_MOVE_RTL); + } + public static IconData drive_file_rename_outline() { + return new IconData(FontImage.MATERIAL_DRIVE_FILE_RENAME_OUTLINE); + } + public static IconData drive_folder_upload() { + return new IconData(FontImage.MATERIAL_DRIVE_FOLDER_UPLOAD); + } + public static IconData dry() { + return new IconData(FontImage.MATERIAL_DRY); + } + public static IconData dry_cleaning() { + return new IconData(FontImage.MATERIAL_DRY_CLEANING); + } + public static IconData duo() { + return new IconData(FontImage.MATERIAL_DUO); + } + public static IconData dvr() { + return new IconData(FontImage.MATERIAL_DVR); + } + public static IconData dynamic_feed() { + return new IconData(FontImage.MATERIAL_DYNAMIC_FEED); + } + public static IconData dynamic_form() { + return new IconData(FontImage.MATERIAL_DYNAMIC_FORM); + } + public static IconData e_mobiledata() { + return new IconData(FontImage.MATERIAL_E_MOBILEDATA); + } + public static IconData earbuds() { + return new IconData(FontImage.MATERIAL_EARBUDS); + } + public static IconData earbuds_battery() { + return new IconData(FontImage.MATERIAL_EARBUDS_BATTERY); + } + public static IconData east() { + return new IconData(FontImage.MATERIAL_EAST); + } + public static IconData eco() { + return new IconData(FontImage.MATERIAL_ECO); + } + public static IconData edgesensor_high() { + return new IconData(FontImage.MATERIAL_EDGESENSOR_HIGH); + } + public static IconData edgesensor_low() { + return new IconData(FontImage.MATERIAL_EDGESENSOR_LOW); + } + public static IconData edit_attributes() { + return new IconData(FontImage.MATERIAL_EDIT_ATTRIBUTES); + } + public static IconData edit_calendar() { + return new IconData(FontImage.MATERIAL_EDIT_CALENDAR); + } + public static IconData edit_document() { + return new IconData(FontImage.MATERIAL_EDIT_DOCUMENT); + } + public static IconData edit_location() { + return new IconData(FontImage.MATERIAL_EDIT_LOCATION); + } + public static IconData edit_location_alt() { + return new IconData(FontImage.MATERIAL_EDIT_LOCATION_ALT); + } + public static IconData edit_note() { + return new IconData(FontImage.MATERIAL_EDIT_NOTE); + } + public static IconData edit_notifications() { + return new IconData(FontImage.MATERIAL_EDIT_NOTIFICATIONS); + } + public static IconData edit_off() { + return new IconData(FontImage.MATERIAL_EDIT_OFF); + } + public static IconData edit_road() { + return new IconData(FontImage.MATERIAL_EDIT_ROAD); + } + public static IconData edit_square() { + return new IconData(FontImage.MATERIAL_EDIT_SQUARE); + } + public static IconData egg() { + return new IconData(FontImage.MATERIAL_EGG); + } + public static IconData egg_alt() { + return new IconData(FontImage.MATERIAL_EGG_ALT); + } + public static IconData eject() { + return new IconData(FontImage.MATERIAL_EJECT); + } + public static IconData elderly() { + return new IconData(FontImage.MATERIAL_ELDERLY); + } + public static IconData elderly_woman() { + return new IconData(FontImage.MATERIAL_ELDERLY_WOMAN); + } + public static IconData electric_bike() { + return new IconData(FontImage.MATERIAL_ELECTRIC_BIKE); + } + public static IconData electric_bolt() { + return new IconData(FontImage.MATERIAL_ELECTRIC_BOLT); + } + public static IconData electric_car() { + return new IconData(FontImage.MATERIAL_ELECTRIC_CAR); + } + public static IconData electric_meter() { + return new IconData(FontImage.MATERIAL_ELECTRIC_METER); + } + public static IconData electric_moped() { + return new IconData(FontImage.MATERIAL_ELECTRIC_MOPED); + } + public static IconData electric_rickshaw() { + return new IconData(FontImage.MATERIAL_ELECTRIC_RICKSHAW); + } + public static IconData electric_scooter() { + return new IconData(FontImage.MATERIAL_ELECTRIC_SCOOTER); + } + public static IconData electrical_services() { + return new IconData(FontImage.MATERIAL_ELECTRICAL_SERVICES); + } + public static IconData elevator() { + return new IconData(FontImage.MATERIAL_ELEVATOR); + } + public static IconData emergency() { + return new IconData(FontImage.MATERIAL_EMERGENCY); + } + public static IconData emergency_recording() { + return new IconData(FontImage.MATERIAL_EMERGENCY_RECORDING); + } + public static IconData emergency_share() { + return new IconData(FontImage.MATERIAL_EMERGENCY_SHARE); + } + public static IconData emoji_emotions() { + return new IconData(FontImage.MATERIAL_EMOJI_EMOTIONS); + } + public static IconData emoji_events() { + return new IconData(FontImage.MATERIAL_EMOJI_EVENTS); + } + public static IconData emoji_flags() { + return new IconData(FontImage.MATERIAL_EMOJI_FLAGS); + } + public static IconData emoji_food_beverage() { + return new IconData(FontImage.MATERIAL_EMOJI_FOOD_BEVERAGE); + } + public static IconData emoji_nature() { + return new IconData(FontImage.MATERIAL_EMOJI_NATURE); + } + public static IconData emoji_objects() { + return new IconData(FontImage.MATERIAL_EMOJI_OBJECTS); + } + public static IconData emoji_people() { + return new IconData(FontImage.MATERIAL_EMOJI_PEOPLE); + } + public static IconData emoji_symbols() { + return new IconData(FontImage.MATERIAL_EMOJI_SYMBOLS); + } + public static IconData emoji_transportation() { + return new IconData(FontImage.MATERIAL_EMOJI_TRANSPORTATION); + } + public static IconData energy_savings_leaf() { + return new IconData(FontImage.MATERIAL_ENERGY_SAVINGS_LEAF); + } + public static IconData engineering() { + return new IconData(FontImage.MATERIAL_ENGINEERING); + } + public static IconData enhance_photo_translate() { + return new IconData(FontImage.MATERIAL_ENHANCE_PHOTO_TRANSLATE); + } + public static IconData enhanced_encryption() { + return new IconData(FontImage.MATERIAL_ENHANCED_ENCRYPTION); + } + public static IconData equalizer() { + return new IconData(FontImage.MATERIAL_EQUALIZER); + } + public static IconData error() { + return new IconData(FontImage.MATERIAL_ERROR); + } + public static IconData error_outline() { + return new IconData(FontImage.MATERIAL_ERROR_OUTLINE); + } + public static IconData escalator() { + return new IconData(FontImage.MATERIAL_ESCALATOR); + } + public static IconData escalator_warning() { + return new IconData(FontImage.MATERIAL_ESCALATOR_WARNING); + } + public static IconData euro() { + return new IconData(FontImage.MATERIAL_EURO); + } + public static IconData euro_symbol() { + return new IconData(FontImage.MATERIAL_EURO_SYMBOL); + } + public static IconData ev_station() { + return new IconData(FontImage.MATERIAL_EV_STATION); + } + public static IconData event() { + return new IconData(FontImage.MATERIAL_EVENT); + } + public static IconData event_available() { + return new IconData(FontImage.MATERIAL_EVENT_AVAILABLE); + } + public static IconData event_busy() { + return new IconData(FontImage.MATERIAL_EVENT_BUSY); + } + public static IconData event_note() { + return new IconData(FontImage.MATERIAL_EVENT_NOTE); + } + public static IconData event_repeat() { + return new IconData(FontImage.MATERIAL_EVENT_REPEAT); + } + public static IconData event_seat() { + return new IconData(FontImage.MATERIAL_EVENT_SEAT); + } + public static IconData exit_to_app() { + return new IconData(FontImage.MATERIAL_EXIT_TO_APP); + } + public static IconData expand() { + return new IconData(FontImage.MATERIAL_EXPAND); + } + public static IconData expand_circle_down() { + return new IconData(FontImage.MATERIAL_EXPAND_CIRCLE_DOWN); + } + public static IconData expand_less() { + return new IconData(FontImage.MATERIAL_EXPAND_LESS); + } + public static IconData expand_more() { + return new IconData(FontImage.MATERIAL_EXPAND_MORE); + } + public static IconData explicit() { + return new IconData(FontImage.MATERIAL_EXPLICIT); + } + public static IconData explore() { + return new IconData(FontImage.MATERIAL_EXPLORE); + } + public static IconData explore_off() { + return new IconData(FontImage.MATERIAL_EXPLORE_OFF); + } + public static IconData exposure() { + return new IconData(FontImage.MATERIAL_EXPOSURE); + } + public static IconData exposure_minus_1() { + return new IconData(FontImage.MATERIAL_EXPOSURE_MINUS_1); + } + public static IconData exposure_minus_2() { + return new IconData(FontImage.MATERIAL_EXPOSURE_MINUS_2); + } + public static IconData exposure_neg_1() { + return new IconData(FontImage.MATERIAL_EXPOSURE_NEG_1); + } + public static IconData exposure_neg_2() { + return new IconData(FontImage.MATERIAL_EXPOSURE_NEG_2); + } + public static IconData exposure_plus_1() { + return new IconData(FontImage.MATERIAL_EXPOSURE_PLUS_1); + } + public static IconData exposure_plus_2() { + return new IconData(FontImage.MATERIAL_EXPOSURE_PLUS_2); + } + public static IconData exposure_zero() { + return new IconData(FontImage.MATERIAL_EXPOSURE_ZERO); + } + public static IconData extension() { + return new IconData(FontImage.MATERIAL_EXTENSION); + } + public static IconData extension_off() { + return new IconData(FontImage.MATERIAL_EXTENSION_OFF); + } + public static IconData face() { + return new IconData(FontImage.MATERIAL_FACE); + } + public static IconData face_2() { + return new IconData(FontImage.MATERIAL_FACE_2); + } + public static IconData face_3() { + return new IconData(FontImage.MATERIAL_FACE_3); + } + public static IconData face_4() { + return new IconData(FontImage.MATERIAL_FACE_4); + } + public static IconData face_5() { + return new IconData(FontImage.MATERIAL_FACE_5); + } + public static IconData face_6() { + return new IconData(FontImage.MATERIAL_FACE_6); + } + public static IconData face_retouching_natural() { + return new IconData(FontImage.MATERIAL_FACE_RETOUCHING_NATURAL); + } + public static IconData face_retouching_off() { + return new IconData(FontImage.MATERIAL_FACE_RETOUCHING_OFF); + } + public static IconData facebook() { + return new IconData(FontImage.MATERIAL_FACEBOOK); + } + public static IconData fact_check() { + return new IconData(FontImage.MATERIAL_FACT_CHECK); + } + public static IconData factory() { + return new IconData(FontImage.MATERIAL_FACTORY); + } + public static IconData family_restroom() { + return new IconData(FontImage.MATERIAL_FAMILY_RESTROOM); + } + public static IconData fast_forward() { + return new IconData(FontImage.MATERIAL_FAST_FORWARD); + } + public static IconData fast_rewind() { + return new IconData(FontImage.MATERIAL_FAST_REWIND); + } + public static IconData fastfood() { + return new IconData(FontImage.MATERIAL_FASTFOOD); + } + public static IconData favorite_outline() { + return new IconData(FontImage.MATERIAL_FAVORITE_OUTLINE); + } + public static IconData fax() { + return new IconData(FontImage.MATERIAL_FAX); + } + public static IconData featured_play_list() { + return new IconData(FontImage.MATERIAL_FEATURED_PLAY_LIST); + } + public static IconData featured_video() { + return new IconData(FontImage.MATERIAL_FEATURED_VIDEO); + } + public static IconData feed() { + return new IconData(FontImage.MATERIAL_FEED); + } + public static IconData female() { + return new IconData(FontImage.MATERIAL_FEMALE); + } + public static IconData fence() { + return new IconData(FontImage.MATERIAL_FENCE); + } + public static IconData festival() { + return new IconData(FontImage.MATERIAL_FESTIVAL); + } + public static IconData fiber_dvr() { + return new IconData(FontImage.MATERIAL_FIBER_DVR); + } + public static IconData fiber_manual_record() { + return new IconData(FontImage.MATERIAL_FIBER_MANUAL_RECORD); + } + public static IconData fiber_new() { + return new IconData(FontImage.MATERIAL_FIBER_NEW); + } + public static IconData fiber_pin() { + return new IconData(FontImage.MATERIAL_FIBER_PIN); + } + public static IconData fiber_smart_record() { + return new IconData(FontImage.MATERIAL_FIBER_SMART_RECORD); + } + public static IconData file_copy() { + return new IconData(FontImage.MATERIAL_FILE_COPY); + } + public static IconData file_download() { + return new IconData(FontImage.MATERIAL_FILE_DOWNLOAD); + } + public static IconData file_download_done() { + return new IconData(FontImage.MATERIAL_FILE_DOWNLOAD_DONE); + } + public static IconData file_download_off() { + return new IconData(FontImage.MATERIAL_FILE_DOWNLOAD_OFF); + } + public static IconData file_open() { + return new IconData(FontImage.MATERIAL_FILE_OPEN); + } + public static IconData file_present() { + return new IconData(FontImage.MATERIAL_FILE_PRESENT); + } + public static IconData file_upload() { + return new IconData(FontImage.MATERIAL_FILE_UPLOAD); + } + public static IconData file_upload_off() { + return new IconData(FontImage.MATERIAL_FILE_UPLOAD_OFF); + } + public static IconData filter() { + return new IconData(FontImage.MATERIAL_FILTER); + } + public static IconData filter_1() { + return new IconData(FontImage.MATERIAL_FILTER_1); + } + public static IconData filter_2() { + return new IconData(FontImage.MATERIAL_FILTER_2); + } + public static IconData filter_3() { + return new IconData(FontImage.MATERIAL_FILTER_3); + } + public static IconData filter_4() { + return new IconData(FontImage.MATERIAL_FILTER_4); + } + public static IconData filter_5() { + return new IconData(FontImage.MATERIAL_FILTER_5); + } + public static IconData filter_6() { + return new IconData(FontImage.MATERIAL_FILTER_6); + } + public static IconData filter_7() { + return new IconData(FontImage.MATERIAL_FILTER_7); + } + public static IconData filter_8() { + return new IconData(FontImage.MATERIAL_FILTER_8); + } + public static IconData filter_9() { + return new IconData(FontImage.MATERIAL_FILTER_9); + } + public static IconData filter_9_plus() { + return new IconData(FontImage.MATERIAL_FILTER_9_PLUS); + } + public static IconData filter_alt() { + return new IconData(FontImage.MATERIAL_FILTER_ALT); + } + public static IconData filter_alt_off() { + return new IconData(FontImage.MATERIAL_FILTER_ALT_OFF); + } + public static IconData filter_b_and_w() { + return new IconData(FontImage.MATERIAL_FILTER_B_AND_W); + } + public static IconData filter_center_focus() { + return new IconData(FontImage.MATERIAL_FILTER_CENTER_FOCUS); + } + public static IconData filter_drama() { + return new IconData(FontImage.MATERIAL_FILTER_DRAMA); + } + public static IconData filter_frames() { + return new IconData(FontImage.MATERIAL_FILTER_FRAMES); + } + public static IconData filter_hdr() { + return new IconData(FontImage.MATERIAL_FILTER_HDR); + } + public static IconData filter_list() { + return new IconData(FontImage.MATERIAL_FILTER_LIST); + } + public static IconData filter_list_alt() { + return new IconData(FontImage.MATERIAL_FILTER_LIST_ALT); + } + public static IconData filter_list_off() { + return new IconData(FontImage.MATERIAL_FILTER_LIST_OFF); + } + public static IconData filter_none() { + return new IconData(FontImage.MATERIAL_FILTER_NONE); + } + public static IconData filter_tilt_shift() { + return new IconData(FontImage.MATERIAL_FILTER_TILT_SHIFT); + } + public static IconData filter_vintage() { + return new IconData(FontImage.MATERIAL_FILTER_VINTAGE); + } + public static IconData find_in_page() { + return new IconData(FontImage.MATERIAL_FIND_IN_PAGE); + } + public static IconData find_replace() { + return new IconData(FontImage.MATERIAL_FIND_REPLACE); + } + public static IconData fingerprint() { + return new IconData(FontImage.MATERIAL_FINGERPRINT); + } + public static IconData fire_extinguisher() { + return new IconData(FontImage.MATERIAL_FIRE_EXTINGUISHER); + } + public static IconData fire_hydrant() { + return new IconData(FontImage.MATERIAL_FIRE_HYDRANT); + } + public static IconData fire_hydrant_alt() { + return new IconData(FontImage.MATERIAL_FIRE_HYDRANT_ALT); + } + public static IconData fire_truck() { + return new IconData(FontImage.MATERIAL_FIRE_TRUCK); + } + public static IconData fireplace() { + return new IconData(FontImage.MATERIAL_FIREPLACE); + } + public static IconData first_page() { + return new IconData(FontImage.MATERIAL_FIRST_PAGE); + } + public static IconData fit_screen() { + return new IconData(FontImage.MATERIAL_FIT_SCREEN); + } + public static IconData fitbit() { + return new IconData(FontImage.MATERIAL_FITBIT); + } + public static IconData fitness_center() { + return new IconData(FontImage.MATERIAL_FITNESS_CENTER); + } + public static IconData flag() { + return new IconData(FontImage.MATERIAL_FLAG); + } + public static IconData flag_circle() { + return new IconData(FontImage.MATERIAL_FLAG_CIRCLE); + } + public static IconData flaky() { + return new IconData(FontImage.MATERIAL_FLAKY); + } + public static IconData flare() { + return new IconData(FontImage.MATERIAL_FLARE); + } + public static IconData flash_auto() { + return new IconData(FontImage.MATERIAL_FLASH_AUTO); + } + public static IconData flash_off() { + return new IconData(FontImage.MATERIAL_FLASH_OFF); + } + public static IconData flash_on() { + return new IconData(FontImage.MATERIAL_FLASH_ON); + } + public static IconData flashlight_off() { + return new IconData(FontImage.MATERIAL_FLASHLIGHT_OFF); + } + public static IconData flashlight_on() { + return new IconData(FontImage.MATERIAL_FLASHLIGHT_ON); + } + public static IconData flatware() { + return new IconData(FontImage.MATERIAL_FLATWARE); + } + public static IconData flight() { + return new IconData(FontImage.MATERIAL_FLIGHT); + } + public static IconData flight_class() { + return new IconData(FontImage.MATERIAL_FLIGHT_CLASS); + } + public static IconData flight_land() { + return new IconData(FontImage.MATERIAL_FLIGHT_LAND); + } + public static IconData flight_takeoff() { + return new IconData(FontImage.MATERIAL_FLIGHT_TAKEOFF); + } + public static IconData flip() { + return new IconData(FontImage.MATERIAL_FLIP); + } + public static IconData flip_camera_android() { + return new IconData(FontImage.MATERIAL_FLIP_CAMERA_ANDROID); + } + public static IconData flip_camera_ios() { + return new IconData(FontImage.MATERIAL_FLIP_CAMERA_IOS); + } + public static IconData flip_to_back() { + return new IconData(FontImage.MATERIAL_FLIP_TO_BACK); + } + public static IconData flip_to_front() { + return new IconData(FontImage.MATERIAL_FLIP_TO_FRONT); + } + public static IconData flood() { + return new IconData(FontImage.MATERIAL_FLOOD); + } + public static IconData flourescent() { + return new IconData(FontImage.MATERIAL_FLOURESCENT); + } + public static IconData fluorescent() { + return new IconData(FontImage.MATERIAL_FLUORESCENT); + } + public static IconData flutter_dash() { + return new IconData(FontImage.MATERIAL_FLUTTER_DASH); + } + public static IconData fmd_bad() { + return new IconData(FontImage.MATERIAL_FMD_BAD); + } + public static IconData fmd_good() { + return new IconData(FontImage.MATERIAL_FMD_GOOD); + } + public static IconData foggy() { + return new IconData(FontImage.MATERIAL_FOGGY); + } + public static IconData folder() { + return new IconData(FontImage.MATERIAL_FOLDER); + } + public static IconData folder_copy() { + return new IconData(FontImage.MATERIAL_FOLDER_COPY); + } + public static IconData folder_delete() { + return new IconData(FontImage.MATERIAL_FOLDER_DELETE); + } + public static IconData folder_off() { + return new IconData(FontImage.MATERIAL_FOLDER_OFF); + } + public static IconData folder_open() { + return new IconData(FontImage.MATERIAL_FOLDER_OPEN); + } + public static IconData folder_shared() { + return new IconData(FontImage.MATERIAL_FOLDER_SHARED); + } + public static IconData folder_special() { + return new IconData(FontImage.MATERIAL_FOLDER_SPECIAL); + } + public static IconData folder_zip() { + return new IconData(FontImage.MATERIAL_FOLDER_ZIP); + } + public static IconData follow_the_signs() { + return new IconData(FontImage.MATERIAL_FOLLOW_THE_SIGNS); + } + public static IconData font_download() { + return new IconData(FontImage.MATERIAL_FONT_DOWNLOAD); + } + public static IconData font_download_off() { + return new IconData(FontImage.MATERIAL_FONT_DOWNLOAD_OFF); + } + public static IconData food_bank() { + return new IconData(FontImage.MATERIAL_FOOD_BANK); + } + public static IconData forest() { + return new IconData(FontImage.MATERIAL_FOREST); + } + public static IconData fork_left() { + return new IconData(FontImage.MATERIAL_FORK_LEFT); + } + public static IconData fork_right() { + return new IconData(FontImage.MATERIAL_FORK_RIGHT); + } + public static IconData forklift() { + return new IconData(FontImage.MATERIAL_FORKLIFT); + } + public static IconData format_align_center() { + return new IconData(FontImage.MATERIAL_FORMAT_ALIGN_CENTER); + } + public static IconData format_align_justify() { + return new IconData(FontImage.MATERIAL_FORMAT_ALIGN_JUSTIFY); + } + public static IconData format_align_left() { + return new IconData(FontImage.MATERIAL_FORMAT_ALIGN_LEFT); + } + public static IconData format_align_right() { + return new IconData(FontImage.MATERIAL_FORMAT_ALIGN_RIGHT); + } + public static IconData format_clear() { + return new IconData(FontImage.MATERIAL_FORMAT_CLEAR); + } + public static IconData format_color_fill() { + return new IconData(FontImage.MATERIAL_FORMAT_COLOR_FILL); + } + public static IconData format_color_reset() { + return new IconData(FontImage.MATERIAL_FORMAT_COLOR_RESET); + } + public static IconData format_color_text() { + return new IconData(FontImage.MATERIAL_FORMAT_COLOR_TEXT); + } + public static IconData format_indent_decrease() { + return new IconData(FontImage.MATERIAL_FORMAT_INDENT_DECREASE); + } + public static IconData format_indent_increase() { + return new IconData(FontImage.MATERIAL_FORMAT_INDENT_INCREASE); + } + public static IconData format_line_spacing() { + return new IconData(FontImage.MATERIAL_FORMAT_LINE_SPACING); + } + public static IconData format_list_bulleted() { + return new IconData(FontImage.MATERIAL_FORMAT_LIST_BULLETED); + } + public static IconData format_list_bulleted_add() { + return new IconData(FontImage.MATERIAL_FORMAT_LIST_BULLETED_ADD); + } + public static IconData format_list_numbered() { + return new IconData(FontImage.MATERIAL_FORMAT_LIST_NUMBERED); + } + public static IconData format_list_numbered_rtl() { + return new IconData(FontImage.MATERIAL_FORMAT_LIST_NUMBERED_RTL); + } + public static IconData format_overline() { + return new IconData(FontImage.MATERIAL_FORMAT_OVERLINE); + } + public static IconData format_paint() { + return new IconData(FontImage.MATERIAL_FORMAT_PAINT); + } + public static IconData format_quote() { + return new IconData(FontImage.MATERIAL_FORMAT_QUOTE); + } + public static IconData format_shapes() { + return new IconData(FontImage.MATERIAL_FORMAT_SHAPES); + } + public static IconData format_size() { + return new IconData(FontImage.MATERIAL_FORMAT_SIZE); + } + public static IconData format_strikethrough() { + return new IconData(FontImage.MATERIAL_FORMAT_STRIKETHROUGH); + } + public static IconData format_textdirection_l_to_r() { + return new IconData(FontImage.MATERIAL_FORMAT_TEXTDIRECTION_L_TO_R); + } + public static IconData format_textdirection_r_to_l() { + return new IconData(FontImage.MATERIAL_FORMAT_TEXTDIRECTION_R_TO_L); + } + public static IconData format_underlined() { + return new IconData(FontImage.MATERIAL_FORMAT_UNDERLINED); + } + public static IconData fort() { + return new IconData(FontImage.MATERIAL_FORT); + } + public static IconData forum() { + return new IconData(FontImage.MATERIAL_FORUM); + } + public static IconData forward() { + return new IconData(FontImage.MATERIAL_FORWARD); + } + public static IconData forward_10() { + return new IconData(FontImage.MATERIAL_FORWARD_10); + } + public static IconData forward_30() { + return new IconData(FontImage.MATERIAL_FORWARD_30); + } + public static IconData forward_5() { + return new IconData(FontImage.MATERIAL_FORWARD_5); + } + public static IconData forward_to_inbox() { + return new IconData(FontImage.MATERIAL_FORWARD_TO_INBOX); + } + public static IconData foundation() { + return new IconData(FontImage.MATERIAL_FOUNDATION); + } + public static IconData free_breakfast() { + return new IconData(FontImage.MATERIAL_FREE_BREAKFAST); + } + public static IconData free_cancellation() { + return new IconData(FontImage.MATERIAL_FREE_CANCELLATION); + } + public static IconData front_hand() { + return new IconData(FontImage.MATERIAL_FRONT_HAND); + } + public static IconData front_loader() { + return new IconData(FontImage.MATERIAL_FRONT_LOADER); + } + public static IconData fullscreen_exit() { + return new IconData(FontImage.MATERIAL_FULLSCREEN_EXIT); + } + public static IconData functions() { + return new IconData(FontImage.MATERIAL_FUNCTIONS); + } + public static IconData g_mobiledata() { + return new IconData(FontImage.MATERIAL_G_MOBILEDATA); + } + public static IconData g_translate() { + return new IconData(FontImage.MATERIAL_G_TRANSLATE); + } + public static IconData gamepad() { + return new IconData(FontImage.MATERIAL_GAMEPAD); + } + public static IconData games() { + return new IconData(FontImage.MATERIAL_GAMES); + } + public static IconData garage() { + return new IconData(FontImage.MATERIAL_GARAGE); + } + public static IconData gas_meter() { + return new IconData(FontImage.MATERIAL_GAS_METER); + } + public static IconData gavel() { + return new IconData(FontImage.MATERIAL_GAVEL); + } + public static IconData generating_tokens() { + return new IconData(FontImage.MATERIAL_GENERATING_TOKENS); + } + public static IconData gesture() { + return new IconData(FontImage.MATERIAL_GESTURE); + } + public static IconData get_app() { + return new IconData(FontImage.MATERIAL_GET_APP); + } + public static IconData gif() { + return new IconData(FontImage.MATERIAL_GIF); + } + public static IconData gif_box() { + return new IconData(FontImage.MATERIAL_GIF_BOX); + } + public static IconData girl() { + return new IconData(FontImage.MATERIAL_GIRL); + } + public static IconData gite() { + return new IconData(FontImage.MATERIAL_GITE); + } + public static IconData golf_course() { + return new IconData(FontImage.MATERIAL_GOLF_COURSE); + } + public static IconData gpp_bad() { + return new IconData(FontImage.MATERIAL_GPP_BAD); + } + public static IconData gpp_good() { + return new IconData(FontImage.MATERIAL_GPP_GOOD); + } + public static IconData gpp_maybe() { + return new IconData(FontImage.MATERIAL_GPP_MAYBE); + } + public static IconData gps_fixed() { + return new IconData(FontImage.MATERIAL_GPS_FIXED); + } + public static IconData gps_not_fixed() { + return new IconData(FontImage.MATERIAL_GPS_NOT_FIXED); + } + public static IconData gps_off() { + return new IconData(FontImage.MATERIAL_GPS_OFF); + } + public static IconData grade() { + return new IconData(FontImage.MATERIAL_GRADE); + } + public static IconData gradient() { + return new IconData(FontImage.MATERIAL_GRADIENT); + } + public static IconData grading() { + return new IconData(FontImage.MATERIAL_GRADING); + } + public static IconData grain() { + return new IconData(FontImage.MATERIAL_GRAIN); + } + public static IconData graphic_eq() { + return new IconData(FontImage.MATERIAL_GRAPHIC_EQ); + } + public static IconData grass() { + return new IconData(FontImage.MATERIAL_GRASS); + } + public static IconData grid_3x3() { + return new IconData(FontImage.MATERIAL_GRID_3X3); + } + public static IconData grid_4x4() { + return new IconData(FontImage.MATERIAL_GRID_4X4); + } + public static IconData grid_goldenratio() { + return new IconData(FontImage.MATERIAL_GRID_GOLDENRATIO); + } + public static IconData grid_off() { + return new IconData(FontImage.MATERIAL_GRID_OFF); + } + public static IconData grid_on() { + return new IconData(FontImage.MATERIAL_GRID_ON); + } + public static IconData grid_view() { + return new IconData(FontImage.MATERIAL_GRID_VIEW); + } + public static IconData group() { + return new IconData(FontImage.MATERIAL_GROUP); + } + public static IconData group_add() { + return new IconData(FontImage.MATERIAL_GROUP_ADD); + } + public static IconData group_off() { + return new IconData(FontImage.MATERIAL_GROUP_OFF); + } + public static IconData group_remove() { + return new IconData(FontImage.MATERIAL_GROUP_REMOVE); + } + public static IconData group_work() { + return new IconData(FontImage.MATERIAL_GROUP_WORK); + } + public static IconData groups() { + return new IconData(FontImage.MATERIAL_GROUPS); + } + public static IconData groups_2() { + return new IconData(FontImage.MATERIAL_GROUPS_2); + } + public static IconData groups_3() { + return new IconData(FontImage.MATERIAL_GROUPS_3); + } + public static IconData h_mobiledata() { + return new IconData(FontImage.MATERIAL_H_MOBILEDATA); + } + public static IconData h_plus_mobiledata() { + return new IconData(FontImage.MATERIAL_H_PLUS_MOBILEDATA); + } + public static IconData hail() { + return new IconData(FontImage.MATERIAL_HAIL); + } + public static IconData handshake() { + return new IconData(FontImage.MATERIAL_HANDSHAKE); + } + public static IconData handyman() { + return new IconData(FontImage.MATERIAL_HANDYMAN); + } + public static IconData hardware() { + return new IconData(FontImage.MATERIAL_HARDWARE); + } + public static IconData hd() { + return new IconData(FontImage.MATERIAL_HD); + } + public static IconData hdr_auto() { + return new IconData(FontImage.MATERIAL_HDR_AUTO); + } + public static IconData hdr_auto_select() { + return new IconData(FontImage.MATERIAL_HDR_AUTO_SELECT); + } + public static IconData hdr_enhanced_select() { + return new IconData(FontImage.MATERIAL_HDR_ENHANCED_SELECT); + } + public static IconData hdr_off() { + return new IconData(FontImage.MATERIAL_HDR_OFF); + } + public static IconData hdr_off_select() { + return new IconData(FontImage.MATERIAL_HDR_OFF_SELECT); + } + public static IconData hdr_on() { + return new IconData(FontImage.MATERIAL_HDR_ON); + } + public static IconData hdr_on_select() { + return new IconData(FontImage.MATERIAL_HDR_ON_SELECT); + } + public static IconData hdr_plus() { + return new IconData(FontImage.MATERIAL_HDR_PLUS); + } + public static IconData hdr_strong() { + return new IconData(FontImage.MATERIAL_HDR_STRONG); + } + public static IconData hdr_weak() { + return new IconData(FontImage.MATERIAL_HDR_WEAK); + } + public static IconData headphones() { + return new IconData(FontImage.MATERIAL_HEADPHONES); + } + public static IconData headphones_battery() { + return new IconData(FontImage.MATERIAL_HEADPHONES_BATTERY); + } + public static IconData headset() { + return new IconData(FontImage.MATERIAL_HEADSET); + } + public static IconData headset_mic() { + return new IconData(FontImage.MATERIAL_HEADSET_MIC); + } + public static IconData headset_off() { + return new IconData(FontImage.MATERIAL_HEADSET_OFF); + } + public static IconData healing() { + return new IconData(FontImage.MATERIAL_HEALING); + } + public static IconData health_and_safety() { + return new IconData(FontImage.MATERIAL_HEALTH_AND_SAFETY); + } + public static IconData hearing() { + return new IconData(FontImage.MATERIAL_HEARING); + } + public static IconData hearing_disabled() { + return new IconData(FontImage.MATERIAL_HEARING_DISABLED); + } + public static IconData heart_broken() { + return new IconData(FontImage.MATERIAL_HEART_BROKEN); + } + public static IconData heat_pump() { + return new IconData(FontImage.MATERIAL_HEAT_PUMP); + } + public static IconData height() { + return new IconData(FontImage.MATERIAL_HEIGHT); + } + public static IconData help_center() { + return new IconData(FontImage.MATERIAL_HELP_CENTER); + } + public static IconData help_outline() { + return new IconData(FontImage.MATERIAL_HELP_OUTLINE); + } + public static IconData hevc() { + return new IconData(FontImage.MATERIAL_HEVC); + } + public static IconData hexagon() { + return new IconData(FontImage.MATERIAL_HEXAGON); + } + public static IconData hide_image() { + return new IconData(FontImage.MATERIAL_HIDE_IMAGE); + } + public static IconData hide_source() { + return new IconData(FontImage.MATERIAL_HIDE_SOURCE); + } + public static IconData high_quality() { + return new IconData(FontImage.MATERIAL_HIGH_QUALITY); + } + public static IconData highlight() { + return new IconData(FontImage.MATERIAL_HIGHLIGHT); + } + public static IconData highlight_alt() { + return new IconData(FontImage.MATERIAL_HIGHLIGHT_ALT); + } + public static IconData highlight_off() { + return new IconData(FontImage.MATERIAL_HIGHLIGHT_OFF); + } + public static IconData highlight_remove() { + return new IconData(FontImage.MATERIAL_HIGHLIGHT_REMOVE); + } + public static IconData hiking() { + return new IconData(FontImage.MATERIAL_HIKING); + } + public static IconData history() { + return new IconData(FontImage.MATERIAL_HISTORY); + } + public static IconData history_edu() { + return new IconData(FontImage.MATERIAL_HISTORY_EDU); + } + public static IconData history_toggle_off() { + return new IconData(FontImage.MATERIAL_HISTORY_TOGGLE_OFF); + } + public static IconData hive() { + return new IconData(FontImage.MATERIAL_HIVE); + } + public static IconData hls() { + return new IconData(FontImage.MATERIAL_HLS); + } + public static IconData hls_off() { + return new IconData(FontImage.MATERIAL_HLS_OFF); + } + public static IconData holiday_village() { + return new IconData(FontImage.MATERIAL_HOLIDAY_VILLAGE); + } + public static IconData home_filled() { + return new IconData(FontImage.MATERIAL_HOME_FILLED); + } + public static IconData home_max() { + return new IconData(FontImage.MATERIAL_HOME_MAX); + } + public static IconData home_mini() { + return new IconData(FontImage.MATERIAL_HOME_MINI); + } + public static IconData home_repair_service() { + return new IconData(FontImage.MATERIAL_HOME_REPAIR_SERVICE); + } + public static IconData home_work() { + return new IconData(FontImage.MATERIAL_HOME_WORK); + } + public static IconData horizontal_distribute() { + return new IconData(FontImage.MATERIAL_HORIZONTAL_DISTRIBUTE); + } + public static IconData horizontal_rule() { + return new IconData(FontImage.MATERIAL_HORIZONTAL_RULE); + } + public static IconData horizontal_split() { + return new IconData(FontImage.MATERIAL_HORIZONTAL_SPLIT); + } + public static IconData hot_tub() { + return new IconData(FontImage.MATERIAL_HOT_TUB); + } + public static IconData hotel_class() { + return new IconData(FontImage.MATERIAL_HOTEL_CLASS); + } + public static IconData hourglass_bottom() { + return new IconData(FontImage.MATERIAL_HOURGLASS_BOTTOM); + } + public static IconData hourglass_disabled() { + return new IconData(FontImage.MATERIAL_HOURGLASS_DISABLED); + } + public static IconData hourglass_empty() { + return new IconData(FontImage.MATERIAL_HOURGLASS_EMPTY); + } + public static IconData hourglass_full() { + return new IconData(FontImage.MATERIAL_HOURGLASS_FULL); + } + public static IconData hourglass_top() { + return new IconData(FontImage.MATERIAL_HOURGLASS_TOP); + } + public static IconData house() { + return new IconData(FontImage.MATERIAL_HOUSE); + } + public static IconData house_siding() { + return new IconData(FontImage.MATERIAL_HOUSE_SIDING); + } + public static IconData houseboat() { + return new IconData(FontImage.MATERIAL_HOUSEBOAT); + } + public static IconData how_to_reg() { + return new IconData(FontImage.MATERIAL_HOW_TO_REG); + } + public static IconData how_to_vote() { + return new IconData(FontImage.MATERIAL_HOW_TO_VOTE); + } + public static IconData html() { + return new IconData(FontImage.MATERIAL_HTML); + } + public static IconData http() { + return new IconData(FontImage.MATERIAL_HTTP); + } + public static IconData https() { + return new IconData(FontImage.MATERIAL_HTTPS); + } + public static IconData hub() { + return new IconData(FontImage.MATERIAL_HUB); + } + public static IconData hvac() { + return new IconData(FontImage.MATERIAL_HVAC); + } + public static IconData ice_skating() { + return new IconData(FontImage.MATERIAL_ICE_SKATING); + } + public static IconData icecream() { + return new IconData(FontImage.MATERIAL_ICECREAM); + } + public static IconData image() { + return new IconData(FontImage.MATERIAL_IMAGE); + } + public static IconData image_aspect_ratio() { + return new IconData(FontImage.MATERIAL_IMAGE_ASPECT_RATIO); + } + public static IconData image_not_supported() { + return new IconData(FontImage.MATERIAL_IMAGE_NOT_SUPPORTED); + } + public static IconData image_search() { + return new IconData(FontImage.MATERIAL_IMAGE_SEARCH); + } + public static IconData imagesearch_roller() { + return new IconData(FontImage.MATERIAL_IMAGESEARCH_ROLLER); + } + public static IconData import_contacts() { + return new IconData(FontImage.MATERIAL_IMPORT_CONTACTS); + } + public static IconData import_export() { + return new IconData(FontImage.MATERIAL_IMPORT_EXPORT); + } + public static IconData important_devices() { + return new IconData(FontImage.MATERIAL_IMPORTANT_DEVICES); + } + public static IconData inbox() { + return new IconData(FontImage.MATERIAL_INBOX); + } + public static IconData incomplete_circle() { + return new IconData(FontImage.MATERIAL_INCOMPLETE_CIRCLE); + } + public static IconData indeterminate_check_box() { + return new IconData(FontImage.MATERIAL_INDETERMINATE_CHECK_BOX); + } + public static IconData input() { + return new IconData(FontImage.MATERIAL_INPUT); + } + public static IconData insert_chart() { + return new IconData(FontImage.MATERIAL_INSERT_CHART); + } + public static IconData insert_chart_outlined() { + return new IconData(FontImage.MATERIAL_INSERT_CHART_OUTLINED); + } + public static IconData insert_comment() { + return new IconData(FontImage.MATERIAL_INSERT_COMMENT); + } + public static IconData insert_drive_file() { + return new IconData(FontImage.MATERIAL_INSERT_DRIVE_FILE); + } + public static IconData insert_emoticon() { + return new IconData(FontImage.MATERIAL_INSERT_EMOTICON); + } + public static IconData insert_invitation() { + return new IconData(FontImage.MATERIAL_INSERT_INVITATION); + } + public static IconData insert_link() { + return new IconData(FontImage.MATERIAL_INSERT_LINK); + } + public static IconData insert_page_break() { + return new IconData(FontImage.MATERIAL_INSERT_PAGE_BREAK); + } + public static IconData insert_photo() { + return new IconData(FontImage.MATERIAL_INSERT_PHOTO); + } + public static IconData insights() { + return new IconData(FontImage.MATERIAL_INSIGHTS); + } + public static IconData install_desktop() { + return new IconData(FontImage.MATERIAL_INSTALL_DESKTOP); + } + public static IconData install_mobile() { + return new IconData(FontImage.MATERIAL_INSTALL_MOBILE); + } + public static IconData integration_instructions() { + return new IconData(FontImage.MATERIAL_INTEGRATION_INSTRUCTIONS); + } + public static IconData interests() { + return new IconData(FontImage.MATERIAL_INTERESTS); + } + public static IconData interpreter_mode() { + return new IconData(FontImage.MATERIAL_INTERPRETER_MODE); + } + public static IconData inventory() { + return new IconData(FontImage.MATERIAL_INVENTORY); + } + public static IconData inventory_2() { + return new IconData(FontImage.MATERIAL_INVENTORY_2); + } + public static IconData invert_colors() { + return new IconData(FontImage.MATERIAL_INVERT_COLORS); + } + public static IconData invert_colors_off() { + return new IconData(FontImage.MATERIAL_INVERT_COLORS_OFF); + } + public static IconData invert_colors_on() { + return new IconData(FontImage.MATERIAL_INVERT_COLORS_ON); + } + public static IconData ios_share() { + return new IconData(FontImage.MATERIAL_IOS_SHARE); + } + public static IconData iron() { + return new IconData(FontImage.MATERIAL_IRON); + } + public static IconData iso() { + return new IconData(FontImage.MATERIAL_ISO); + } + public static IconData javascript() { + return new IconData(FontImage.MATERIAL_JAVASCRIPT); + } + public static IconData join_full() { + return new IconData(FontImage.MATERIAL_JOIN_FULL); + } + public static IconData join_inner() { + return new IconData(FontImage.MATERIAL_JOIN_INNER); + } + public static IconData join_left() { + return new IconData(FontImage.MATERIAL_JOIN_LEFT); + } + public static IconData join_right() { + return new IconData(FontImage.MATERIAL_JOIN_RIGHT); + } + public static IconData kayaking() { + return new IconData(FontImage.MATERIAL_KAYAKING); + } + public static IconData kebab_dining() { + return new IconData(FontImage.MATERIAL_KEBAB_DINING); + } + public static IconData key() { + return new IconData(FontImage.MATERIAL_KEY); + } + public static IconData key_off() { + return new IconData(FontImage.MATERIAL_KEY_OFF); + } + public static IconData keyboard() { + return new IconData(FontImage.MATERIAL_KEYBOARD); + } + public static IconData keyboard_alt() { + return new IconData(FontImage.MATERIAL_KEYBOARD_ALT); + } + public static IconData keyboard_arrow_left() { + return new IconData(FontImage.MATERIAL_KEYBOARD_ARROW_LEFT); + } + public static IconData keyboard_arrow_right() { + return new IconData(FontImage.MATERIAL_KEYBOARD_ARROW_RIGHT); + } + public static IconData keyboard_backspace() { + return new IconData(FontImage.MATERIAL_KEYBOARD_BACKSPACE); + } + public static IconData keyboard_capslock() { + return new IconData(FontImage.MATERIAL_KEYBOARD_CAPSLOCK); + } + public static IconData keyboard_command() { + return new IconData(FontImage.MATERIAL_KEYBOARD_COMMAND); + } + public static IconData keyboard_command_key() { + return new IconData(FontImage.MATERIAL_KEYBOARD_COMMAND_KEY); + } + public static IconData keyboard_control() { + return new IconData(FontImage.MATERIAL_KEYBOARD_CONTROL); + } + public static IconData keyboard_control_key() { + return new IconData(FontImage.MATERIAL_KEYBOARD_CONTROL_KEY); + } + public static IconData keyboard_double_arrow_down() { + return new IconData(FontImage.MATERIAL_KEYBOARD_DOUBLE_ARROW_DOWN); + } + public static IconData keyboard_double_arrow_left() { + return new IconData(FontImage.MATERIAL_KEYBOARD_DOUBLE_ARROW_LEFT); + } + public static IconData keyboard_double_arrow_right() { + return new IconData(FontImage.MATERIAL_KEYBOARD_DOUBLE_ARROW_RIGHT); + } + public static IconData keyboard_double_arrow_up() { + return new IconData(FontImage.MATERIAL_KEYBOARD_DOUBLE_ARROW_UP); + } + public static IconData keyboard_hide() { + return new IconData(FontImage.MATERIAL_KEYBOARD_HIDE); + } + public static IconData keyboard_option() { + return new IconData(FontImage.MATERIAL_KEYBOARD_OPTION); + } + public static IconData keyboard_option_key() { + return new IconData(FontImage.MATERIAL_KEYBOARD_OPTION_KEY); + } + public static IconData keyboard_return() { + return new IconData(FontImage.MATERIAL_KEYBOARD_RETURN); + } + public static IconData keyboard_tab() { + return new IconData(FontImage.MATERIAL_KEYBOARD_TAB); + } + public static IconData keyboard_voice() { + return new IconData(FontImage.MATERIAL_KEYBOARD_VOICE); + } + public static IconData king_bed() { + return new IconData(FontImage.MATERIAL_KING_BED); + } + public static IconData kitchen() { + return new IconData(FontImage.MATERIAL_KITCHEN); + } + public static IconData kitesurfing() { + return new IconData(FontImage.MATERIAL_KITESURFING); + } + public static IconData label() { + return new IconData(FontImage.MATERIAL_LABEL); + } + public static IconData label_important() { + return new IconData(FontImage.MATERIAL_LABEL_IMPORTANT); + } + public static IconData label_important_outline() { + return new IconData(FontImage.MATERIAL_LABEL_IMPORTANT_OUTLINE); + } + public static IconData label_off() { + return new IconData(FontImage.MATERIAL_LABEL_OFF); + } + public static IconData label_outline() { + return new IconData(FontImage.MATERIAL_LABEL_OUTLINE); + } + public static IconData lan() { + return new IconData(FontImage.MATERIAL_LAN); + } + public static IconData landscape() { + return new IconData(FontImage.MATERIAL_LANDSCAPE); + } + public static IconData landslide() { + return new IconData(FontImage.MATERIAL_LANDSLIDE); + } + public static IconData language() { + return new IconData(FontImage.MATERIAL_LANGUAGE); + } + public static IconData laptop() { + return new IconData(FontImage.MATERIAL_LAPTOP); + } + public static IconData laptop_chromebook() { + return new IconData(FontImage.MATERIAL_LAPTOP_CHROMEBOOK); + } + public static IconData laptop_mac() { + return new IconData(FontImage.MATERIAL_LAPTOP_MAC); + } + public static IconData laptop_windows() { + return new IconData(FontImage.MATERIAL_LAPTOP_WINDOWS); + } + public static IconData last_page() { + return new IconData(FontImage.MATERIAL_LAST_PAGE); + } + public static IconData launch() { + return new IconData(FontImage.MATERIAL_LAUNCH); + } + public static IconData layers() { + return new IconData(FontImage.MATERIAL_LAYERS); + } + public static IconData layers_clear() { + return new IconData(FontImage.MATERIAL_LAYERS_CLEAR); + } + public static IconData leaderboard() { + return new IconData(FontImage.MATERIAL_LEADERBOARD); + } + public static IconData leak_add() { + return new IconData(FontImage.MATERIAL_LEAK_ADD); + } + public static IconData leak_remove() { + return new IconData(FontImage.MATERIAL_LEAK_REMOVE); + } + public static IconData leave_bags_at_home() { + return new IconData(FontImage.MATERIAL_LEAVE_BAGS_AT_HOME); + } + public static IconData legend_toggle() { + return new IconData(FontImage.MATERIAL_LEGEND_TOGGLE); + } + public static IconData lens() { + return new IconData(FontImage.MATERIAL_LENS); + } + public static IconData lens_blur() { + return new IconData(FontImage.MATERIAL_LENS_BLUR); + } + public static IconData library_add() { + return new IconData(FontImage.MATERIAL_LIBRARY_ADD); + } + public static IconData library_add_check() { + return new IconData(FontImage.MATERIAL_LIBRARY_ADD_CHECK); + } + public static IconData library_music() { + return new IconData(FontImage.MATERIAL_LIBRARY_MUSIC); + } + public static IconData light() { + return new IconData(FontImage.MATERIAL_LIGHT); + } + public static IconData light_mode() { + return new IconData(FontImage.MATERIAL_LIGHT_MODE); + } + public static IconData lightbulb() { + return new IconData(FontImage.MATERIAL_LIGHTBULB); + } + public static IconData lightbulb_circle() { + return new IconData(FontImage.MATERIAL_LIGHTBULB_CIRCLE); + } + public static IconData lightbulb_outline() { + return new IconData(FontImage.MATERIAL_LIGHTBULB_OUTLINE); + } + public static IconData line_axis() { + return new IconData(FontImage.MATERIAL_LINE_AXIS); + } + public static IconData line_style() { + return new IconData(FontImage.MATERIAL_LINE_STYLE); + } + public static IconData line_weight() { + return new IconData(FontImage.MATERIAL_LINE_WEIGHT); + } + public static IconData linear_scale() { + return new IconData(FontImage.MATERIAL_LINEAR_SCALE); + } + public static IconData link_off() { + return new IconData(FontImage.MATERIAL_LINK_OFF); + } + public static IconData linked_camera() { + return new IconData(FontImage.MATERIAL_LINKED_CAMERA); + } + public static IconData liquor() { + return new IconData(FontImage.MATERIAL_LIQUOR); + } + public static IconData list() { + return new IconData(FontImage.MATERIAL_LIST); + } + public static IconData list_alt() { + return new IconData(FontImage.MATERIAL_LIST_ALT); + } + public static IconData live_help() { + return new IconData(FontImage.MATERIAL_LIVE_HELP); + } + public static IconData live_tv() { + return new IconData(FontImage.MATERIAL_LIVE_TV); + } + public static IconData living() { + return new IconData(FontImage.MATERIAL_LIVING); + } + public static IconData local_activity() { + return new IconData(FontImage.MATERIAL_LOCAL_ACTIVITY); + } + public static IconData local_airport() { + return new IconData(FontImage.MATERIAL_LOCAL_AIRPORT); + } + public static IconData local_atm() { + return new IconData(FontImage.MATERIAL_LOCAL_ATM); + } + public static IconData local_attraction() { + return new IconData(FontImage.MATERIAL_LOCAL_ATTRACTION); + } + public static IconData local_bar() { + return new IconData(FontImage.MATERIAL_LOCAL_BAR); + } + public static IconData local_cafe() { + return new IconData(FontImage.MATERIAL_LOCAL_CAFE); + } + public static IconData local_car_wash() { + return new IconData(FontImage.MATERIAL_LOCAL_CAR_WASH); + } + public static IconData local_convenience_store() { + return new IconData(FontImage.MATERIAL_LOCAL_CONVENIENCE_STORE); + } + public static IconData local_dining() { + return new IconData(FontImage.MATERIAL_LOCAL_DINING); + } + public static IconData local_drink() { + return new IconData(FontImage.MATERIAL_LOCAL_DRINK); + } + public static IconData local_fire_department() { + return new IconData(FontImage.MATERIAL_LOCAL_FIRE_DEPARTMENT); + } + public static IconData local_florist() { + return new IconData(FontImage.MATERIAL_LOCAL_FLORIST); + } + public static IconData local_gas_station() { + return new IconData(FontImage.MATERIAL_LOCAL_GAS_STATION); + } + public static IconData local_grocery_store() { + return new IconData(FontImage.MATERIAL_LOCAL_GROCERY_STORE); + } + public static IconData local_hospital() { + return new IconData(FontImage.MATERIAL_LOCAL_HOSPITAL); + } + public static IconData local_hotel() { + return new IconData(FontImage.MATERIAL_LOCAL_HOTEL); + } + public static IconData local_laundry_service() { + return new IconData(FontImage.MATERIAL_LOCAL_LAUNDRY_SERVICE); + } + public static IconData local_library() { + return new IconData(FontImage.MATERIAL_LOCAL_LIBRARY); + } + public static IconData local_mall() { + return new IconData(FontImage.MATERIAL_LOCAL_MALL); + } + public static IconData local_movies() { + return new IconData(FontImage.MATERIAL_LOCAL_MOVIES); + } + public static IconData local_offer() { + return new IconData(FontImage.MATERIAL_LOCAL_OFFER); + } + public static IconData local_parking() { + return new IconData(FontImage.MATERIAL_LOCAL_PARKING); + } + public static IconData local_pharmacy() { + return new IconData(FontImage.MATERIAL_LOCAL_PHARMACY); + } + public static IconData local_phone() { + return new IconData(FontImage.MATERIAL_LOCAL_PHONE); + } + public static IconData local_pizza() { + return new IconData(FontImage.MATERIAL_LOCAL_PIZZA); + } + public static IconData local_play() { + return new IconData(FontImage.MATERIAL_LOCAL_PLAY); + } + public static IconData local_police() { + return new IconData(FontImage.MATERIAL_LOCAL_POLICE); + } + public static IconData local_post_office() { + return new IconData(FontImage.MATERIAL_LOCAL_POST_OFFICE); + } + public static IconData local_print_shop() { + return new IconData(FontImage.MATERIAL_LOCAL_PRINT_SHOP); + } + public static IconData local_printshop() { + return new IconData(FontImage.MATERIAL_LOCAL_PRINTSHOP); + } + public static IconData local_restaurant() { + return new IconData(FontImage.MATERIAL_LOCAL_RESTAURANT); + } + public static IconData local_see() { + return new IconData(FontImage.MATERIAL_LOCAL_SEE); + } + public static IconData local_shipping() { + return new IconData(FontImage.MATERIAL_LOCAL_SHIPPING); + } + public static IconData local_taxi() { + return new IconData(FontImage.MATERIAL_LOCAL_TAXI); + } + public static IconData location_city() { + return new IconData(FontImage.MATERIAL_LOCATION_CITY); + } + public static IconData location_disabled() { + return new IconData(FontImage.MATERIAL_LOCATION_DISABLED); + } + public static IconData location_history() { + return new IconData(FontImage.MATERIAL_LOCATION_HISTORY); + } + public static IconData location_off() { + return new IconData(FontImage.MATERIAL_LOCATION_OFF); + } + public static IconData location_on() { + return new IconData(FontImage.MATERIAL_LOCATION_ON); + } + public static IconData location_pin() { + return new IconData(FontImage.MATERIAL_LOCATION_PIN); + } + public static IconData location_searching() { + return new IconData(FontImage.MATERIAL_LOCATION_SEARCHING); + } + public static IconData lock_clock() { + return new IconData(FontImage.MATERIAL_LOCK_CLOCK); + } + public static IconData lock_open() { + return new IconData(FontImage.MATERIAL_LOCK_OPEN); + } + public static IconData lock_outline() { + return new IconData(FontImage.MATERIAL_LOCK_OUTLINE); + } + public static IconData lock_person() { + return new IconData(FontImage.MATERIAL_LOCK_PERSON); + } + public static IconData lock_reset() { + return new IconData(FontImage.MATERIAL_LOCK_RESET); + } + public static IconData login() { + return new IconData(FontImage.MATERIAL_LOGIN); + } + public static IconData logo_dev() { + return new IconData(FontImage.MATERIAL_LOGO_DEV); + } + public static IconData logout() { + return new IconData(FontImage.MATERIAL_LOGOUT); + } + public static IconData looks() { + return new IconData(FontImage.MATERIAL_LOOKS); + } + public static IconData looks_3() { + return new IconData(FontImage.MATERIAL_LOOKS_3); + } + public static IconData looks_4() { + return new IconData(FontImage.MATERIAL_LOOKS_4); + } + public static IconData looks_5() { + return new IconData(FontImage.MATERIAL_LOOKS_5); + } + public static IconData looks_6() { + return new IconData(FontImage.MATERIAL_LOOKS_6); + } + public static IconData looks_one() { + return new IconData(FontImage.MATERIAL_LOOKS_ONE); + } + public static IconData looks_two() { + return new IconData(FontImage.MATERIAL_LOOKS_TWO); + } + public static IconData loop() { + return new IconData(FontImage.MATERIAL_LOOP); + } + public static IconData loupe() { + return new IconData(FontImage.MATERIAL_LOUPE); + } + public static IconData low_priority() { + return new IconData(FontImage.MATERIAL_LOW_PRIORITY); + } + public static IconData loyalty() { + return new IconData(FontImage.MATERIAL_LOYALTY); + } + public static IconData lte_mobiledata() { + return new IconData(FontImage.MATERIAL_LTE_MOBILEDATA); + } + public static IconData lte_plus_mobiledata() { + return new IconData(FontImage.MATERIAL_LTE_PLUS_MOBILEDATA); + } + public static IconData luggage() { + return new IconData(FontImage.MATERIAL_LUGGAGE); + } + public static IconData lunch_dining() { + return new IconData(FontImage.MATERIAL_LUNCH_DINING); + } + public static IconData lyrics() { + return new IconData(FontImage.MATERIAL_LYRICS); + } + public static IconData macro_off() { + return new IconData(FontImage.MATERIAL_MACRO_OFF); + } + public static IconData mail() { + return new IconData(FontImage.MATERIAL_MAIL); + } + public static IconData mail_lock() { + return new IconData(FontImage.MATERIAL_MAIL_LOCK); + } + public static IconData mail_outline() { + return new IconData(FontImage.MATERIAL_MAIL_OUTLINE); + } + public static IconData male() { + return new IconData(FontImage.MATERIAL_MALE); + } + public static IconData man() { + return new IconData(FontImage.MATERIAL_MAN); + } + public static IconData man_2() { + return new IconData(FontImage.MATERIAL_MAN_2); + } + public static IconData man_3() { + return new IconData(FontImage.MATERIAL_MAN_3); + } + public static IconData man_4() { + return new IconData(FontImage.MATERIAL_MAN_4); + } + public static IconData manage_accounts() { + return new IconData(FontImage.MATERIAL_MANAGE_ACCOUNTS); + } + public static IconData manage_history() { + return new IconData(FontImage.MATERIAL_MANAGE_HISTORY); + } + public static IconData manage_search() { + return new IconData(FontImage.MATERIAL_MANAGE_SEARCH); + } + public static IconData map() { + return new IconData(FontImage.MATERIAL_MAP); + } + public static IconData maps_home_work() { + return new IconData(FontImage.MATERIAL_MAPS_HOME_WORK); + } + public static IconData maps_ugc() { + return new IconData(FontImage.MATERIAL_MAPS_UGC); + } + public static IconData margin() { + return new IconData(FontImage.MATERIAL_MARGIN); + } + public static IconData mark_as_unread() { + return new IconData(FontImage.MATERIAL_MARK_AS_UNREAD); + } + public static IconData mark_chat_read() { + return new IconData(FontImage.MATERIAL_MARK_CHAT_READ); + } + public static IconData mark_chat_unread() { + return new IconData(FontImage.MATERIAL_MARK_CHAT_UNREAD); + } + public static IconData mark_email_read() { + return new IconData(FontImage.MATERIAL_MARK_EMAIL_READ); + } + public static IconData mark_email_unread() { + return new IconData(FontImage.MATERIAL_MARK_EMAIL_UNREAD); + } + public static IconData mark_unread_chat_alt() { + return new IconData(FontImage.MATERIAL_MARK_UNREAD_CHAT_ALT); + } + public static IconData markunread() { + return new IconData(FontImage.MATERIAL_MARKUNREAD); + } + public static IconData markunread_mailbox() { + return new IconData(FontImage.MATERIAL_MARKUNREAD_MAILBOX); + } + public static IconData masks() { + return new IconData(FontImage.MATERIAL_MASKS); + } + public static IconData maximize() { + return new IconData(FontImage.MATERIAL_MAXIMIZE); + } + public static IconData media_bluetooth_off() { + return new IconData(FontImage.MATERIAL_MEDIA_BLUETOOTH_OFF); + } + public static IconData media_bluetooth_on() { + return new IconData(FontImage.MATERIAL_MEDIA_BLUETOOTH_ON); + } + public static IconData mediation() { + return new IconData(FontImage.MATERIAL_MEDIATION); + } + public static IconData medical_information() { + return new IconData(FontImage.MATERIAL_MEDICAL_INFORMATION); + } + public static IconData medical_services() { + return new IconData(FontImage.MATERIAL_MEDICAL_SERVICES); + } + public static IconData medication() { + return new IconData(FontImage.MATERIAL_MEDICATION); + } + public static IconData medication_liquid() { + return new IconData(FontImage.MATERIAL_MEDICATION_LIQUID); + } + public static IconData meeting_room() { + return new IconData(FontImage.MATERIAL_MEETING_ROOM); + } + public static IconData memory() { + return new IconData(FontImage.MATERIAL_MEMORY); + } + public static IconData menu_book() { + return new IconData(FontImage.MATERIAL_MENU_BOOK); + } + public static IconData menu_open() { + return new IconData(FontImage.MATERIAL_MENU_OPEN); + } + public static IconData merge() { + return new IconData(FontImage.MATERIAL_MERGE); + } + public static IconData merge_type() { + return new IconData(FontImage.MATERIAL_MERGE_TYPE); + } + public static IconData message() { + return new IconData(FontImage.MATERIAL_MESSAGE); + } + public static IconData messenger() { + return new IconData(FontImage.MATERIAL_MESSENGER); + } + public static IconData messenger_outline() { + return new IconData(FontImage.MATERIAL_MESSENGER_OUTLINE); + } + public static IconData mic_external_off() { + return new IconData(FontImage.MATERIAL_MIC_EXTERNAL_OFF); + } + public static IconData mic_external_on() { + return new IconData(FontImage.MATERIAL_MIC_EXTERNAL_ON); + } + public static IconData mic_none() { + return new IconData(FontImage.MATERIAL_MIC_NONE); + } + public static IconData mic_off() { + return new IconData(FontImage.MATERIAL_MIC_OFF); + } + public static IconData microwave() { + return new IconData(FontImage.MATERIAL_MICROWAVE); + } + public static IconData military_tech() { + return new IconData(FontImage.MATERIAL_MILITARY_TECH); + } + public static IconData minimize() { + return new IconData(FontImage.MATERIAL_MINIMIZE); + } + public static IconData minor_crash() { + return new IconData(FontImage.MATERIAL_MINOR_CRASH); + } + public static IconData miscellaneous_services() { + return new IconData(FontImage.MATERIAL_MISCELLANEOUS_SERVICES); + } + public static IconData missed_video_call() { + return new IconData(FontImage.MATERIAL_MISSED_VIDEO_CALL); + } + public static IconData mms() { + return new IconData(FontImage.MATERIAL_MMS); + } + public static IconData mobile_friendly() { + return new IconData(FontImage.MATERIAL_MOBILE_FRIENDLY); + } + public static IconData mobile_off() { + return new IconData(FontImage.MATERIAL_MOBILE_OFF); + } + public static IconData mobile_screen_share() { + return new IconData(FontImage.MATERIAL_MOBILE_SCREEN_SHARE); + } + public static IconData mobiledata_off() { + return new IconData(FontImage.MATERIAL_MOBILEDATA_OFF); + } + public static IconData mode() { + return new IconData(FontImage.MATERIAL_MODE); + } + public static IconData mode_comment() { + return new IconData(FontImage.MATERIAL_MODE_COMMENT); + } + public static IconData mode_edit() { + return new IconData(FontImage.MATERIAL_MODE_EDIT); + } + public static IconData mode_edit_outline() { + return new IconData(FontImage.MATERIAL_MODE_EDIT_OUTLINE); + } + public static IconData mode_fan_off() { + return new IconData(FontImage.MATERIAL_MODE_FAN_OFF); + } + public static IconData mode_night() { + return new IconData(FontImage.MATERIAL_MODE_NIGHT); + } + public static IconData mode_of_travel() { + return new IconData(FontImage.MATERIAL_MODE_OF_TRAVEL); + } + public static IconData mode_standby() { + return new IconData(FontImage.MATERIAL_MODE_STANDBY); + } + public static IconData model_training() { + return new IconData(FontImage.MATERIAL_MODEL_TRAINING); + } + public static IconData monetization_on() { + return new IconData(FontImage.MATERIAL_MONETIZATION_ON); + } + public static IconData money() { + return new IconData(FontImage.MATERIAL_MONEY); + } + public static IconData money_off_csred() { + return new IconData(FontImage.MATERIAL_MONEY_OFF_CSRED); + } + public static IconData monitor() { + return new IconData(FontImage.MATERIAL_MONITOR); + } + public static IconData monitor_heart() { + return new IconData(FontImage.MATERIAL_MONITOR_HEART); + } + public static IconData monitor_weight() { + return new IconData(FontImage.MATERIAL_MONITOR_WEIGHT); + } + public static IconData monochrome_photos() { + return new IconData(FontImage.MATERIAL_MONOCHROME_PHOTOS); + } + public static IconData mood() { + return new IconData(FontImage.MATERIAL_MOOD); + } + public static IconData mood_bad() { + return new IconData(FontImage.MATERIAL_MOOD_BAD); + } + public static IconData moped() { + return new IconData(FontImage.MATERIAL_MOPED); + } + public static IconData more() { + return new IconData(FontImage.MATERIAL_MORE); + } + public static IconData more_time() { + return new IconData(FontImage.MATERIAL_MORE_TIME); + } + public static IconData mosque() { + return new IconData(FontImage.MATERIAL_MOSQUE); + } + public static IconData motion_photos_auto() { + return new IconData(FontImage.MATERIAL_MOTION_PHOTOS_AUTO); + } + public static IconData motion_photos_off() { + return new IconData(FontImage.MATERIAL_MOTION_PHOTOS_OFF); + } + public static IconData motion_photos_on() { + return new IconData(FontImage.MATERIAL_MOTION_PHOTOS_ON); + } + public static IconData motion_photos_pause() { + return new IconData(FontImage.MATERIAL_MOTION_PHOTOS_PAUSE); + } + public static IconData motion_photos_paused() { + return new IconData(FontImage.MATERIAL_MOTION_PHOTOS_PAUSED); + } + public static IconData motorcycle() { + return new IconData(FontImage.MATERIAL_MOTORCYCLE); + } + public static IconData mouse() { + return new IconData(FontImage.MATERIAL_MOUSE); + } + public static IconData move_down() { + return new IconData(FontImage.MATERIAL_MOVE_DOWN); + } + public static IconData move_to_inbox() { + return new IconData(FontImage.MATERIAL_MOVE_TO_INBOX); + } + public static IconData move_up() { + return new IconData(FontImage.MATERIAL_MOVE_UP); + } + public static IconData movie() { + return new IconData(FontImage.MATERIAL_MOVIE); + } + public static IconData movie_creation() { + return new IconData(FontImage.MATERIAL_MOVIE_CREATION); + } + public static IconData movie_edit() { + return new IconData(FontImage.MATERIAL_MOVIE_EDIT); + } + public static IconData movie_filter() { + return new IconData(FontImage.MATERIAL_MOVIE_FILTER); + } + public static IconData moving() { + return new IconData(FontImage.MATERIAL_MOVING); + } + public static IconData mp() { + return new IconData(FontImage.MATERIAL_MP); + } + public static IconData multiline_chart() { + return new IconData(FontImage.MATERIAL_MULTILINE_CHART); + } + public static IconData multiple_stop() { + return new IconData(FontImage.MATERIAL_MULTIPLE_STOP); + } + public static IconData multitrack_audio() { + return new IconData(FontImage.MATERIAL_MULTITRACK_AUDIO); + } + public static IconData museum() { + return new IconData(FontImage.MATERIAL_MUSEUM); + } + public static IconData music_note() { + return new IconData(FontImage.MATERIAL_MUSIC_NOTE); + } + public static IconData music_off() { + return new IconData(FontImage.MATERIAL_MUSIC_OFF); + } + public static IconData music_video() { + return new IconData(FontImage.MATERIAL_MUSIC_VIDEO); + } + public static IconData my_library_add() { + return new IconData(FontImage.MATERIAL_MY_LIBRARY_ADD); + } + public static IconData my_library_books() { + return new IconData(FontImage.MATERIAL_MY_LIBRARY_BOOKS); + } + public static IconData my_library_music() { + return new IconData(FontImage.MATERIAL_MY_LIBRARY_MUSIC); + } + public static IconData my_location() { + return new IconData(FontImage.MATERIAL_MY_LOCATION); + } + public static IconData nat() { + return new IconData(FontImage.MATERIAL_NAT); + } + public static IconData nature() { + return new IconData(FontImage.MATERIAL_NATURE); + } + public static IconData nature_people() { + return new IconData(FontImage.MATERIAL_NATURE_PEOPLE); + } + public static IconData navigate_before() { + return new IconData(FontImage.MATERIAL_NAVIGATE_BEFORE); + } + public static IconData navigate_next() { + return new IconData(FontImage.MATERIAL_NAVIGATE_NEXT); + } + public static IconData navigation() { + return new IconData(FontImage.MATERIAL_NAVIGATION); + } + public static IconData near_me() { + return new IconData(FontImage.MATERIAL_NEAR_ME); + } + public static IconData near_me_disabled() { + return new IconData(FontImage.MATERIAL_NEAR_ME_DISABLED); + } + public static IconData nearby_error() { + return new IconData(FontImage.MATERIAL_NEARBY_ERROR); + } + public static IconData nearby_off() { + return new IconData(FontImage.MATERIAL_NEARBY_OFF); + } + public static IconData nest_cam_wired_stand() { + return new IconData(FontImage.MATERIAL_NEST_CAM_WIRED_STAND); + } + public static IconData network_cell() { + return new IconData(FontImage.MATERIAL_NETWORK_CELL); + } + public static IconData network_check() { + return new IconData(FontImage.MATERIAL_NETWORK_CHECK); + } + public static IconData network_locked() { + return new IconData(FontImage.MATERIAL_NETWORK_LOCKED); + } + public static IconData network_ping() { + return new IconData(FontImage.MATERIAL_NETWORK_PING); + } + public static IconData network_wifi() { + return new IconData(FontImage.MATERIAL_NETWORK_WIFI); + } + public static IconData network_wifi_1_bar() { + return new IconData(FontImage.MATERIAL_NETWORK_WIFI_1_BAR); + } + public static IconData network_wifi_2_bar() { + return new IconData(FontImage.MATERIAL_NETWORK_WIFI_2_BAR); + } + public static IconData network_wifi_3_bar() { + return new IconData(FontImage.MATERIAL_NETWORK_WIFI_3_BAR); + } + public static IconData new_label() { + return new IconData(FontImage.MATERIAL_NEW_LABEL); + } + public static IconData new_releases() { + return new IconData(FontImage.MATERIAL_NEW_RELEASES); + } + public static IconData newspaper() { + return new IconData(FontImage.MATERIAL_NEWSPAPER); + } + public static IconData next_plan() { + return new IconData(FontImage.MATERIAL_NEXT_PLAN); + } + public static IconData next_week() { + return new IconData(FontImage.MATERIAL_NEXT_WEEK); + } + public static IconData nfc() { + return new IconData(FontImage.MATERIAL_NFC); + } + public static IconData night_shelter() { + return new IconData(FontImage.MATERIAL_NIGHT_SHELTER); + } + public static IconData nightlife() { + return new IconData(FontImage.MATERIAL_NIGHTLIFE); + } + public static IconData nightlight() { + return new IconData(FontImage.MATERIAL_NIGHTLIGHT); + } + public static IconData nightlight_round() { + return new IconData(FontImage.MATERIAL_NIGHTLIGHT_ROUND); + } + public static IconData nights_stay() { + return new IconData(FontImage.MATERIAL_NIGHTS_STAY); + } + public static IconData no_accounts() { + return new IconData(FontImage.MATERIAL_NO_ACCOUNTS); + } + public static IconData no_adult_content() { + return new IconData(FontImage.MATERIAL_NO_ADULT_CONTENT); + } + public static IconData no_backpack() { + return new IconData(FontImage.MATERIAL_NO_BACKPACK); + } + public static IconData no_cell() { + return new IconData(FontImage.MATERIAL_NO_CELL); + } + public static IconData no_crash() { + return new IconData(FontImage.MATERIAL_NO_CRASH); + } + public static IconData no_drinks() { + return new IconData(FontImage.MATERIAL_NO_DRINKS); + } + public static IconData no_encryption() { + return new IconData(FontImage.MATERIAL_NO_ENCRYPTION); + } + public static IconData no_encryption_gmailerrorred() { + return new IconData(FontImage.MATERIAL_NO_ENCRYPTION_GMAILERRORRED); + } + public static IconData no_flash() { + return new IconData(FontImage.MATERIAL_NO_FLASH); + } + public static IconData no_food() { + return new IconData(FontImage.MATERIAL_NO_FOOD); + } + public static IconData no_luggage() { + return new IconData(FontImage.MATERIAL_NO_LUGGAGE); + } + public static IconData no_meals() { + return new IconData(FontImage.MATERIAL_NO_MEALS); + } + public static IconData no_meals_ouline() { + return new IconData(FontImage.MATERIAL_NO_MEALS_OULINE); + } + public static IconData no_meeting_room() { + return new IconData(FontImage.MATERIAL_NO_MEETING_ROOM); + } + public static IconData no_photography() { + return new IconData(FontImage.MATERIAL_NO_PHOTOGRAPHY); + } + public static IconData no_sim() { + return new IconData(FontImage.MATERIAL_NO_SIM); + } + public static IconData no_stroller() { + return new IconData(FontImage.MATERIAL_NO_STROLLER); + } + public static IconData no_transfer() { + return new IconData(FontImage.MATERIAL_NO_TRANSFER); + } + public static IconData noise_aware() { + return new IconData(FontImage.MATERIAL_NOISE_AWARE); + } + public static IconData noise_control_off() { + return new IconData(FontImage.MATERIAL_NOISE_CONTROL_OFF); + } + public static IconData nordic_walking() { + return new IconData(FontImage.MATERIAL_NORDIC_WALKING); + } + public static IconData north() { + return new IconData(FontImage.MATERIAL_NORTH); + } + public static IconData north_east() { + return new IconData(FontImage.MATERIAL_NORTH_EAST); + } + public static IconData north_west() { + return new IconData(FontImage.MATERIAL_NORTH_WEST); + } + public static IconData not_accessible() { + return new IconData(FontImage.MATERIAL_NOT_ACCESSIBLE); + } + public static IconData not_listed_location() { + return new IconData(FontImage.MATERIAL_NOT_LISTED_LOCATION); + } + public static IconData not_started() { + return new IconData(FontImage.MATERIAL_NOT_STARTED); + } + public static IconData note() { + return new IconData(FontImage.MATERIAL_NOTE); + } + public static IconData note_add() { + return new IconData(FontImage.MATERIAL_NOTE_ADD); + } + public static IconData note_alt() { + return new IconData(FontImage.MATERIAL_NOTE_ALT); + } + public static IconData notes() { + return new IconData(FontImage.MATERIAL_NOTES); + } + public static IconData notification_add() { + return new IconData(FontImage.MATERIAL_NOTIFICATION_ADD); + } + public static IconData notification_important() { + return new IconData(FontImage.MATERIAL_NOTIFICATION_IMPORTANT); + } + public static IconData notifications_active() { + return new IconData(FontImage.MATERIAL_NOTIFICATIONS_ACTIVE); + } + public static IconData notifications_none() { + return new IconData(FontImage.MATERIAL_NOTIFICATIONS_NONE); + } + public static IconData notifications_off() { + return new IconData(FontImage.MATERIAL_NOTIFICATIONS_OFF); + } + public static IconData notifications_on() { + return new IconData(FontImage.MATERIAL_NOTIFICATIONS_ON); + } + public static IconData notifications_paused() { + return new IconData(FontImage.MATERIAL_NOTIFICATIONS_PAUSED); + } + public static IconData now_wallpaper() { + return new IconData(FontImage.MATERIAL_NOW_WALLPAPER); + } + public static IconData now_widgets() { + return new IconData(FontImage.MATERIAL_NOW_WIDGETS); + } + public static IconData numbers() { + return new IconData(FontImage.MATERIAL_NUMBERS); + } + public static IconData offline_bolt() { + return new IconData(FontImage.MATERIAL_OFFLINE_BOLT); + } + public static IconData offline_pin() { + return new IconData(FontImage.MATERIAL_OFFLINE_PIN); + } + public static IconData offline_share() { + return new IconData(FontImage.MATERIAL_OFFLINE_SHARE); + } + public static IconData oil_barrel() { + return new IconData(FontImage.MATERIAL_OIL_BARREL); + } + public static IconData on_device_training() { + return new IconData(FontImage.MATERIAL_ON_DEVICE_TRAINING); + } + public static IconData ondemand_video() { + return new IconData(FontImage.MATERIAL_ONDEMAND_VIDEO); + } + public static IconData online_prediction() { + return new IconData(FontImage.MATERIAL_ONLINE_PREDICTION); + } + public static IconData opacity() { + return new IconData(FontImage.MATERIAL_OPACITY); + } + public static IconData open_in_browser() { + return new IconData(FontImage.MATERIAL_OPEN_IN_BROWSER); + } + public static IconData open_in_full() { + return new IconData(FontImage.MATERIAL_OPEN_IN_FULL); + } + public static IconData open_in_new() { + return new IconData(FontImage.MATERIAL_OPEN_IN_NEW); + } + public static IconData open_in_new_off() { + return new IconData(FontImage.MATERIAL_OPEN_IN_NEW_OFF); + } + public static IconData open_with() { + return new IconData(FontImage.MATERIAL_OPEN_WITH); + } + public static IconData other_houses() { + return new IconData(FontImage.MATERIAL_OTHER_HOUSES); + } + public static IconData outbond() { + return new IconData(FontImage.MATERIAL_OUTBOND); + } + public static IconData outbound() { + return new IconData(FontImage.MATERIAL_OUTBOUND); + } + public static IconData outbox() { + return new IconData(FontImage.MATERIAL_OUTBOX); + } + public static IconData outdoor_grill() { + return new IconData(FontImage.MATERIAL_OUTDOOR_GRILL); + } + public static IconData outgoing_mail() { + return new IconData(FontImage.MATERIAL_OUTGOING_MAIL); + } + public static IconData outlet() { + return new IconData(FontImage.MATERIAL_OUTLET); + } + public static IconData outlined_flag() { + return new IconData(FontImage.MATERIAL_OUTLINED_FLAG); + } + public static IconData output() { + return new IconData(FontImage.MATERIAL_OUTPUT); + } + public static IconData padding() { + return new IconData(FontImage.MATERIAL_PADDING); + } + public static IconData pages() { + return new IconData(FontImage.MATERIAL_PAGES); + } + public static IconData pageview() { + return new IconData(FontImage.MATERIAL_PAGEVIEW); + } + public static IconData paid() { + return new IconData(FontImage.MATERIAL_PAID); + } + public static IconData palette() { + return new IconData(FontImage.MATERIAL_PALETTE); + } + public static IconData pallet() { + return new IconData(FontImage.MATERIAL_PALLET); + } + public static IconData pan_tool() { + return new IconData(FontImage.MATERIAL_PAN_TOOL); + } + public static IconData pan_tool_alt() { + return new IconData(FontImage.MATERIAL_PAN_TOOL_ALT); + } + public static IconData panorama() { + return new IconData(FontImage.MATERIAL_PANORAMA); + } + public static IconData panorama_fish_eye() { + return new IconData(FontImage.MATERIAL_PANORAMA_FISH_EYE); + } + public static IconData panorama_fisheye() { + return new IconData(FontImage.MATERIAL_PANORAMA_FISHEYE); + } + public static IconData panorama_horizontal() { + return new IconData(FontImage.MATERIAL_PANORAMA_HORIZONTAL); + } + public static IconData panorama_horizontal_select() { + return new IconData(FontImage.MATERIAL_PANORAMA_HORIZONTAL_SELECT); + } + public static IconData panorama_photosphere() { + return new IconData(FontImage.MATERIAL_PANORAMA_PHOTOSPHERE); + } + public static IconData panorama_photosphere_select() { + return new IconData(FontImage.MATERIAL_PANORAMA_PHOTOSPHERE_SELECT); + } + public static IconData panorama_vertical() { + return new IconData(FontImage.MATERIAL_PANORAMA_VERTICAL); + } + public static IconData panorama_vertical_select() { + return new IconData(FontImage.MATERIAL_PANORAMA_VERTICAL_SELECT); + } + public static IconData panorama_wide_angle() { + return new IconData(FontImage.MATERIAL_PANORAMA_WIDE_ANGLE); + } + public static IconData panorama_wide_angle_select() { + return new IconData(FontImage.MATERIAL_PANORAMA_WIDE_ANGLE_SELECT); + } + public static IconData paragliding() { + return new IconData(FontImage.MATERIAL_PARAGLIDING); + } + public static IconData park() { + return new IconData(FontImage.MATERIAL_PARK); + } + public static IconData party_mode() { + return new IconData(FontImage.MATERIAL_PARTY_MODE); + } + public static IconData password() { + return new IconData(FontImage.MATERIAL_PASSWORD); + } + public static IconData pattern() { + return new IconData(FontImage.MATERIAL_PATTERN); + } + public static IconData pause() { + return new IconData(FontImage.MATERIAL_PAUSE); + } + public static IconData pause_circle() { + return new IconData(FontImage.MATERIAL_PAUSE_CIRCLE); + } + public static IconData pause_circle_filled() { + return new IconData(FontImage.MATERIAL_PAUSE_CIRCLE_FILLED); + } + public static IconData pause_circle_outline() { + return new IconData(FontImage.MATERIAL_PAUSE_CIRCLE_OUTLINE); + } + public static IconData pause_presentation() { + return new IconData(FontImage.MATERIAL_PAUSE_PRESENTATION); + } + public static IconData payment() { + return new IconData(FontImage.MATERIAL_PAYMENT); + } + public static IconData payments() { + return new IconData(FontImage.MATERIAL_PAYMENTS); + } + public static IconData paypal() { + return new IconData(FontImage.MATERIAL_PAYPAL); + } + public static IconData pedal_bike() { + return new IconData(FontImage.MATERIAL_PEDAL_BIKE); + } + public static IconData pending() { + return new IconData(FontImage.MATERIAL_PENDING); + } + public static IconData pending_actions() { + return new IconData(FontImage.MATERIAL_PENDING_ACTIONS); + } + public static IconData pentagon() { + return new IconData(FontImage.MATERIAL_PENTAGON); + } + public static IconData people() { + return new IconData(FontImage.MATERIAL_PEOPLE); + } + public static IconData people_alt() { + return new IconData(FontImage.MATERIAL_PEOPLE_ALT); + } + public static IconData people_outline() { + return new IconData(FontImage.MATERIAL_PEOPLE_OUTLINE); + } + public static IconData percent() { + return new IconData(FontImage.MATERIAL_PERCENT); + } + public static IconData perm_camera_mic() { + return new IconData(FontImage.MATERIAL_PERM_CAMERA_MIC); + } + public static IconData perm_contact_cal() { + return new IconData(FontImage.MATERIAL_PERM_CONTACT_CAL); + } + public static IconData perm_contact_calendar() { + return new IconData(FontImage.MATERIAL_PERM_CONTACT_CALENDAR); + } + public static IconData perm_data_setting() { + return new IconData(FontImage.MATERIAL_PERM_DATA_SETTING); + } + public static IconData perm_device_info() { + return new IconData(FontImage.MATERIAL_PERM_DEVICE_INFO); + } + public static IconData perm_device_information() { + return new IconData(FontImage.MATERIAL_PERM_DEVICE_INFORMATION); + } + public static IconData perm_identity() { + return new IconData(FontImage.MATERIAL_PERM_IDENTITY); + } + public static IconData perm_media() { + return new IconData(FontImage.MATERIAL_PERM_MEDIA); + } + public static IconData perm_phone_msg() { + return new IconData(FontImage.MATERIAL_PERM_PHONE_MSG); + } + public static IconData perm_scan_wifi() { + return new IconData(FontImage.MATERIAL_PERM_SCAN_WIFI); + } + public static IconData person_2() { + return new IconData(FontImage.MATERIAL_PERSON_2); + } + public static IconData person_3() { + return new IconData(FontImage.MATERIAL_PERSON_3); + } + public static IconData person_4() { + return new IconData(FontImage.MATERIAL_PERSON_4); + } + public static IconData person_add_alt() { + return new IconData(FontImage.MATERIAL_PERSON_ADD_ALT); + } + public static IconData person_add_alt_1() { + return new IconData(FontImage.MATERIAL_PERSON_ADD_ALT_1); + } + public static IconData person_add_disabled() { + return new IconData(FontImage.MATERIAL_PERSON_ADD_DISABLED); + } + public static IconData person_off() { + return new IconData(FontImage.MATERIAL_PERSON_OFF); + } + public static IconData person_outline() { + return new IconData(FontImage.MATERIAL_PERSON_OUTLINE); + } + public static IconData person_pin() { + return new IconData(FontImage.MATERIAL_PERSON_PIN); + } + public static IconData person_pin_circle() { + return new IconData(FontImage.MATERIAL_PERSON_PIN_CIRCLE); + } + public static IconData person_remove() { + return new IconData(FontImage.MATERIAL_PERSON_REMOVE); + } + public static IconData person_remove_alt_1() { + return new IconData(FontImage.MATERIAL_PERSON_REMOVE_ALT_1); + } + public static IconData person_search() { + return new IconData(FontImage.MATERIAL_PERSON_SEARCH); + } + public static IconData personal_injury() { + return new IconData(FontImage.MATERIAL_PERSONAL_INJURY); + } + public static IconData personal_video() { + return new IconData(FontImage.MATERIAL_PERSONAL_VIDEO); + } + public static IconData pest_control() { + return new IconData(FontImage.MATERIAL_PEST_CONTROL); + } + public static IconData pest_control_rodent() { + return new IconData(FontImage.MATERIAL_PEST_CONTROL_RODENT); + } + public static IconData pets() { + return new IconData(FontImage.MATERIAL_PETS); + } + public static IconData phishing() { + return new IconData(FontImage.MATERIAL_PHISHING); + } + public static IconData phone_android() { + return new IconData(FontImage.MATERIAL_PHONE_ANDROID); + } + public static IconData phone_bluetooth_speaker() { + return new IconData(FontImage.MATERIAL_PHONE_BLUETOOTH_SPEAKER); + } + public static IconData phone_callback() { + return new IconData(FontImage.MATERIAL_PHONE_CALLBACK); + } + public static IconData phone_disabled() { + return new IconData(FontImage.MATERIAL_PHONE_DISABLED); + } + public static IconData phone_enabled() { + return new IconData(FontImage.MATERIAL_PHONE_ENABLED); + } + public static IconData phone_forwarded() { + return new IconData(FontImage.MATERIAL_PHONE_FORWARDED); + } + public static IconData phone_in_talk() { + return new IconData(FontImage.MATERIAL_PHONE_IN_TALK); + } + public static IconData phone_iphone() { + return new IconData(FontImage.MATERIAL_PHONE_IPHONE); + } + public static IconData phone_locked() { + return new IconData(FontImage.MATERIAL_PHONE_LOCKED); + } + public static IconData phone_missed() { + return new IconData(FontImage.MATERIAL_PHONE_MISSED); + } + public static IconData phone_paused() { + return new IconData(FontImage.MATERIAL_PHONE_PAUSED); + } + public static IconData phonelink() { + return new IconData(FontImage.MATERIAL_PHONELINK); + } + public static IconData phonelink_erase() { + return new IconData(FontImage.MATERIAL_PHONELINK_ERASE); + } + public static IconData phonelink_lock() { + return new IconData(FontImage.MATERIAL_PHONELINK_LOCK); + } + public static IconData phonelink_off() { + return new IconData(FontImage.MATERIAL_PHONELINK_OFF); + } + public static IconData phonelink_ring() { + return new IconData(FontImage.MATERIAL_PHONELINK_RING); + } + public static IconData phonelink_setup() { + return new IconData(FontImage.MATERIAL_PHONELINK_SETUP); + } + public static IconData photo_album() { + return new IconData(FontImage.MATERIAL_PHOTO_ALBUM); + } + public static IconData photo_camera() { + return new IconData(FontImage.MATERIAL_PHOTO_CAMERA); + } + public static IconData photo_camera_back() { + return new IconData(FontImage.MATERIAL_PHOTO_CAMERA_BACK); + } + public static IconData photo_camera_front() { + return new IconData(FontImage.MATERIAL_PHOTO_CAMERA_FRONT); + } + public static IconData photo_filter() { + return new IconData(FontImage.MATERIAL_PHOTO_FILTER); + } + public static IconData photo_size_select_actual() { + return new IconData(FontImage.MATERIAL_PHOTO_SIZE_SELECT_ACTUAL); + } + public static IconData photo_size_select_large() { + return new IconData(FontImage.MATERIAL_PHOTO_SIZE_SELECT_LARGE); + } + public static IconData photo_size_select_small() { + return new IconData(FontImage.MATERIAL_PHOTO_SIZE_SELECT_SMALL); + } + public static IconData php() { + return new IconData(FontImage.MATERIAL_PHP); + } + public static IconData piano() { + return new IconData(FontImage.MATERIAL_PIANO); + } + public static IconData piano_off() { + return new IconData(FontImage.MATERIAL_PIANO_OFF); + } + public static IconData picture_as_pdf() { + return new IconData(FontImage.MATERIAL_PICTURE_AS_PDF); + } + public static IconData picture_in_picture() { + return new IconData(FontImage.MATERIAL_PICTURE_IN_PICTURE); + } + public static IconData picture_in_picture_alt() { + return new IconData(FontImage.MATERIAL_PICTURE_IN_PICTURE_ALT); + } + public static IconData pie_chart_outline() { + return new IconData(FontImage.MATERIAL_PIE_CHART_OUTLINE); + } + public static IconData pie_chart_outlined() { + return new IconData(FontImage.MATERIAL_PIE_CHART_OUTLINED); + } + public static IconData pin() { + return new IconData(FontImage.MATERIAL_PIN); + } + public static IconData pin_drop() { + return new IconData(FontImage.MATERIAL_PIN_DROP); + } + public static IconData pin_end() { + return new IconData(FontImage.MATERIAL_PIN_END); + } + public static IconData pin_invoke() { + return new IconData(FontImage.MATERIAL_PIN_INVOKE); + } + public static IconData pinch() { + return new IconData(FontImage.MATERIAL_PINCH); + } + public static IconData pivot_table_chart() { + return new IconData(FontImage.MATERIAL_PIVOT_TABLE_CHART); + } + public static IconData pix() { + return new IconData(FontImage.MATERIAL_PIX); + } + public static IconData plagiarism() { + return new IconData(FontImage.MATERIAL_PLAGIARISM); + } + public static IconData play_arrow() { + return new IconData(FontImage.MATERIAL_PLAY_ARROW); + } + public static IconData play_circle() { + return new IconData(FontImage.MATERIAL_PLAY_CIRCLE); + } + public static IconData play_circle_fill() { + return new IconData(FontImage.MATERIAL_PLAY_CIRCLE_FILL); + } + public static IconData play_circle_filled() { + return new IconData(FontImage.MATERIAL_PLAY_CIRCLE_FILLED); + } + public static IconData play_circle_outline() { + return new IconData(FontImage.MATERIAL_PLAY_CIRCLE_OUTLINE); + } + public static IconData play_disabled() { + return new IconData(FontImage.MATERIAL_PLAY_DISABLED); + } + public static IconData play_for_work() { + return new IconData(FontImage.MATERIAL_PLAY_FOR_WORK); + } + public static IconData play_lesson() { + return new IconData(FontImage.MATERIAL_PLAY_LESSON); + } + public static IconData playlist_add() { + return new IconData(FontImage.MATERIAL_PLAYLIST_ADD); + } + public static IconData playlist_add_check() { + return new IconData(FontImage.MATERIAL_PLAYLIST_ADD_CHECK); + } + public static IconData playlist_add_check_circle() { + return new IconData(FontImage.MATERIAL_PLAYLIST_ADD_CHECK_CIRCLE); + } + public static IconData playlist_add_circle() { + return new IconData(FontImage.MATERIAL_PLAYLIST_ADD_CIRCLE); + } + public static IconData playlist_play() { + return new IconData(FontImage.MATERIAL_PLAYLIST_PLAY); + } + public static IconData playlist_remove() { + return new IconData(FontImage.MATERIAL_PLAYLIST_REMOVE); + } + public static IconData plumbing() { + return new IconData(FontImage.MATERIAL_PLUMBING); + } + public static IconData plus_one() { + return new IconData(FontImage.MATERIAL_PLUS_ONE); + } + public static IconData podcasts() { + return new IconData(FontImage.MATERIAL_PODCASTS); + } + public static IconData point_of_sale() { + return new IconData(FontImage.MATERIAL_POINT_OF_SALE); + } + public static IconData policy() { + return new IconData(FontImage.MATERIAL_POLICY); + } + public static IconData poll() { + return new IconData(FontImage.MATERIAL_POLL); + } + public static IconData polyline() { + return new IconData(FontImage.MATERIAL_POLYLINE); + } + public static IconData polymer() { + return new IconData(FontImage.MATERIAL_POLYMER); + } + public static IconData pool() { + return new IconData(FontImage.MATERIAL_POOL); + } + public static IconData portable_wifi_off() { + return new IconData(FontImage.MATERIAL_PORTABLE_WIFI_OFF); + } + public static IconData portrait() { + return new IconData(FontImage.MATERIAL_PORTRAIT); + } + public static IconData post_add() { + return new IconData(FontImage.MATERIAL_POST_ADD); + } + public static IconData power() { + return new IconData(FontImage.MATERIAL_POWER); + } + public static IconData power_input() { + return new IconData(FontImage.MATERIAL_POWER_INPUT); + } + public static IconData power_off() { + return new IconData(FontImage.MATERIAL_POWER_OFF); + } + public static IconData power_settings_new() { + return new IconData(FontImage.MATERIAL_POWER_SETTINGS_NEW); + } + public static IconData precision_manufacturing() { + return new IconData(FontImage.MATERIAL_PRECISION_MANUFACTURING); + } + public static IconData pregnant_woman() { + return new IconData(FontImage.MATERIAL_PREGNANT_WOMAN); + } + public static IconData present_to_all() { + return new IconData(FontImage.MATERIAL_PRESENT_TO_ALL); + } + public static IconData preview() { + return new IconData(FontImage.MATERIAL_PREVIEW); + } + public static IconData price_change() { + return new IconData(FontImage.MATERIAL_PRICE_CHANGE); + } + public static IconData price_check() { + return new IconData(FontImage.MATERIAL_PRICE_CHECK); + } + public static IconData print() { + return new IconData(FontImage.MATERIAL_PRINT); + } + public static IconData print_disabled() { + return new IconData(FontImage.MATERIAL_PRINT_DISABLED); + } + public static IconData priority_high() { + return new IconData(FontImage.MATERIAL_PRIORITY_HIGH); + } + public static IconData privacy_tip() { + return new IconData(FontImage.MATERIAL_PRIVACY_TIP); + } + public static IconData private_connectivity() { + return new IconData(FontImage.MATERIAL_PRIVATE_CONNECTIVITY); + } + public static IconData production_quantity_limits() { + return new IconData(FontImage.MATERIAL_PRODUCTION_QUANTITY_LIMITS); + } + public static IconData propane() { + return new IconData(FontImage.MATERIAL_PROPANE); + } + public static IconData propane_tank() { + return new IconData(FontImage.MATERIAL_PROPANE_TANK); + } + public static IconData psychology() { + return new IconData(FontImage.MATERIAL_PSYCHOLOGY); + } + public static IconData psychology_alt() { + return new IconData(FontImage.MATERIAL_PSYCHOLOGY_ALT); + } + public static IconData public_() { + return new IconData(FontImage.MATERIAL_PUBLIC); + } + public static IconData public_off() { + return new IconData(FontImage.MATERIAL_PUBLIC_OFF); + } + public static IconData publish() { + return new IconData(FontImage.MATERIAL_PUBLISH); + } + public static IconData published_with_changes() { + return new IconData(FontImage.MATERIAL_PUBLISHED_WITH_CHANGES); + } + public static IconData punch_clock() { + return new IconData(FontImage.MATERIAL_PUNCH_CLOCK); + } + public static IconData push_pin() { + return new IconData(FontImage.MATERIAL_PUSH_PIN); + } + public static IconData qr_code() { + return new IconData(FontImage.MATERIAL_QR_CODE); + } + public static IconData qr_code_2() { + return new IconData(FontImage.MATERIAL_QR_CODE_2); + } + public static IconData qr_code_scanner() { + return new IconData(FontImage.MATERIAL_QR_CODE_SCANNER); + } + public static IconData query_builder() { + return new IconData(FontImage.MATERIAL_QUERY_BUILDER); + } + public static IconData query_stats() { + return new IconData(FontImage.MATERIAL_QUERY_STATS); + } + public static IconData question_answer() { + return new IconData(FontImage.MATERIAL_QUESTION_ANSWER); + } + public static IconData question_mark() { + return new IconData(FontImage.MATERIAL_QUESTION_MARK); + } + public static IconData queue() { + return new IconData(FontImage.MATERIAL_QUEUE); + } + public static IconData queue_music() { + return new IconData(FontImage.MATERIAL_QUEUE_MUSIC); + } + public static IconData queue_play_next() { + return new IconData(FontImage.MATERIAL_QUEUE_PLAY_NEXT); + } + public static IconData quick_contacts_dialer() { + return new IconData(FontImage.MATERIAL_QUICK_CONTACTS_DIALER); + } + public static IconData quick_contacts_mail() { + return new IconData(FontImage.MATERIAL_QUICK_CONTACTS_MAIL); + } + public static IconData quickreply() { + return new IconData(FontImage.MATERIAL_QUICKREPLY); + } + public static IconData quiz() { + return new IconData(FontImage.MATERIAL_QUIZ); + } + public static IconData quora() { + return new IconData(FontImage.MATERIAL_QUORA); + } + public static IconData r_mobiledata() { + return new IconData(FontImage.MATERIAL_R_MOBILEDATA); + } + public static IconData radar() { + return new IconData(FontImage.MATERIAL_RADAR); + } + public static IconData radio() { + return new IconData(FontImage.MATERIAL_RADIO); + } + public static IconData radio_button_checked() { + return new IconData(FontImage.MATERIAL_RADIO_BUTTON_CHECKED); + } + public static IconData radio_button_off() { + return new IconData(FontImage.MATERIAL_RADIO_BUTTON_OFF); + } + public static IconData radio_button_on() { + return new IconData(FontImage.MATERIAL_RADIO_BUTTON_ON); + } + public static IconData radio_button_unchecked() { + return new IconData(FontImage.MATERIAL_RADIO_BUTTON_UNCHECKED); + } + public static IconData railway_alert() { + return new IconData(FontImage.MATERIAL_RAILWAY_ALERT); + } + public static IconData ramen_dining() { + return new IconData(FontImage.MATERIAL_RAMEN_DINING); + } + public static IconData ramp_left() { + return new IconData(FontImage.MATERIAL_RAMP_LEFT); + } + public static IconData ramp_right() { + return new IconData(FontImage.MATERIAL_RAMP_RIGHT); + } + public static IconData rate_review() { + return new IconData(FontImage.MATERIAL_RATE_REVIEW); + } + public static IconData raw_off() { + return new IconData(FontImage.MATERIAL_RAW_OFF); + } + public static IconData raw_on() { + return new IconData(FontImage.MATERIAL_RAW_ON); + } + public static IconData read_more() { + return new IconData(FontImage.MATERIAL_READ_MORE); + } + public static IconData real_estate_agent() { + return new IconData(FontImage.MATERIAL_REAL_ESTATE_AGENT); + } + public static IconData rebase_edit() { + return new IconData(FontImage.MATERIAL_REBASE_EDIT); + } + public static IconData receipt() { + return new IconData(FontImage.MATERIAL_RECEIPT); + } + public static IconData receipt_long() { + return new IconData(FontImage.MATERIAL_RECEIPT_LONG); + } + public static IconData recent_actors() { + return new IconData(FontImage.MATERIAL_RECENT_ACTORS); + } + public static IconData recommend() { + return new IconData(FontImage.MATERIAL_RECOMMEND); + } + public static IconData record_voice_over() { + return new IconData(FontImage.MATERIAL_RECORD_VOICE_OVER); + } + public static IconData rectangle() { + return new IconData(FontImage.MATERIAL_RECTANGLE); + } + public static IconData recycling() { + return new IconData(FontImage.MATERIAL_RECYCLING); + } + public static IconData reddit() { + return new IconData(FontImage.MATERIAL_REDDIT); + } + public static IconData redeem() { + return new IconData(FontImage.MATERIAL_REDEEM); + } + public static IconData redo() { + return new IconData(FontImage.MATERIAL_REDO); + } + public static IconData reduce_capacity() { + return new IconData(FontImage.MATERIAL_REDUCE_CAPACITY); + } + public static IconData remember_me() { + return new IconData(FontImage.MATERIAL_REMEMBER_ME); + } + public static IconData remove_circle() { + return new IconData(FontImage.MATERIAL_REMOVE_CIRCLE); + } + public static IconData remove_done() { + return new IconData(FontImage.MATERIAL_REMOVE_DONE); + } + public static IconData remove_from_queue() { + return new IconData(FontImage.MATERIAL_REMOVE_FROM_QUEUE); + } + public static IconData remove_moderator() { + return new IconData(FontImage.MATERIAL_REMOVE_MODERATOR); + } + public static IconData remove_red_eye() { + return new IconData(FontImage.MATERIAL_REMOVE_RED_EYE); + } + public static IconData remove_road() { + return new IconData(FontImage.MATERIAL_REMOVE_ROAD); + } + public static IconData remove_shopping_cart() { + return new IconData(FontImage.MATERIAL_REMOVE_SHOPPING_CART); + } + public static IconData reorder() { + return new IconData(FontImage.MATERIAL_REORDER); + } + public static IconData repartition() { + return new IconData(FontImage.MATERIAL_REPARTITION); + } + public static IconData repeat() { + return new IconData(FontImage.MATERIAL_REPEAT); + } + public static IconData repeat_on() { + return new IconData(FontImage.MATERIAL_REPEAT_ON); + } + public static IconData repeat_one() { + return new IconData(FontImage.MATERIAL_REPEAT_ONE); + } + public static IconData repeat_one_on() { + return new IconData(FontImage.MATERIAL_REPEAT_ONE_ON); + } + public static IconData replay_10() { + return new IconData(FontImage.MATERIAL_REPLAY_10); + } + public static IconData replay_30() { + return new IconData(FontImage.MATERIAL_REPLAY_30); + } + public static IconData replay_5() { + return new IconData(FontImage.MATERIAL_REPLAY_5); + } + public static IconData replay_circle_filled() { + return new IconData(FontImage.MATERIAL_REPLAY_CIRCLE_FILLED); + } + public static IconData reply() { + return new IconData(FontImage.MATERIAL_REPLY); + } + public static IconData report() { + return new IconData(FontImage.MATERIAL_REPORT); + } + public static IconData report_gmailerrorred() { + return new IconData(FontImage.MATERIAL_REPORT_GMAILERRORRED); + } + public static IconData report_off() { + return new IconData(FontImage.MATERIAL_REPORT_OFF); + } + public static IconData report_problem() { + return new IconData(FontImage.MATERIAL_REPORT_PROBLEM); + } + public static IconData request_page() { + return new IconData(FontImage.MATERIAL_REQUEST_PAGE); + } + public static IconData request_quote() { + return new IconData(FontImage.MATERIAL_REQUEST_QUOTE); + } + public static IconData reset_tv() { + return new IconData(FontImage.MATERIAL_RESET_TV); + } + public static IconData restart_alt() { + return new IconData(FontImage.MATERIAL_RESTART_ALT); + } + public static IconData restaurant() { + return new IconData(FontImage.MATERIAL_RESTAURANT); + } + public static IconData restore() { + return new IconData(FontImage.MATERIAL_RESTORE); + } + public static IconData restore_from_trash() { + return new IconData(FontImage.MATERIAL_RESTORE_FROM_TRASH); + } + public static IconData restore_page() { + return new IconData(FontImage.MATERIAL_RESTORE_PAGE); + } + public static IconData reviews() { + return new IconData(FontImage.MATERIAL_REVIEWS); + } + public static IconData rice_bowl() { + return new IconData(FontImage.MATERIAL_RICE_BOWL); + } + public static IconData ring_volume() { + return new IconData(FontImage.MATERIAL_RING_VOLUME); + } + public static IconData rocket() { + return new IconData(FontImage.MATERIAL_ROCKET); + } + public static IconData rocket_launch() { + return new IconData(FontImage.MATERIAL_ROCKET_LAUNCH); + } + public static IconData roller_shades() { + return new IconData(FontImage.MATERIAL_ROLLER_SHADES); + } + public static IconData roller_shades_closed() { + return new IconData(FontImage.MATERIAL_ROLLER_SHADES_CLOSED); + } + public static IconData roller_skating() { + return new IconData(FontImage.MATERIAL_ROLLER_SKATING); + } + public static IconData roofing() { + return new IconData(FontImage.MATERIAL_ROOFING); + } + public static IconData room() { + return new IconData(FontImage.MATERIAL_ROOM); + } + public static IconData room_preferences() { + return new IconData(FontImage.MATERIAL_ROOM_PREFERENCES); + } + public static IconData room_service() { + return new IconData(FontImage.MATERIAL_ROOM_SERVICE); + } + public static IconData rotate_90_degrees_ccw() { + return new IconData(FontImage.MATERIAL_ROTATE_90_DEGREES_CCW); + } + public static IconData rotate_90_degrees_cw() { + return new IconData(FontImage.MATERIAL_ROTATE_90_DEGREES_CW); + } + public static IconData rotate_left() { + return new IconData(FontImage.MATERIAL_ROTATE_LEFT); + } + public static IconData rotate_right() { + return new IconData(FontImage.MATERIAL_ROTATE_RIGHT); + } + public static IconData roundabout_left() { + return new IconData(FontImage.MATERIAL_ROUNDABOUT_LEFT); + } + public static IconData roundabout_right() { + return new IconData(FontImage.MATERIAL_ROUNDABOUT_RIGHT); + } + public static IconData rounded_corner() { + return new IconData(FontImage.MATERIAL_ROUNDED_CORNER); + } + public static IconData route() { + return new IconData(FontImage.MATERIAL_ROUTE); + } + public static IconData router() { + return new IconData(FontImage.MATERIAL_ROUTER); + } + public static IconData rowing() { + return new IconData(FontImage.MATERIAL_ROWING); + } + public static IconData rss_feed() { + return new IconData(FontImage.MATERIAL_RSS_FEED); + } + public static IconData rsvp() { + return new IconData(FontImage.MATERIAL_RSVP); + } + public static IconData rtt() { + return new IconData(FontImage.MATERIAL_RTT); + } + public static IconData rule() { + return new IconData(FontImage.MATERIAL_RULE); + } + public static IconData rule_folder() { + return new IconData(FontImage.MATERIAL_RULE_FOLDER); + } + public static IconData run_circle() { + return new IconData(FontImage.MATERIAL_RUN_CIRCLE); + } + public static IconData running_with_errors() { + return new IconData(FontImage.MATERIAL_RUNNING_WITH_ERRORS); + } + public static IconData rv_hookup() { + return new IconData(FontImage.MATERIAL_RV_HOOKUP); + } + public static IconData safety_check() { + return new IconData(FontImage.MATERIAL_SAFETY_CHECK); + } + public static IconData safety_divider() { + return new IconData(FontImage.MATERIAL_SAFETY_DIVIDER); + } + public static IconData sailing() { + return new IconData(FontImage.MATERIAL_SAILING); + } + public static IconData sanitizer() { + return new IconData(FontImage.MATERIAL_SANITIZER); + } + public static IconData satellite() { + return new IconData(FontImage.MATERIAL_SATELLITE); + } + public static IconData satellite_alt() { + return new IconData(FontImage.MATERIAL_SATELLITE_ALT); + } + public static IconData save() { + return new IconData(FontImage.MATERIAL_SAVE); + } + public static IconData save_alt() { + return new IconData(FontImage.MATERIAL_SAVE_ALT); + } + public static IconData save_as() { + return new IconData(FontImage.MATERIAL_SAVE_AS); + } + public static IconData saved_search() { + return new IconData(FontImage.MATERIAL_SAVED_SEARCH); + } + public static IconData savings() { + return new IconData(FontImage.MATERIAL_SAVINGS); + } + public static IconData scale() { + return new IconData(FontImage.MATERIAL_SCALE); + } + public static IconData scanner() { + return new IconData(FontImage.MATERIAL_SCANNER); + } + public static IconData scatter_plot() { + return new IconData(FontImage.MATERIAL_SCATTER_PLOT); + } + public static IconData schedule() { + return new IconData(FontImage.MATERIAL_SCHEDULE); + } + public static IconData schedule_send() { + return new IconData(FontImage.MATERIAL_SCHEDULE_SEND); + } + public static IconData schema() { + return new IconData(FontImage.MATERIAL_SCHEMA); + } + public static IconData school() { + return new IconData(FontImage.MATERIAL_SCHOOL); + } + public static IconData science() { + return new IconData(FontImage.MATERIAL_SCIENCE); + } + public static IconData score() { + return new IconData(FontImage.MATERIAL_SCORE); + } + public static IconData scoreboard() { + return new IconData(FontImage.MATERIAL_SCOREBOARD); + } + public static IconData screen_lock_landscape() { + return new IconData(FontImage.MATERIAL_SCREEN_LOCK_LANDSCAPE); + } + public static IconData screen_lock_portrait() { + return new IconData(FontImage.MATERIAL_SCREEN_LOCK_PORTRAIT); + } + public static IconData screen_lock_rotation() { + return new IconData(FontImage.MATERIAL_SCREEN_LOCK_ROTATION); + } + public static IconData screen_rotation() { + return new IconData(FontImage.MATERIAL_SCREEN_ROTATION); + } + public static IconData screen_rotation_alt() { + return new IconData(FontImage.MATERIAL_SCREEN_ROTATION_ALT); + } + public static IconData screen_search_desktop() { + return new IconData(FontImage.MATERIAL_SCREEN_SEARCH_DESKTOP); + } + public static IconData screen_share() { + return new IconData(FontImage.MATERIAL_SCREEN_SHARE); + } + public static IconData screenshot() { + return new IconData(FontImage.MATERIAL_SCREENSHOT); + } + public static IconData screenshot_monitor() { + return new IconData(FontImage.MATERIAL_SCREENSHOT_MONITOR); + } + public static IconData scuba_diving() { + return new IconData(FontImage.MATERIAL_SCUBA_DIVING); + } + public static IconData sd() { + return new IconData(FontImage.MATERIAL_SD); + } + public static IconData sd_card() { + return new IconData(FontImage.MATERIAL_SD_CARD); + } + public static IconData sd_card_alert() { + return new IconData(FontImage.MATERIAL_SD_CARD_ALERT); + } + public static IconData sd_storage() { + return new IconData(FontImage.MATERIAL_SD_STORAGE); + } + public static IconData search_off() { + return new IconData(FontImage.MATERIAL_SEARCH_OFF); + } + public static IconData security_update() { + return new IconData(FontImage.MATERIAL_SECURITY_UPDATE); + } + public static IconData security_update_good() { + return new IconData(FontImage.MATERIAL_SECURITY_UPDATE_GOOD); + } + public static IconData security_update_warning() { + return new IconData(FontImage.MATERIAL_SECURITY_UPDATE_WARNING); + } + public static IconData segment() { + return new IconData(FontImage.MATERIAL_SEGMENT); + } + public static IconData select_all() { + return new IconData(FontImage.MATERIAL_SELECT_ALL); + } + public static IconData self_improvement() { + return new IconData(FontImage.MATERIAL_SELF_IMPROVEMENT); + } + public static IconData sell() { + return new IconData(FontImage.MATERIAL_SELL); + } + public static IconData send() { + return new IconData(FontImage.MATERIAL_SEND); + } + public static IconData send_and_archive() { + return new IconData(FontImage.MATERIAL_SEND_AND_ARCHIVE); + } + public static IconData send_time_extension() { + return new IconData(FontImage.MATERIAL_SEND_TIME_EXTENSION); + } + public static IconData send_to_mobile() { + return new IconData(FontImage.MATERIAL_SEND_TO_MOBILE); + } + public static IconData sensor_door() { + return new IconData(FontImage.MATERIAL_SENSOR_DOOR); + } + public static IconData sensor_occupied() { + return new IconData(FontImage.MATERIAL_SENSOR_OCCUPIED); + } + public static IconData sensor_window() { + return new IconData(FontImage.MATERIAL_SENSOR_WINDOW); + } + public static IconData sensors() { + return new IconData(FontImage.MATERIAL_SENSORS); + } + public static IconData sensors_off() { + return new IconData(FontImage.MATERIAL_SENSORS_OFF); + } + public static IconData sentiment_dissatisfied() { + return new IconData(FontImage.MATERIAL_SENTIMENT_DISSATISFIED); + } + public static IconData sentiment_neutral() { + return new IconData(FontImage.MATERIAL_SENTIMENT_NEUTRAL); + } + public static IconData sentiment_satisfied() { + return new IconData(FontImage.MATERIAL_SENTIMENT_SATISFIED); + } + public static IconData sentiment_satisfied_alt() { + return new IconData(FontImage.MATERIAL_SENTIMENT_SATISFIED_ALT); + } + public static IconData sentiment_very_dissatisfied() { + return new IconData(FontImage.MATERIAL_SENTIMENT_VERY_DISSATISFIED); + } + public static IconData sentiment_very_satisfied() { + return new IconData(FontImage.MATERIAL_SENTIMENT_VERY_SATISFIED); + } + public static IconData set_meal() { + return new IconData(FontImage.MATERIAL_SET_MEAL); + } + public static IconData settings_accessibility() { + return new IconData(FontImage.MATERIAL_SETTINGS_ACCESSIBILITY); + } + public static IconData settings_applications() { + return new IconData(FontImage.MATERIAL_SETTINGS_APPLICATIONS); + } + public static IconData settings_backup_restore() { + return new IconData(FontImage.MATERIAL_SETTINGS_BACKUP_RESTORE); + } + public static IconData settings_bluetooth() { + return new IconData(FontImage.MATERIAL_SETTINGS_BLUETOOTH); + } + public static IconData settings_brightness() { + return new IconData(FontImage.MATERIAL_SETTINGS_BRIGHTNESS); + } + public static IconData settings_cell() { + return new IconData(FontImage.MATERIAL_SETTINGS_CELL); + } + public static IconData settings_display() { + return new IconData(FontImage.MATERIAL_SETTINGS_DISPLAY); + } + public static IconData settings_ethernet() { + return new IconData(FontImage.MATERIAL_SETTINGS_ETHERNET); + } + public static IconData settings_input_antenna() { + return new IconData(FontImage.MATERIAL_SETTINGS_INPUT_ANTENNA); + } + public static IconData settings_input_component() { + return new IconData(FontImage.MATERIAL_SETTINGS_INPUT_COMPONENT); + } + public static IconData settings_input_composite() { + return new IconData(FontImage.MATERIAL_SETTINGS_INPUT_COMPOSITE); + } + public static IconData settings_input_hdmi() { + return new IconData(FontImage.MATERIAL_SETTINGS_INPUT_HDMI); + } + public static IconData settings_input_svideo() { + return new IconData(FontImage.MATERIAL_SETTINGS_INPUT_SVIDEO); + } + public static IconData settings_overscan() { + return new IconData(FontImage.MATERIAL_SETTINGS_OVERSCAN); + } + public static IconData settings_phone() { + return new IconData(FontImage.MATERIAL_SETTINGS_PHONE); + } + public static IconData settings_power() { + return new IconData(FontImage.MATERIAL_SETTINGS_POWER); + } + public static IconData settings_remote() { + return new IconData(FontImage.MATERIAL_SETTINGS_REMOTE); + } + public static IconData settings_suggest() { + return new IconData(FontImage.MATERIAL_SETTINGS_SUGGEST); + } + public static IconData settings_system_daydream() { + return new IconData(FontImage.MATERIAL_SETTINGS_SYSTEM_DAYDREAM); + } + public static IconData settings_voice() { + return new IconData(FontImage.MATERIAL_SETTINGS_VOICE); + } + public static IconData severe_cold() { + return new IconData(FontImage.MATERIAL_SEVERE_COLD); + } + public static IconData shape_line() { + return new IconData(FontImage.MATERIAL_SHAPE_LINE); + } + public static IconData share_arrival_time() { + return new IconData(FontImage.MATERIAL_SHARE_ARRIVAL_TIME); + } + public static IconData share_location() { + return new IconData(FontImage.MATERIAL_SHARE_LOCATION); + } + public static IconData shelves() { + return new IconData(FontImage.MATERIAL_SHELVES); + } + public static IconData shield() { + return new IconData(FontImage.MATERIAL_SHIELD); + } + public static IconData shield_moon() { + return new IconData(FontImage.MATERIAL_SHIELD_MOON); + } + public static IconData shop() { + return new IconData(FontImage.MATERIAL_SHOP); + } + public static IconData shop_2() { + return new IconData(FontImage.MATERIAL_SHOP_2); + } + public static IconData shop_two() { + return new IconData(FontImage.MATERIAL_SHOP_TWO); + } + public static IconData shopify() { + return new IconData(FontImage.MATERIAL_SHOPIFY); + } + public static IconData shopping_bag() { + return new IconData(FontImage.MATERIAL_SHOPPING_BAG); + } + public static IconData shopping_basket() { + return new IconData(FontImage.MATERIAL_SHOPPING_BASKET); + } + public static IconData shopping_cart_checkout() { + return new IconData(FontImage.MATERIAL_SHOPPING_CART_CHECKOUT); + } + public static IconData short_text() { + return new IconData(FontImage.MATERIAL_SHORT_TEXT); + } + public static IconData shortcut() { + return new IconData(FontImage.MATERIAL_SHORTCUT); + } + public static IconData show_chart() { + return new IconData(FontImage.MATERIAL_SHOW_CHART); + } + public static IconData shower() { + return new IconData(FontImage.MATERIAL_SHOWER); + } + public static IconData shuffle() { + return new IconData(FontImage.MATERIAL_SHUFFLE); + } + public static IconData shuffle_on() { + return new IconData(FontImage.MATERIAL_SHUFFLE_ON); + } + public static IconData shutter_speed() { + return new IconData(FontImage.MATERIAL_SHUTTER_SPEED); + } + public static IconData sick() { + return new IconData(FontImage.MATERIAL_SICK); + } + public static IconData sign_language() { + return new IconData(FontImage.MATERIAL_SIGN_LANGUAGE); + } + public static IconData signal_cellular_0_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_0_BAR); + } + public static IconData signal_cellular_4_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_4_BAR); + } + public static IconData signal_cellular_alt() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_ALT); + } + public static IconData signal_cellular_alt_1_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_ALT_1_BAR); + } + public static IconData signal_cellular_alt_2_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_ALT_2_BAR); + } + public static IconData signal_cellular_connected_no_internet_0_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_CONNECTED_NO_INTERNET_0_BAR); + } + public static IconData signal_cellular_connected_no_internet_4_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_CONNECTED_NO_INTERNET_4_BAR); + } + public static IconData signal_cellular_no_sim() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_NO_SIM); + } + public static IconData signal_cellular_nodata() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_NODATA); + } + public static IconData signal_cellular_null() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_NULL); + } + public static IconData signal_cellular_off() { + return new IconData(FontImage.MATERIAL_SIGNAL_CELLULAR_OFF); + } + public static IconData signal_wifi_0_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_0_BAR); + } + public static IconData signal_wifi_4_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_4_BAR); + } + public static IconData signal_wifi_4_bar_lock() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_4_BAR_LOCK); + } + public static IconData signal_wifi_bad() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_BAD); + } + public static IconData signal_wifi_connected_no_internet_4() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_CONNECTED_NO_INTERNET_4); + } + public static IconData signal_wifi_off() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_OFF); + } + public static IconData signal_wifi_statusbar_4_bar() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_STATUSBAR_4_BAR); + } + public static IconData signal_wifi_statusbar_connected_no_internet_4() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_STATUSBAR_CONNECTED_NO_INTERNET_4); + } + public static IconData signal_wifi_statusbar_null() { + return new IconData(FontImage.MATERIAL_SIGNAL_WIFI_STATUSBAR_NULL); + } + public static IconData signpost() { + return new IconData(FontImage.MATERIAL_SIGNPOST); + } + public static IconData sim_card() { + return new IconData(FontImage.MATERIAL_SIM_CARD); + } + public static IconData sim_card_alert() { + return new IconData(FontImage.MATERIAL_SIM_CARD_ALERT); + } + public static IconData sim_card_download() { + return new IconData(FontImage.MATERIAL_SIM_CARD_DOWNLOAD); + } + public static IconData single_bed() { + return new IconData(FontImage.MATERIAL_SINGLE_BED); + } + public static IconData sip() { + return new IconData(FontImage.MATERIAL_SIP); + } + public static IconData skateboarding() { + return new IconData(FontImage.MATERIAL_SKATEBOARDING); + } + public static IconData skip_next() { + return new IconData(FontImage.MATERIAL_SKIP_NEXT); + } + public static IconData skip_previous() { + return new IconData(FontImage.MATERIAL_SKIP_PREVIOUS); + } + public static IconData sledding() { + return new IconData(FontImage.MATERIAL_SLEDDING); + } + public static IconData slideshow() { + return new IconData(FontImage.MATERIAL_SLIDESHOW); + } + public static IconData slow_motion_video() { + return new IconData(FontImage.MATERIAL_SLOW_MOTION_VIDEO); + } + public static IconData smart_button() { + return new IconData(FontImage.MATERIAL_SMART_BUTTON); + } + public static IconData smart_display() { + return new IconData(FontImage.MATERIAL_SMART_DISPLAY); + } + public static IconData smart_screen() { + return new IconData(FontImage.MATERIAL_SMART_SCREEN); + } + public static IconData smart_toy() { + return new IconData(FontImage.MATERIAL_SMART_TOY); + } + public static IconData smartphone() { + return new IconData(FontImage.MATERIAL_SMARTPHONE); + } + public static IconData smoke_free() { + return new IconData(FontImage.MATERIAL_SMOKE_FREE); + } + public static IconData smoking_rooms() { + return new IconData(FontImage.MATERIAL_SMOKING_ROOMS); + } + public static IconData sms() { + return new IconData(FontImage.MATERIAL_SMS); + } + public static IconData sms_failed() { + return new IconData(FontImage.MATERIAL_SMS_FAILED); + } + public static IconData snapchat() { + return new IconData(FontImage.MATERIAL_SNAPCHAT); + } + public static IconData snippet_folder() { + return new IconData(FontImage.MATERIAL_SNIPPET_FOLDER); + } + public static IconData snooze() { + return new IconData(FontImage.MATERIAL_SNOOZE); + } + public static IconData snowboarding() { + return new IconData(FontImage.MATERIAL_SNOWBOARDING); + } + public static IconData snowing() { + return new IconData(FontImage.MATERIAL_SNOWING); + } + public static IconData snowmobile() { + return new IconData(FontImage.MATERIAL_SNOWMOBILE); + } + public static IconData snowshoeing() { + return new IconData(FontImage.MATERIAL_SNOWSHOEING); + } + public static IconData soap() { + return new IconData(FontImage.MATERIAL_SOAP); + } + public static IconData social_distance() { + return new IconData(FontImage.MATERIAL_SOCIAL_DISTANCE); + } + public static IconData solar_power() { + return new IconData(FontImage.MATERIAL_SOLAR_POWER); + } + public static IconData sort_by_alpha() { + return new IconData(FontImage.MATERIAL_SORT_BY_ALPHA); + } + public static IconData sos() { + return new IconData(FontImage.MATERIAL_SOS); + } + public static IconData soup_kitchen() { + return new IconData(FontImage.MATERIAL_SOUP_KITCHEN); + } + public static IconData source() { + return new IconData(FontImage.MATERIAL_SOURCE); + } + public static IconData south() { + return new IconData(FontImage.MATERIAL_SOUTH); + } + public static IconData south_america() { + return new IconData(FontImage.MATERIAL_SOUTH_AMERICA); + } + public static IconData south_east() { + return new IconData(FontImage.MATERIAL_SOUTH_EAST); + } + public static IconData south_west() { + return new IconData(FontImage.MATERIAL_SOUTH_WEST); + } + public static IconData spa() { + return new IconData(FontImage.MATERIAL_SPA); + } + public static IconData space_bar() { + return new IconData(FontImage.MATERIAL_SPACE_BAR); + } + public static IconData space_dashboard() { + return new IconData(FontImage.MATERIAL_SPACE_DASHBOARD); + } + public static IconData spatial_audio() { + return new IconData(FontImage.MATERIAL_SPATIAL_AUDIO); + } + public static IconData spatial_audio_off() { + return new IconData(FontImage.MATERIAL_SPATIAL_AUDIO_OFF); + } + public static IconData spatial_tracking() { + return new IconData(FontImage.MATERIAL_SPATIAL_TRACKING); + } + public static IconData speaker() { + return new IconData(FontImage.MATERIAL_SPEAKER); + } + public static IconData speaker_group() { + return new IconData(FontImage.MATERIAL_SPEAKER_GROUP); + } + public static IconData speaker_notes() { + return new IconData(FontImage.MATERIAL_SPEAKER_NOTES); + } + public static IconData speaker_notes_off() { + return new IconData(FontImage.MATERIAL_SPEAKER_NOTES_OFF); + } + public static IconData speaker_phone() { + return new IconData(FontImage.MATERIAL_SPEAKER_PHONE); + } + public static IconData speed() { + return new IconData(FontImage.MATERIAL_SPEED); + } + public static IconData spellcheck() { + return new IconData(FontImage.MATERIAL_SPELLCHECK); + } + public static IconData splitscreen() { + return new IconData(FontImage.MATERIAL_SPLITSCREEN); + } + public static IconData spoke() { + return new IconData(FontImage.MATERIAL_SPOKE); + } + public static IconData sports() { + return new IconData(FontImage.MATERIAL_SPORTS); + } + public static IconData sports_bar() { + return new IconData(FontImage.MATERIAL_SPORTS_BAR); + } + public static IconData sports_baseball() { + return new IconData(FontImage.MATERIAL_SPORTS_BASEBALL); + } + public static IconData sports_basketball() { + return new IconData(FontImage.MATERIAL_SPORTS_BASKETBALL); + } + public static IconData sports_cricket() { + return new IconData(FontImage.MATERIAL_SPORTS_CRICKET); + } + public static IconData sports_esports() { + return new IconData(FontImage.MATERIAL_SPORTS_ESPORTS); + } + public static IconData sports_football() { + return new IconData(FontImage.MATERIAL_SPORTS_FOOTBALL); + } + public static IconData sports_golf() { + return new IconData(FontImage.MATERIAL_SPORTS_GOLF); + } + public static IconData sports_gymnastics() { + return new IconData(FontImage.MATERIAL_SPORTS_GYMNASTICS); + } + public static IconData sports_handball() { + return new IconData(FontImage.MATERIAL_SPORTS_HANDBALL); + } + public static IconData sports_hockey() { + return new IconData(FontImage.MATERIAL_SPORTS_HOCKEY); + } + public static IconData sports_kabaddi() { + return new IconData(FontImage.MATERIAL_SPORTS_KABADDI); + } + public static IconData sports_martial_arts() { + return new IconData(FontImage.MATERIAL_SPORTS_MARTIAL_ARTS); + } + public static IconData sports_mma() { + return new IconData(FontImage.MATERIAL_SPORTS_MMA); + } + public static IconData sports_motorsports() { + return new IconData(FontImage.MATERIAL_SPORTS_MOTORSPORTS); + } + public static IconData sports_rugby() { + return new IconData(FontImage.MATERIAL_SPORTS_RUGBY); + } + public static IconData sports_score() { + return new IconData(FontImage.MATERIAL_SPORTS_SCORE); + } + public static IconData sports_soccer() { + return new IconData(FontImage.MATERIAL_SPORTS_SOCCER); + } + public static IconData sports_tennis() { + return new IconData(FontImage.MATERIAL_SPORTS_TENNIS); + } + public static IconData sports_volleyball() { + return new IconData(FontImage.MATERIAL_SPORTS_VOLLEYBALL); + } + public static IconData square() { + return new IconData(FontImage.MATERIAL_SQUARE); + } + public static IconData square_foot() { + return new IconData(FontImage.MATERIAL_SQUARE_FOOT); + } + public static IconData ssid_chart() { + return new IconData(FontImage.MATERIAL_SSID_CHART); + } + public static IconData stacked_bar_chart() { + return new IconData(FontImage.MATERIAL_STACKED_BAR_CHART); + } + public static IconData stacked_line_chart() { + return new IconData(FontImage.MATERIAL_STACKED_LINE_CHART); + } + public static IconData stadium() { + return new IconData(FontImage.MATERIAL_STADIUM); + } + public static IconData stairs() { + return new IconData(FontImage.MATERIAL_STAIRS); + } + public static IconData star_border_purple500() { + return new IconData(FontImage.MATERIAL_STAR_BORDER_PURPLE500); + } + public static IconData star_half() { + return new IconData(FontImage.MATERIAL_STAR_HALF); + } + public static IconData star_outline() { + return new IconData(FontImage.MATERIAL_STAR_OUTLINE); + } + public static IconData star_purple500() { + return new IconData(FontImage.MATERIAL_STAR_PURPLE500); + } + public static IconData star_rate() { + return new IconData(FontImage.MATERIAL_STAR_RATE); + } + public static IconData stars() { + return new IconData(FontImage.MATERIAL_STARS); + } + public static IconData start() { + return new IconData(FontImage.MATERIAL_START); + } + public static IconData stay_current_landscape() { + return new IconData(FontImage.MATERIAL_STAY_CURRENT_LANDSCAPE); + } + public static IconData stay_current_portrait() { + return new IconData(FontImage.MATERIAL_STAY_CURRENT_PORTRAIT); + } + public static IconData stay_primary_landscape() { + return new IconData(FontImage.MATERIAL_STAY_PRIMARY_LANDSCAPE); + } + public static IconData stay_primary_portrait() { + return new IconData(FontImage.MATERIAL_STAY_PRIMARY_PORTRAIT); + } + public static IconData sticky_note_2() { + return new IconData(FontImage.MATERIAL_STICKY_NOTE_2); + } + public static IconData stop() { + return new IconData(FontImage.MATERIAL_STOP); + } + public static IconData stop_circle() { + return new IconData(FontImage.MATERIAL_STOP_CIRCLE); + } + public static IconData stop_screen_share() { + return new IconData(FontImage.MATERIAL_STOP_SCREEN_SHARE); + } + public static IconData storage() { + return new IconData(FontImage.MATERIAL_STORAGE); + } + public static IconData store() { + return new IconData(FontImage.MATERIAL_STORE); + } + public static IconData store_mall_directory() { + return new IconData(FontImage.MATERIAL_STORE_MALL_DIRECTORY); + } + public static IconData storefront() { + return new IconData(FontImage.MATERIAL_STOREFRONT); + } + public static IconData storm() { + return new IconData(FontImage.MATERIAL_STORM); + } + public static IconData straight() { + return new IconData(FontImage.MATERIAL_STRAIGHT); + } + public static IconData straighten() { + return new IconData(FontImage.MATERIAL_STRAIGHTEN); + } + public static IconData stream() { + return new IconData(FontImage.MATERIAL_STREAM); + } + public static IconData streetview() { + return new IconData(FontImage.MATERIAL_STREETVIEW); + } + public static IconData strikethrough_s() { + return new IconData(FontImage.MATERIAL_STRIKETHROUGH_S); + } + public static IconData stroller() { + return new IconData(FontImage.MATERIAL_STROLLER); + } + public static IconData style() { + return new IconData(FontImage.MATERIAL_STYLE); + } + public static IconData subdirectory_arrow_left() { + return new IconData(FontImage.MATERIAL_SUBDIRECTORY_ARROW_LEFT); + } + public static IconData subdirectory_arrow_right() { + return new IconData(FontImage.MATERIAL_SUBDIRECTORY_ARROW_RIGHT); + } + public static IconData subject() { + return new IconData(FontImage.MATERIAL_SUBJECT); + } + public static IconData subscript() { + return new IconData(FontImage.MATERIAL_SUBSCRIPT); + } + public static IconData subscriptions() { + return new IconData(FontImage.MATERIAL_SUBSCRIPTIONS); + } + public static IconData subtitles() { + return new IconData(FontImage.MATERIAL_SUBTITLES); + } + public static IconData subtitles_off() { + return new IconData(FontImage.MATERIAL_SUBTITLES_OFF); + } + public static IconData subway() { + return new IconData(FontImage.MATERIAL_SUBWAY); + } + public static IconData summarize() { + return new IconData(FontImage.MATERIAL_SUMMARIZE); + } + public static IconData sunny() { + return new IconData(FontImage.MATERIAL_SUNNY); + } + public static IconData sunny_snowing() { + return new IconData(FontImage.MATERIAL_SUNNY_SNOWING); + } + public static IconData superscript() { + return new IconData(FontImage.MATERIAL_SUPERSCRIPT); + } + public static IconData supervised_user_circle() { + return new IconData(FontImage.MATERIAL_SUPERVISED_USER_CIRCLE); + } + public static IconData supervisor_account() { + return new IconData(FontImage.MATERIAL_SUPERVISOR_ACCOUNT); + } + public static IconData support() { + return new IconData(FontImage.MATERIAL_SUPPORT); + } + public static IconData support_agent() { + return new IconData(FontImage.MATERIAL_SUPPORT_AGENT); + } + public static IconData surfing() { + return new IconData(FontImage.MATERIAL_SURFING); + } + public static IconData surround_sound() { + return new IconData(FontImage.MATERIAL_SURROUND_SOUND); + } + public static IconData swap_calls() { + return new IconData(FontImage.MATERIAL_SWAP_CALLS); + } + public static IconData swap_horiz() { + return new IconData(FontImage.MATERIAL_SWAP_HORIZ); + } + public static IconData swap_horizontal_circle() { + return new IconData(FontImage.MATERIAL_SWAP_HORIZONTAL_CIRCLE); + } + public static IconData swap_vert() { + return new IconData(FontImage.MATERIAL_SWAP_VERT); + } + public static IconData swap_vert_circle() { + return new IconData(FontImage.MATERIAL_SWAP_VERT_CIRCLE); + } + public static IconData swap_vertical_circle() { + return new IconData(FontImage.MATERIAL_SWAP_VERTICAL_CIRCLE); + } + public static IconData swipe() { + return new IconData(FontImage.MATERIAL_SWIPE); + } + public static IconData swipe_down() { + return new IconData(FontImage.MATERIAL_SWIPE_DOWN); + } + public static IconData swipe_down_alt() { + return new IconData(FontImage.MATERIAL_SWIPE_DOWN_ALT); + } + public static IconData swipe_left() { + return new IconData(FontImage.MATERIAL_SWIPE_LEFT); + } + public static IconData swipe_left_alt() { + return new IconData(FontImage.MATERIAL_SWIPE_LEFT_ALT); + } + public static IconData swipe_right() { + return new IconData(FontImage.MATERIAL_SWIPE_RIGHT); + } + public static IconData swipe_right_alt() { + return new IconData(FontImage.MATERIAL_SWIPE_RIGHT_ALT); + } + public static IconData swipe_up() { + return new IconData(FontImage.MATERIAL_SWIPE_UP); + } + public static IconData swipe_up_alt() { + return new IconData(FontImage.MATERIAL_SWIPE_UP_ALT); + } + public static IconData swipe_vertical() { + return new IconData(FontImage.MATERIAL_SWIPE_VERTICAL); + } + public static IconData switch_access_shortcut() { + return new IconData(FontImage.MATERIAL_SWITCH_ACCESS_SHORTCUT); + } + public static IconData switch_access_shortcut_add() { + return new IconData(FontImage.MATERIAL_SWITCH_ACCESS_SHORTCUT_ADD); + } + public static IconData switch_account() { + return new IconData(FontImage.MATERIAL_SWITCH_ACCOUNT); + } + public static IconData switch_camera() { + return new IconData(FontImage.MATERIAL_SWITCH_CAMERA); + } + public static IconData switch_left() { + return new IconData(FontImage.MATERIAL_SWITCH_LEFT); + } + public static IconData switch_right() { + return new IconData(FontImage.MATERIAL_SWITCH_RIGHT); + } + public static IconData switch_video() { + return new IconData(FontImage.MATERIAL_SWITCH_VIDEO); + } + public static IconData synagogue() { + return new IconData(FontImage.MATERIAL_SYNAGOGUE); + } + public static IconData sync() { + return new IconData(FontImage.MATERIAL_SYNC); + } + public static IconData sync_alt() { + return new IconData(FontImage.MATERIAL_SYNC_ALT); + } + public static IconData sync_disabled() { + return new IconData(FontImage.MATERIAL_SYNC_DISABLED); + } + public static IconData sync_lock() { + return new IconData(FontImage.MATERIAL_SYNC_LOCK); + } + public static IconData sync_problem() { + return new IconData(FontImage.MATERIAL_SYNC_PROBLEM); + } + public static IconData system_security_update() { + return new IconData(FontImage.MATERIAL_SYSTEM_SECURITY_UPDATE); + } + public static IconData system_security_update_good() { + return new IconData(FontImage.MATERIAL_SYSTEM_SECURITY_UPDATE_GOOD); + } + public static IconData system_security_update_warning() { + return new IconData(FontImage.MATERIAL_SYSTEM_SECURITY_UPDATE_WARNING); + } + public static IconData system_update() { + return new IconData(FontImage.MATERIAL_SYSTEM_UPDATE); + } + public static IconData system_update_alt() { + return new IconData(FontImage.MATERIAL_SYSTEM_UPDATE_ALT); + } + public static IconData system_update_tv() { + return new IconData(FontImage.MATERIAL_SYSTEM_UPDATE_TV); + } + public static IconData tab() { + return new IconData(FontImage.MATERIAL_TAB); + } + public static IconData tab_unselected() { + return new IconData(FontImage.MATERIAL_TAB_UNSELECTED); + } + public static IconData table_bar() { + return new IconData(FontImage.MATERIAL_TABLE_BAR); + } + public static IconData table_restaurant() { + return new IconData(FontImage.MATERIAL_TABLE_RESTAURANT); + } + public static IconData table_rows() { + return new IconData(FontImage.MATERIAL_TABLE_ROWS); + } + public static IconData table_view() { + return new IconData(FontImage.MATERIAL_TABLE_VIEW); + } + public static IconData tablet() { + return new IconData(FontImage.MATERIAL_TABLET); + } + public static IconData tablet_android() { + return new IconData(FontImage.MATERIAL_TABLET_ANDROID); + } + public static IconData tablet_mac() { + return new IconData(FontImage.MATERIAL_TABLET_MAC); + } + public static IconData tag() { + return new IconData(FontImage.MATERIAL_TAG); + } + public static IconData tag_faces() { + return new IconData(FontImage.MATERIAL_TAG_FACES); + } + public static IconData takeout_dining() { + return new IconData(FontImage.MATERIAL_TAKEOUT_DINING); + } + public static IconData tap_and_play() { + return new IconData(FontImage.MATERIAL_TAP_AND_PLAY); + } + public static IconData tapas() { + return new IconData(FontImage.MATERIAL_TAPAS); + } + public static IconData task() { + return new IconData(FontImage.MATERIAL_TASK); + } + public static IconData task_alt() { + return new IconData(FontImage.MATERIAL_TASK_ALT); + } + public static IconData taxi_alert() { + return new IconData(FontImage.MATERIAL_TAXI_ALERT); + } + public static IconData telegram() { + return new IconData(FontImage.MATERIAL_TELEGRAM); + } + public static IconData temple_buddhist() { + return new IconData(FontImage.MATERIAL_TEMPLE_BUDDHIST); + } + public static IconData temple_hindu() { + return new IconData(FontImage.MATERIAL_TEMPLE_HINDU); + } + public static IconData terminal() { + return new IconData(FontImage.MATERIAL_TERMINAL); + } + public static IconData terrain() { + return new IconData(FontImage.MATERIAL_TERRAIN); + } + public static IconData text_decrease() { + return new IconData(FontImage.MATERIAL_TEXT_DECREASE); + } + public static IconData text_fields() { + return new IconData(FontImage.MATERIAL_TEXT_FIELDS); + } + public static IconData text_format() { + return new IconData(FontImage.MATERIAL_TEXT_FORMAT); + } + public static IconData text_increase() { + return new IconData(FontImage.MATERIAL_TEXT_INCREASE); + } + public static IconData text_rotate_up() { + return new IconData(FontImage.MATERIAL_TEXT_ROTATE_UP); + } + public static IconData text_rotate_vertical() { + return new IconData(FontImage.MATERIAL_TEXT_ROTATE_VERTICAL); + } + public static IconData text_rotation_angledown() { + return new IconData(FontImage.MATERIAL_TEXT_ROTATION_ANGLEDOWN); + } + public static IconData text_rotation_angleup() { + return new IconData(FontImage.MATERIAL_TEXT_ROTATION_ANGLEUP); + } + public static IconData text_rotation_down() { + return new IconData(FontImage.MATERIAL_TEXT_ROTATION_DOWN); + } + public static IconData text_rotation_none() { + return new IconData(FontImage.MATERIAL_TEXT_ROTATION_NONE); + } + public static IconData text_snippet() { + return new IconData(FontImage.MATERIAL_TEXT_SNIPPET); + } + public static IconData textsms() { + return new IconData(FontImage.MATERIAL_TEXTSMS); + } + public static IconData texture() { + return new IconData(FontImage.MATERIAL_TEXTURE); + } + public static IconData theater_comedy() { + return new IconData(FontImage.MATERIAL_THEATER_COMEDY); + } + public static IconData theaters() { + return new IconData(FontImage.MATERIAL_THEATERS); + } + public static IconData thermostat() { + return new IconData(FontImage.MATERIAL_THERMOSTAT); + } + public static IconData thermostat_auto() { + return new IconData(FontImage.MATERIAL_THERMOSTAT_AUTO); + } + public static IconData thumb_down() { + return new IconData(FontImage.MATERIAL_THUMB_DOWN); + } + public static IconData thumb_down_alt() { + return new IconData(FontImage.MATERIAL_THUMB_DOWN_ALT); + } + public static IconData thumb_down_off_alt() { + return new IconData(FontImage.MATERIAL_THUMB_DOWN_OFF_ALT); + } + public static IconData thumb_up() { + return new IconData(FontImage.MATERIAL_THUMB_UP); + } + public static IconData thumb_up_alt() { + return new IconData(FontImage.MATERIAL_THUMB_UP_ALT); + } + public static IconData thumb_up_off_alt() { + return new IconData(FontImage.MATERIAL_THUMB_UP_OFF_ALT); + } + public static IconData thumbs_up_down() { + return new IconData(FontImage.MATERIAL_THUMBS_UP_DOWN); + } + public static IconData thunderstorm() { + return new IconData(FontImage.MATERIAL_THUNDERSTORM); + } + public static IconData tiktok() { + return new IconData(FontImage.MATERIAL_TIKTOK); + } + public static IconData time_to_leave() { + return new IconData(FontImage.MATERIAL_TIME_TO_LEAVE); + } + public static IconData timelapse() { + return new IconData(FontImage.MATERIAL_TIMELAPSE); + } + public static IconData timeline() { + return new IconData(FontImage.MATERIAL_TIMELINE); + } + public static IconData timer() { + return new IconData(FontImage.MATERIAL_TIMER); + } + public static IconData timer_10() { + return new IconData(FontImage.MATERIAL_TIMER_10); + } + public static IconData timer_10_select() { + return new IconData(FontImage.MATERIAL_TIMER_10_SELECT); + } + public static IconData timer_3() { + return new IconData(FontImage.MATERIAL_TIMER_3); + } + public static IconData timer_3_select() { + return new IconData(FontImage.MATERIAL_TIMER_3_SELECT); + } + public static IconData timer_off() { + return new IconData(FontImage.MATERIAL_TIMER_OFF); + } + public static IconData tips_and_updates() { + return new IconData(FontImage.MATERIAL_TIPS_AND_UPDATES); + } + public static IconData tire_repair() { + return new IconData(FontImage.MATERIAL_TIRE_REPAIR); + } + public static IconData title() { + return new IconData(FontImage.MATERIAL_TITLE); + } + public static IconData toc() { + return new IconData(FontImage.MATERIAL_TOC); + } + public static IconData today() { + return new IconData(FontImage.MATERIAL_TODAY); + } + public static IconData toggle_off() { + return new IconData(FontImage.MATERIAL_TOGGLE_OFF); + } + public static IconData toggle_on() { + return new IconData(FontImage.MATERIAL_TOGGLE_ON); + } + public static IconData token() { + return new IconData(FontImage.MATERIAL_TOKEN); + } + public static IconData toll() { + return new IconData(FontImage.MATERIAL_TOLL); + } + public static IconData tonality() { + return new IconData(FontImage.MATERIAL_TONALITY); + } + public static IconData topic() { + return new IconData(FontImage.MATERIAL_TOPIC); + } + public static IconData tornado() { + return new IconData(FontImage.MATERIAL_TORNADO); + } + public static IconData touch_app() { + return new IconData(FontImage.MATERIAL_TOUCH_APP); + } + public static IconData tour() { + return new IconData(FontImage.MATERIAL_TOUR); + } + public static IconData toys() { + return new IconData(FontImage.MATERIAL_TOYS); + } + public static IconData track_changes() { + return new IconData(FontImage.MATERIAL_TRACK_CHANGES); + } + public static IconData traffic() { + return new IconData(FontImage.MATERIAL_TRAFFIC); + } + public static IconData train() { + return new IconData(FontImage.MATERIAL_TRAIN); + } + public static IconData tram() { + return new IconData(FontImage.MATERIAL_TRAM); + } + public static IconData transcribe() { + return new IconData(FontImage.MATERIAL_TRANSCRIBE); + } + public static IconData transfer_within_a_station() { + return new IconData(FontImage.MATERIAL_TRANSFER_WITHIN_A_STATION); + } + public static IconData transform() { + return new IconData(FontImage.MATERIAL_TRANSFORM); + } + public static IconData transgender() { + return new IconData(FontImage.MATERIAL_TRANSGENDER); + } + public static IconData transit_enterexit() { + return new IconData(FontImage.MATERIAL_TRANSIT_ENTEREXIT); + } + public static IconData translate() { + return new IconData(FontImage.MATERIAL_TRANSLATE); + } + public static IconData travel_explore() { + return new IconData(FontImage.MATERIAL_TRAVEL_EXPLORE); + } + public static IconData trending_down() { + return new IconData(FontImage.MATERIAL_TRENDING_DOWN); + } + public static IconData trending_flat() { + return new IconData(FontImage.MATERIAL_TRENDING_FLAT); + } + public static IconData trending_neutral() { + return new IconData(FontImage.MATERIAL_TRENDING_NEUTRAL); + } + public static IconData trending_up() { + return new IconData(FontImage.MATERIAL_TRENDING_UP); + } + public static IconData trip_origin() { + return new IconData(FontImage.MATERIAL_TRIP_ORIGIN); + } + public static IconData trolley() { + return new IconData(FontImage.MATERIAL_TROLLEY); + } + public static IconData troubleshoot() { + return new IconData(FontImage.MATERIAL_TROUBLESHOOT); + } + public static IconData try_() { + return new IconData(FontImage.MATERIAL_TRY); + } + public static IconData tsunami() { + return new IconData(FontImage.MATERIAL_TSUNAMI); + } + public static IconData tty() { + return new IconData(FontImage.MATERIAL_TTY); + } + public static IconData tungsten() { + return new IconData(FontImage.MATERIAL_TUNGSTEN); + } + public static IconData turn_left() { + return new IconData(FontImage.MATERIAL_TURN_LEFT); + } + public static IconData turn_right() { + return new IconData(FontImage.MATERIAL_TURN_RIGHT); + } + public static IconData turn_sharp_left() { + return new IconData(FontImage.MATERIAL_TURN_SHARP_LEFT); + } + public static IconData turn_sharp_right() { + return new IconData(FontImage.MATERIAL_TURN_SHARP_RIGHT); + } + public static IconData turn_slight_left() { + return new IconData(FontImage.MATERIAL_TURN_SLIGHT_LEFT); + } + public static IconData turn_slight_right() { + return new IconData(FontImage.MATERIAL_TURN_SLIGHT_RIGHT); + } + public static IconData turned_in() { + return new IconData(FontImage.MATERIAL_TURNED_IN); + } + public static IconData turned_in_not() { + return new IconData(FontImage.MATERIAL_TURNED_IN_NOT); + } + public static IconData tv() { + return new IconData(FontImage.MATERIAL_TV); + } + public static IconData tv_off() { + return new IconData(FontImage.MATERIAL_TV_OFF); + } + public static IconData two_wheeler() { + return new IconData(FontImage.MATERIAL_TWO_WHEELER); + } + public static IconData type_specimen() { + return new IconData(FontImage.MATERIAL_TYPE_SPECIMEN); + } + public static IconData u_turn_left() { + return new IconData(FontImage.MATERIAL_U_TURN_LEFT); + } + public static IconData u_turn_right() { + return new IconData(FontImage.MATERIAL_U_TURN_RIGHT); + } + public static IconData umbrella() { + return new IconData(FontImage.MATERIAL_UMBRELLA); + } + public static IconData unarchive() { + return new IconData(FontImage.MATERIAL_UNARCHIVE); + } + public static IconData undo() { + return new IconData(FontImage.MATERIAL_UNDO); + } + public static IconData unfold_less() { + return new IconData(FontImage.MATERIAL_UNFOLD_LESS); + } + public static IconData unfold_less_double() { + return new IconData(FontImage.MATERIAL_UNFOLD_LESS_DOUBLE); + } + public static IconData unfold_more() { + return new IconData(FontImage.MATERIAL_UNFOLD_MORE); + } + public static IconData unfold_more_double() { + return new IconData(FontImage.MATERIAL_UNFOLD_MORE_DOUBLE); + } + public static IconData unpublished() { + return new IconData(FontImage.MATERIAL_UNPUBLISHED); + } + public static IconData unsubscribe() { + return new IconData(FontImage.MATERIAL_UNSUBSCRIBE); + } + public static IconData upcoming() { + return new IconData(FontImage.MATERIAL_UPCOMING); + } + public static IconData update() { + return new IconData(FontImage.MATERIAL_UPDATE); + } + public static IconData update_disabled() { + return new IconData(FontImage.MATERIAL_UPDATE_DISABLED); + } + public static IconData upgrade() { + return new IconData(FontImage.MATERIAL_UPGRADE); + } + public static IconData upload() { + return new IconData(FontImage.MATERIAL_UPLOAD); + } + public static IconData upload_file() { + return new IconData(FontImage.MATERIAL_UPLOAD_FILE); + } + public static IconData usb() { + return new IconData(FontImage.MATERIAL_USB); + } + public static IconData usb_off() { + return new IconData(FontImage.MATERIAL_USB_OFF); + } + public static IconData vaccines() { + return new IconData(FontImage.MATERIAL_VACCINES); + } + public static IconData vape_free() { + return new IconData(FontImage.MATERIAL_VAPE_FREE); + } + public static IconData vaping_rooms() { + return new IconData(FontImage.MATERIAL_VAPING_ROOMS); + } + public static IconData verified() { + return new IconData(FontImage.MATERIAL_VERIFIED); + } + public static IconData verified_user() { + return new IconData(FontImage.MATERIAL_VERIFIED_USER); + } + public static IconData vertical_align_bottom() { + return new IconData(FontImage.MATERIAL_VERTICAL_ALIGN_BOTTOM); + } + public static IconData vertical_align_center() { + return new IconData(FontImage.MATERIAL_VERTICAL_ALIGN_CENTER); + } + public static IconData vertical_align_top() { + return new IconData(FontImage.MATERIAL_VERTICAL_ALIGN_TOP); + } + public static IconData vertical_distribute() { + return new IconData(FontImage.MATERIAL_VERTICAL_DISTRIBUTE); + } + public static IconData vertical_shades() { + return new IconData(FontImage.MATERIAL_VERTICAL_SHADES); + } + public static IconData vertical_shades_closed() { + return new IconData(FontImage.MATERIAL_VERTICAL_SHADES_CLOSED); + } + public static IconData vibration() { + return new IconData(FontImage.MATERIAL_VIBRATION); + } + public static IconData video_call() { + return new IconData(FontImage.MATERIAL_VIDEO_CALL); + } + public static IconData video_camera_back() { + return new IconData(FontImage.MATERIAL_VIDEO_CAMERA_BACK); + } + public static IconData video_camera_front() { + return new IconData(FontImage.MATERIAL_VIDEO_CAMERA_FRONT); + } + public static IconData video_chat() { + return new IconData(FontImage.MATERIAL_VIDEO_CHAT); + } + public static IconData video_collection() { + return new IconData(FontImage.MATERIAL_VIDEO_COLLECTION); + } + public static IconData video_file() { + return new IconData(FontImage.MATERIAL_VIDEO_FILE); + } + public static IconData video_label() { + return new IconData(FontImage.MATERIAL_VIDEO_LABEL); + } + public static IconData video_library() { + return new IconData(FontImage.MATERIAL_VIDEO_LIBRARY); + } + public static IconData video_settings() { + return new IconData(FontImage.MATERIAL_VIDEO_SETTINGS); + } + public static IconData video_stable() { + return new IconData(FontImage.MATERIAL_VIDEO_STABLE); + } + public static IconData videocam() { + return new IconData(FontImage.MATERIAL_VIDEOCAM); + } + public static IconData videocam_off() { + return new IconData(FontImage.MATERIAL_VIDEOCAM_OFF); + } + public static IconData videogame_asset() { + return new IconData(FontImage.MATERIAL_VIDEOGAME_ASSET); + } + public static IconData videogame_asset_off() { + return new IconData(FontImage.MATERIAL_VIDEOGAME_ASSET_OFF); + } + public static IconData view_agenda() { + return new IconData(FontImage.MATERIAL_VIEW_AGENDA); + } + public static IconData view_array() { + return new IconData(FontImage.MATERIAL_VIEW_ARRAY); + } + public static IconData view_carousel() { + return new IconData(FontImage.MATERIAL_VIEW_CAROUSEL); + } + public static IconData view_column() { + return new IconData(FontImage.MATERIAL_VIEW_COLUMN); + } + public static IconData view_comfortable() { + return new IconData(FontImage.MATERIAL_VIEW_COMFORTABLE); + } + public static IconData view_comfy() { + return new IconData(FontImage.MATERIAL_VIEW_COMFY); + } + public static IconData view_comfy_alt() { + return new IconData(FontImage.MATERIAL_VIEW_COMFY_ALT); + } + public static IconData view_compact() { + return new IconData(FontImage.MATERIAL_VIEW_COMPACT); + } + public static IconData view_compact_alt() { + return new IconData(FontImage.MATERIAL_VIEW_COMPACT_ALT); + } + public static IconData view_cozy() { + return new IconData(FontImage.MATERIAL_VIEW_COZY); + } + public static IconData view_day() { + return new IconData(FontImage.MATERIAL_VIEW_DAY); + } + public static IconData view_headline() { + return new IconData(FontImage.MATERIAL_VIEW_HEADLINE); + } + public static IconData view_in_ar() { + return new IconData(FontImage.MATERIAL_VIEW_IN_AR); + } + public static IconData view_kanban() { + return new IconData(FontImage.MATERIAL_VIEW_KANBAN); + } + public static IconData view_list() { + return new IconData(FontImage.MATERIAL_VIEW_LIST); + } + public static IconData view_module() { + return new IconData(FontImage.MATERIAL_VIEW_MODULE); + } + public static IconData view_quilt() { + return new IconData(FontImage.MATERIAL_VIEW_QUILT); + } + public static IconData view_sidebar() { + return new IconData(FontImage.MATERIAL_VIEW_SIDEBAR); + } + public static IconData view_stream() { + return new IconData(FontImage.MATERIAL_VIEW_STREAM); + } + public static IconData view_timeline() { + return new IconData(FontImage.MATERIAL_VIEW_TIMELINE); + } + public static IconData view_week() { + return new IconData(FontImage.MATERIAL_VIEW_WEEK); + } + public static IconData vignette() { + return new IconData(FontImage.MATERIAL_VIGNETTE); + } + public static IconData villa() { + return new IconData(FontImage.MATERIAL_VILLA); + } + public static IconData voice_chat() { + return new IconData(FontImage.MATERIAL_VOICE_CHAT); + } + public static IconData voice_over_off() { + return new IconData(FontImage.MATERIAL_VOICE_OVER_OFF); + } + public static IconData voicemail() { + return new IconData(FontImage.MATERIAL_VOICEMAIL); + } + public static IconData volcano() { + return new IconData(FontImage.MATERIAL_VOLCANO); + } + public static IconData volume_down() { + return new IconData(FontImage.MATERIAL_VOLUME_DOWN); + } + public static IconData volume_down_alt() { + return new IconData(FontImage.MATERIAL_VOLUME_DOWN_ALT); + } + public static IconData volume_mute() { + return new IconData(FontImage.MATERIAL_VOLUME_MUTE); + } + public static IconData volume_off() { + return new IconData(FontImage.MATERIAL_VOLUME_OFF); + } + public static IconData volume_up() { + return new IconData(FontImage.MATERIAL_VOLUME_UP); + } + public static IconData volunteer_activism() { + return new IconData(FontImage.MATERIAL_VOLUNTEER_ACTIVISM); + } + public static IconData vpn_key() { + return new IconData(FontImage.MATERIAL_VPN_KEY); + } + public static IconData vpn_key_off() { + return new IconData(FontImage.MATERIAL_VPN_KEY_OFF); + } + public static IconData vpn_lock() { + return new IconData(FontImage.MATERIAL_VPN_LOCK); + } + public static IconData vrpano() { + return new IconData(FontImage.MATERIAL_VRPANO); + } + public static IconData wallet() { + return new IconData(FontImage.MATERIAL_WALLET); + } + public static IconData wallet_giftcard() { + return new IconData(FontImage.MATERIAL_WALLET_GIFTCARD); + } + public static IconData wallet_membership() { + return new IconData(FontImage.MATERIAL_WALLET_MEMBERSHIP); + } + public static IconData wallet_travel() { + return new IconData(FontImage.MATERIAL_WALLET_TRAVEL); + } + public static IconData wallpaper() { + return new IconData(FontImage.MATERIAL_WALLPAPER); + } + public static IconData warehouse() { + return new IconData(FontImage.MATERIAL_WAREHOUSE); + } + public static IconData warning() { + return new IconData(FontImage.MATERIAL_WARNING); + } + public static IconData warning_amber() { + return new IconData(FontImage.MATERIAL_WARNING_AMBER); + } + public static IconData wash() { + return new IconData(FontImage.MATERIAL_WASH); + } + public static IconData watch() { + return new IconData(FontImage.MATERIAL_WATCH); + } + public static IconData watch_later() { + return new IconData(FontImage.MATERIAL_WATCH_LATER); + } + public static IconData watch_off() { + return new IconData(FontImage.MATERIAL_WATCH_OFF); + } + public static IconData water() { + return new IconData(FontImage.MATERIAL_WATER); + } + public static IconData water_damage() { + return new IconData(FontImage.MATERIAL_WATER_DAMAGE); + } + public static IconData water_drop() { + return new IconData(FontImage.MATERIAL_WATER_DROP); + } + public static IconData waterfall_chart() { + return new IconData(FontImage.MATERIAL_WATERFALL_CHART); + } + public static IconData waves() { + return new IconData(FontImage.MATERIAL_WAVES); + } + public static IconData waving_hand() { + return new IconData(FontImage.MATERIAL_WAVING_HAND); + } + public static IconData wb_auto() { + return new IconData(FontImage.MATERIAL_WB_AUTO); + } + public static IconData wb_cloudy() { + return new IconData(FontImage.MATERIAL_WB_CLOUDY); + } + public static IconData wb_incandescent() { + return new IconData(FontImage.MATERIAL_WB_INCANDESCENT); + } + public static IconData wb_iridescent() { + return new IconData(FontImage.MATERIAL_WB_IRIDESCENT); + } + public static IconData wb_shade() { + return new IconData(FontImage.MATERIAL_WB_SHADE); + } + public static IconData wb_sunny() { + return new IconData(FontImage.MATERIAL_WB_SUNNY); + } + public static IconData wb_twighlight() { + return new IconData(FontImage.MATERIAL_WB_TWIGHLIGHT); + } + public static IconData wb_twilight() { + return new IconData(FontImage.MATERIAL_WB_TWILIGHT); + } + public static IconData wc() { + return new IconData(FontImage.MATERIAL_WC); + } + public static IconData web() { + return new IconData(FontImage.MATERIAL_WEB); + } + public static IconData web_asset_off() { + return new IconData(FontImage.MATERIAL_WEB_ASSET_OFF); + } + public static IconData web_stories() { + return new IconData(FontImage.MATERIAL_WEB_STORIES); + } + public static IconData webhook() { + return new IconData(FontImage.MATERIAL_WEBHOOK); + } + public static IconData wechat() { + return new IconData(FontImage.MATERIAL_WECHAT); + } + public static IconData weekend() { + return new IconData(FontImage.MATERIAL_WEEKEND); + } + public static IconData west() { + return new IconData(FontImage.MATERIAL_WEST); + } + public static IconData whatshot() { + return new IconData(FontImage.MATERIAL_WHATSHOT); + } + public static IconData wheelchair_pickup() { + return new IconData(FontImage.MATERIAL_WHEELCHAIR_PICKUP); + } + public static IconData where_to_vote() { + return new IconData(FontImage.MATERIAL_WHERE_TO_VOTE); + } + public static IconData widgets() { + return new IconData(FontImage.MATERIAL_WIDGETS); + } + public static IconData width_full() { + return new IconData(FontImage.MATERIAL_WIDTH_FULL); + } + public static IconData width_normal() { + return new IconData(FontImage.MATERIAL_WIDTH_NORMAL); + } + public static IconData width_wide() { + return new IconData(FontImage.MATERIAL_WIDTH_WIDE); + } + public static IconData wifi() { + return new IconData(FontImage.MATERIAL_WIFI); + } + public static IconData wifi_1_bar() { + return new IconData(FontImage.MATERIAL_WIFI_1_BAR); + } + public static IconData wifi_2_bar() { + return new IconData(FontImage.MATERIAL_WIFI_2_BAR); + } + public static IconData wifi_calling() { + return new IconData(FontImage.MATERIAL_WIFI_CALLING); + } + public static IconData wifi_calling_3() { + return new IconData(FontImage.MATERIAL_WIFI_CALLING_3); + } + public static IconData wifi_channel() { + return new IconData(FontImage.MATERIAL_WIFI_CHANNEL); + } + public static IconData wifi_find() { + return new IconData(FontImage.MATERIAL_WIFI_FIND); + } + public static IconData wifi_lock() { + return new IconData(FontImage.MATERIAL_WIFI_LOCK); + } + public static IconData wifi_off() { + return new IconData(FontImage.MATERIAL_WIFI_OFF); + } + public static IconData wifi_password() { + return new IconData(FontImage.MATERIAL_WIFI_PASSWORD); + } + public static IconData wifi_protected_setup() { + return new IconData(FontImage.MATERIAL_WIFI_PROTECTED_SETUP); + } + public static IconData wifi_tethering() { + return new IconData(FontImage.MATERIAL_WIFI_TETHERING); + } + public static IconData wifi_tethering_error() { + return new IconData(FontImage.MATERIAL_WIFI_TETHERING_ERROR); + } + public static IconData wifi_tethering_error_rounded() { + return new IconData(FontImage.MATERIAL_WIFI_TETHERING_ERROR_ROUNDED); + } + public static IconData wifi_tethering_off() { + return new IconData(FontImage.MATERIAL_WIFI_TETHERING_OFF); + } + public static IconData wind_power() { + return new IconData(FontImage.MATERIAL_WIND_POWER); + } + public static IconData window() { + return new IconData(FontImage.MATERIAL_WINDOW); + } + public static IconData wine_bar() { + return new IconData(FontImage.MATERIAL_WINE_BAR); + } + public static IconData woman() { + return new IconData(FontImage.MATERIAL_WOMAN); + } + public static IconData woman_2() { + return new IconData(FontImage.MATERIAL_WOMAN_2); + } + public static IconData woo_commerce() { + return new IconData(FontImage.MATERIAL_WOO_COMMERCE); + } + public static IconData wordpress() { + return new IconData(FontImage.MATERIAL_WORDPRESS); + } + public static IconData work() { + return new IconData(FontImage.MATERIAL_WORK); + } + public static IconData work_history() { + return new IconData(FontImage.MATERIAL_WORK_HISTORY); + } + public static IconData work_off() { + return new IconData(FontImage.MATERIAL_WORK_OFF); + } + public static IconData work_outline() { + return new IconData(FontImage.MATERIAL_WORK_OUTLINE); + } + public static IconData workspace_premium() { + return new IconData(FontImage.MATERIAL_WORKSPACE_PREMIUM); + } + public static IconData workspaces() { + return new IconData(FontImage.MATERIAL_WORKSPACES); + } + public static IconData workspaces_filled() { + return new IconData(FontImage.MATERIAL_WORKSPACES_FILLED); + } + public static IconData workspaces_outline() { + return new IconData(FontImage.MATERIAL_WORKSPACES_OUTLINE); + } + public static IconData wrap_text() { + return new IconData(FontImage.MATERIAL_WRAP_TEXT); + } + public static IconData wrong_location() { + return new IconData(FontImage.MATERIAL_WRONG_LOCATION); + } + public static IconData wysiwyg() { + return new IconData(FontImage.MATERIAL_WYSIWYG); + } + public static IconData yard() { + return new IconData(FontImage.MATERIAL_YARD); + } + public static IconData youtube_searched_for() { + return new IconData(FontImage.MATERIAL_YOUTUBE_SEARCHED_FOR); + } + public static IconData zoom_in() { + return new IconData(FontImage.MATERIAL_ZOOM_IN); + } + public static IconData zoom_in_map() { + return new IconData(FontImage.MATERIAL_ZOOM_IN_MAP); + } + public static IconData zoom_out() { + return new IconData(FontImage.MATERIAL_ZOOM_OUT); + } + public static IconData zoom_out_map() { + return new IconData(FontImage.MATERIAL_ZOOM_OUT_MAP); + } } diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/foundation/FoundationLib.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/foundation/FoundationLib.java index 899b4abcc83..320d8ea61f9 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/foundation/FoundationLib.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/foundation/FoundationLib.java @@ -120,4 +120,10 @@ private static TargetPlatform byName(String name) { } return null; } + + /// Flutter's `debugPrint(message, wrapWidth: ...)`: prints the message, as Flutter + /// does outside a test; `wrapWidth` is ignored. + public static void debugPrint(String message, Long wrapWidth) { + dart.runtime.DartRuntime.print(message); + } } diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButton.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButton.java index 1d0e82f6ea4..17376bf26df 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButton.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButton.java @@ -37,7 +37,7 @@ public class BackButton extends IconButton { public BackButton() { - icon(new Icon(Icons.arrow_back)); + icon(new Icon(Icons.arrow_back())); } public BackButton(com.codename1.flutter.Key key, Color color, Funcs.VoidFunc0 onPressed) { diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButtonIcon.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButtonIcon.java index bb2d3abeed5..1b271160e9f 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButtonIcon.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/BackButtonIcon.java @@ -54,7 +54,7 @@ public class BackButtonIcon extends StatelessWidget */ @Override public com.codename1.flutter.IconData iconData() { - return isApplePlatform() ? Icons.arrow_back_ios : Icons.arrow_back; + return isApplePlatform() ? Icons.arrow_back_ios() : Icons.arrow_back(); } /** Whether the platform the app is running on uses the chevron. */ @@ -71,7 +71,7 @@ private static boolean isApplePlatform() { @Override public Widget build(BuildContext context) { - return new Icon(isApplePlatform(context) ? Icons.arrow_back_ios : Icons.arrow_back); + return new Icon(isApplePlatform(context) ? Icons.arrow_back_ios() : Icons.arrow_back()); } /** Whether the ambient theme targets a platform that uses the chevron. */ diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/ButtonRenderElement.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/ButtonRenderElement.java index 1d69f9a8b7d..a445b2eb0f8 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/ButtonRenderElement.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/ButtonRenderElement.java @@ -486,11 +486,18 @@ private void style(Button b) { ButtonStyle st = ((ButtonBase) w).getStyle(); int[] elevatedPad = resolvedPadding(st, vpad, hpad); all.setPadding(elevatedPad[0], elevatedPad[1], elevatedPad[2], elevatedPad[3]); - all.setFgColor(styled(st == null ? null : st.getForegroundColor(), cs.primary()).rgb()); + // FilledButton shares this shape with its own colours and no resting shadow. + boolean filled = w instanceof FilledButton; + boolean tonal = filled && ((FilledButton) w).isTonal(); + com.codename1.flutter.Color fgRole = tonal ? cs.onSecondaryContainer() + : filled ? cs.onPrimary() : cs.primary(); + com.codename1.flutter.Color bgRole = tonal ? cs.secondaryContainer() + : filled ? cs.primary() : cs.surfaceContainerLow(); + all.setFgColor(styled(st == null ? null : st.getForegroundColor(), fgRole).rgb()); RoundBorder border = RoundBorder.create() .rectangle(true) - .color(styled(st == null ? null : st.getBackgroundColor(), cs.surfaceContainerLow()).rgb()); - if (!isElevationZero(st)) { + .color(styled(st == null ? null : st.getBackgroundColor(), bgRole).rgb()); + if (!filled && !isElevationZero(st)) { // Scaled by elevation, the same ElevationShadow mapping every other // elevated surface in this runtime uses (Card, the shaped branch // below) -- a flat shadowOpacity(40) regardless of elevation used to diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/CloseButton.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/CloseButton.java index 5f90fa7747e..9f86fb5d14b 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/CloseButton.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/CloseButton.java @@ -36,7 +36,7 @@ public class CloseButton extends IconButton { public CloseButton() { - icon(new Icon(Icons.close)); + icon(new Icon(Icons.close())); } public CloseButton(com.codename1.flutter.Key key, Color color, Funcs.VoidFunc0 onPressed) { diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/Dialogs.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/Dialogs.java index 5fb9d2a90e4..d281d9c6171 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/Dialogs.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/Dialogs.java @@ -189,6 +189,14 @@ public static int openDialogCount() { return dialogStack.size(); } + /** + * The root element of the topmost open dialog, or null: what a back request + * inspects for PopScope guards, since a dialog is its own route. + */ + public static Element topDialogRoot() { + return dialogStack.isEmpty() ? null : dialogStack.get(dialogStack.size() - 1).root; + } + /** * Test / hot-restart hook: forgets all open dialogs without disposing. */ diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/FilledButton.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/FilledButton.java new file mode 100644 index 00000000000..6158a5f9cb6 --- /dev/null +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/FilledButton.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.material; + +import com.codename1.flutter.Key; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/** + * Material 3's {@code FilledButton}: the elevated button's shape and layout with the + * primary colour as its face and no shadow. {@code FilledButton.tonal} uses the + * secondary container colours instead. See ButtonRenderElement's elevated branch. + */ +public class FilledButton extends ElevatedButton { + private boolean tonal; + + public FilledButton() { + } + + /** Whether this is the tonal variant. */ + public boolean isTonal() { + return tonal; + } + + public static FilledButton tonal(Key key, Funcs.VoidFunc0 onPressed, ButtonStyle style, Widget child) { + FilledButton b = new FilledButton(); + b.tonal = true; + b.key(key); + b.onPressed(onPressed); + b.style(style); + b.child(child); + return b; + } + + public static FilledButton icon(Key key, Funcs.VoidFunc0 onPressed, ButtonStyle style, + Widget icon, Widget label) { + FilledButton b = new FilledButton(); + b.key(key); + b.onPressed(onPressed); + b.style(style); + b.child(label != null ? label : icon); + if (label != null) { + b.leadingIcon(icon); + } + return b; + } +} diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/MaterialApp.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/MaterialApp.java index fa4182d356d..2ccb8a75822 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/MaterialApp.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/MaterialApp.java @@ -84,6 +84,41 @@ public com.codename1.flutter.navigation.Navigator.RouteTable routeTable() { private Funcs.Func1 onUnknownRoute; private ScrollBehavior scrollBehavior; private Funcs.Func2, DartIterable, Locale> localeListResolutionCallback; + private com.codename1.flutter.navigation.RouterConfigSource routerConfig; + + /** + * Flutter's {@code MaterialApp.router(routerConfig: ...)}. The router stands in + * for the route table: it generates every route from its location, and the app + * opens on the router's initial location. Arguments are positional here, in the + * order the stub declares them. + */ + public static MaterialApp router(Object key, Object routerConfig, String title, ThemeData theme, + ThemeData darkTheme, ThemeMode themeMode, Boolean debugShowCheckedModeBanner, + Object localizationsDelegates, Object supportedLocales, Locale locale) { + MaterialApp app = new MaterialApp(); + app.routerConfig(routerConfig); + app.title(title); + app.theme(theme); + app.darkTheme(darkTheme); + app.themeMode(themeMode); + if (debugShowCheckedModeBanner != null) { + app.debugShowCheckedModeBanner(debugShowCheckedModeBanner.booleanValue()); + } + app.localizationsDelegates(localizationsDelegates); + app.supportedLocales(supportedLocales); + app.locale(locale); + return app; + } + + /** See {@link #router}. A value that is not a router is ignored. */ + public void routerConfig(Object v) { + this.routerConfig = v instanceof com.codename1.flutter.navigation.RouterConfigSource + ? (com.codename1.flutter.navigation.RouterConfigSource) v : null; + if (routerConfig != null) { + this.onGenerateRoute = routerConfig.routeGenerator(); + this.initialRoute = routerConfig.initialLocation(); + } + } public void title(String v) { this.title = v; @@ -316,6 +351,9 @@ public Widget build(BuildContext context) { // meant opening a study permanently replaced the gallery's. boolean rootApp = com.codename1.flutter.navigation.Navigator.installRouteTable( context, routes, onGenerateRoute, onUnknownRoute); + if (routerConfig != null) { + routerConfig.attached(context); + } Widget content = home; // A routing-based app (no home widget) renders its initial route — Flutter diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PaginatedDataTable.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PaginatedDataTable.java index b081a749e26..3cbea03d361 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PaginatedDataTable.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PaginatedDataTable.java @@ -208,8 +208,8 @@ private Widget headingCell(final int index) { parts.add(label); com.codename1.flutter.widgets.Icon arrow = new com.codename1.flutter.widgets.Icon( isSortAscending() - ? com.codename1.flutter.Icons.keyboard_arrow_up - : com.codename1.flutter.Icons.keyboard_arrow_down); + ? com.codename1.flutter.Icons.keyboard_arrow_up() + : com.codename1.flutter.Icons.keyboard_arrow_down()); arrow.size(16); parts.add(arrow); Row withArrow = new Row(); @@ -248,9 +248,9 @@ private Widget footer(long total) { com.codename1.flutter.widgets.Text range = new com.codename1.flutter.widgets.Text( (total == 0 ? 0 : first + 1) + "-" + last + " of " + total); parts.add(range); - parts.add(pageButton(com.codename1.flutter.Icons.chevron_left, + parts.add(pageButton(com.codename1.flutter.Icons.chevron_left(), first > 0, Math.max(0, first - rowsPerPage))); - parts.add(pageButton(com.codename1.flutter.Icons.chevron_right, + parts.add(pageButton(com.codename1.flutter.Icons.chevron_right(), last < total, first + rowsPerPage)); Row row = new Row(); diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PopupMenuButton.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PopupMenuButton.java index a9fba9767cb..d9431734f27 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PopupMenuButton.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/PopupMenuButton.java @@ -176,8 +176,8 @@ public Widget effectiveTrigger(com.codename1.flutter.BuildContext context) { boolean apple = p == com.codename1.flutter.TargetPlatform.iOS || p == com.codename1.flutter.TargetPlatform.macOS; return new com.codename1.flutter.widgets.Icon(apple - ? com.codename1.flutter.Icons.more_horiz - : com.codename1.flutter.Icons.more_vert); + ? com.codename1.flutter.Icons.more_horiz() + : com.codename1.flutter.Icons.more_vert()); } @Override diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/TextFieldRenderElement.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/TextFieldRenderElement.java index 0f50868d858..32c1c507f0a 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/TextFieldRenderElement.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/material/TextFieldRenderElement.java @@ -236,8 +236,8 @@ public void actionPerformed(ActionEvent evt) { *

{@code decorated()} only runs once, on mount ({@code createComponent()}), * and {@code updateComponent} used to call only {@link #apply} -- never * touching the row it built. So a rebuild that swapped the decoration's icon - * -- the demo's password field toggling between {@code Icons.visibility} and - * {@code Icons.visibility_off} -- kept drawing whichever glyph happened to be + * -- the demo's password field toggling between {@code Icons.visibility()} and + * {@code Icons.visibility_off()} -- kept drawing whichever glyph happened to be * current the one time the row was built, and an {@code IconButton} that * appears or disappears across rebuilds did nothing either way.

* diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/Navigator.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/Navigator.java index ffcfdc0c84b..2ab0f21f085 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/Navigator.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/Navigator.java @@ -187,7 +187,8 @@ public static dart.async.Future push(BuildContext context, Route route) tb.setBackCommand("", new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { - pop(null); + // a request, as Flutter's back button is: PopScope may veto it + maybePop(null, null); } }); } @@ -212,6 +213,11 @@ public void actionPerformed(ActionEvent evt) { * so with a dialog over the base route maybePop answered false without closing it * and a system-back handler could not dismiss the dialog. */ + /** Whether a pop would remove anything: a pushed page or an open dialog. */ + public static boolean canPopAnything() { + return poppable(); + } + static boolean poppable() { return com.codename1.flutter.material.Dialogs.openDialogCount() > 0 || !stack.isEmpty(); } @@ -229,11 +235,22 @@ public static void pop(BuildContext context) { * does and completes the popped route's future with {@code result}. */ public static void pop(BuildContext context, Object result) { - if (Dialogs.popTopDialog(result)) { - return; + // An explicit pop skips canPop, but its PopScopes still hear that it happened + // (onPopInvokedWithResult(true, result)). Collected first: the pop unmounts them. + List scopes; + if (Dialogs.openDialogCount() > 0) { + scopes = PopGuards.collect(Dialogs.topDialogRoot()); + if (Dialogs.popTopDialog(result)) { + PopGuards.notifyPopScopes(scopes, true, result); + return; + } } + scopes = stack.isEmpty() ? null : PopGuards.collect(stack.get(stack.size() - 1).rootElement); RouteEntry e = popPage(); if (e != null) { + if (scopes != null) { + PopGuards.notifyPopScopes(scopes, true, result); + } e.popped.complete(result); } } @@ -288,6 +305,12 @@ static Form formAt(int i) { return stack.get(i).form; } + /// Test hook: headless, a page never mounts, so a test mounts one itself and hands + /// its root here for maybePop's PopScope/WillPopScope lookup. + static void attachTopRootForTest(Element root) { + stack.get(stack.size() - 1).rootElement = root; + } + static String routeNameAt(int i) { Route r = stack.get(i).route; return r == null || r.settings() == null ? null : r.settings().name(); @@ -651,8 +674,7 @@ public dart.async.Future maybePop(Object result) { if (!poppable()) { return dart.async.Future.value(Boolean.FALSE); } - Navigator.pop(null, result); - return dart.async.Future.value(Boolean.TRUE); + return Navigator.maybePop(null, result); } @Override @@ -856,8 +878,7 @@ public dart.async.Future maybePop(Object result) { if (!poppable()) { return dart.async.Future.value(Boolean.FALSE); } - Navigator.pop(context, result); - return dart.async.Future.value(Boolean.TRUE); + return Navigator.maybePop(context, result); } @Override @@ -931,12 +952,47 @@ public static dart.async.Future maybePop(BuildContext context) { * {@link #maybePop(BuildContext)} with the value the popped route's future completes * with, when something was popped -- Flutter's optional {@code result} parameter. */ - public static dart.async.Future maybePop(BuildContext context, Object result) { + public static dart.async.Future maybePop(final BuildContext context, final Object result) { if (!poppable()) { return dart.async.Future.value(Boolean.FALSE); } - pop(context, result); - return dart.async.Future.value(Boolean.TRUE); + final boolean dialog = Dialogs.openDialogCount() > 0; + if (!dialog && stack.isEmpty()) { + pop(context, result); + return dart.async.Future.value(Boolean.TRUE); + } + // The route's PopScope/WillPopScope widgets decide first -- a back press and + // maybePop are requests, unlike pop. A dialog is itself the top route, so its + // own guards are the ones asked (PopScope(canPop: false) in a dialog blocks it). + final RouteEntry top = dialog ? null : stack.get(stack.size() - 1); + final Element dialogRoot = dialog ? Dialogs.topDialogRoot() : null; + final List scopes = PopGuards.collect(dialog ? dialogRoot : top.rootElement); + if (scopes.isEmpty()) { + pop(context, result); + return dart.async.Future.value(Boolean.TRUE); + } + final dart.async.Completer done = new dart.async.Completer(); + PopGuards.mayPop(scopes, result).then(new dart.runtime.Funcs.VoidFunc1() { + @Override + public void call(Boolean ok) { + // still the top route: a guard that awaited a dialog must not pop the page beneath + boolean pop = Boolean.TRUE.equals(ok) && (dialog + ? Dialogs.openDialogCount() > 0 && Dialogs.topDialogRoot() == dialogRoot + : !stack.isEmpty() && stack.get(stack.size() - 1) == top); + if (pop) { + pop(context, result); + } + // one onPopInvoked per request: a pop reports itself (didPop true) from + // pop(); a refusal, a WillPopScope's included, reports didPop false here + if (!pop) { + PopGuards.notifyPopScopes(scopes, false, result); + } + // true either way: a route that declines to pop has handled the request + // (Flutter's doNotPop); false is reserved for "nothing here, bubble up". + done.complete(Boolean.TRUE); + } + }, null); + return done.future(); } private static final class RouteEntry { diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/PopGuards.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/PopGuards.java new file mode 100644 index 00000000000..792c095a110 --- /dev/null +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/PopGuards.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.navigation; + +import java.util.ArrayList; +import java.util.List; + +import com.codename1.flutter.Element; +import com.codename1.flutter.Widget; +import com.codename1.flutter.widgets.PopScope; +import com.codename1.flutter.widgets.WillPopScope; + +import dart.async.Completer; +import dart.async.Future; +import dart.runtime.Funcs; + +/** + * The PopScope and WillPopScope widgets in a route, asked before a {@code maybePop} or a + * back press closes it. Both used to be accepted and ignored, so a "discard changes?" + * guard never ran and the page closed under it. + */ +final class PopGuards { + private PopGuards() { + } + + /** Every PopScope and WillPopScope mounted under {@code root}, outermost first. */ + static List collect(Element root) { + final List out = new ArrayList(); + if (root != null) { + walk(root, out); + } + return out; + } + + private static void walk(Element e, final List out) { + Widget w = e.widget(); + if (w instanceof PopScope || w instanceof WillPopScope) { + out.add(w); + } + e.visitChildren(new Funcs.VoidFunc1() { + @Override + public void call(Element child) { + walk(child, out); + } + }); + } + + /** + * Whether the route may close: false at once if any PopScope says {@code canPop: false} + * (each scope then hears the attempt), else the answer of every WillPopScope in turn. + */ + static Future mayPop(final List scopes, Object result) { + boolean vetoed = false; + for (Widget w : scopes) { + if (w instanceof PopScope && !((PopScope) w).getCanPop()) { + vetoed = true; + } + } + // The caller reports the outcome to the PopScopes exactly once, whichever guard + // refused -- a WillPopScope veto included -- so nothing is notified here. + if (vetoed) { + return Future.value(Boolean.FALSE); + } + Completer done = new Completer(); + askWillPop(scopes, 0, done); + return done.future(); + } + + static void notifyPopScopes(List scopes, boolean didPop, Object result) { + for (Widget w : scopes) { + if (w instanceof PopScope) { + ((PopScope) w).invoked(didPop, result); + } + } + } + + private static void askWillPop(final List scopes, int from, final Completer done) { + for (int i = from; i < scopes.size(); i++) { + Widget w = scopes.get(i); + if (!(w instanceof WillPopScope) || ((WillPopScope) w).getOnWillPop() == null) { + continue; + } + Object answer = ((WillPopScope) w).getOnWillPop().call(); + final int next = i + 1; + if (answer instanceof Future) { + asObjectFuture(answer).then(new Funcs.VoidFunc1() { + @Override + public void call(Object ok) { + if (Boolean.TRUE.equals(ok)) { + askWillPop(scopes, next, done); + } else { + done.complete(Boolean.FALSE); + } + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object err) { + done.complete(Boolean.FALSE); + } + }); + return; + } + if (!Boolean.TRUE.equals(answer)) { + done.complete(Boolean.FALSE); + return; + } + } + done.complete(Boolean.TRUE); + } + + @SuppressWarnings("unchecked") + private static Future asObjectFuture(Object f) { + return (Future) f; + } +} diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/RouterConfigSource.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/RouterConfigSource.java new file mode 100644 index 00000000000..18dbcc3b317 --- /dev/null +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/navigation/RouterConfigSource.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.navigation; + +import com.codename1.flutter.BuildContext; + +import dart.runtime.Funcs; + +/** + * What {@code MaterialApp.router(routerConfig: ...)} accepts: a router that turns a + * location into a route and names the location the app opens on. go_router's + * {@code GoRouter} implements it. + */ +public interface RouterConfigSource { + + /** Resolves a location (carried as the route settings' name) into a route. */ + Funcs.Func1 routeGenerator(); + + /** The location shown first. */ + String initialLocation(); + + /** Called once the app holding this router has been built. */ + void attached(BuildContext context); +} diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProvider.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProvider.java index d7d51381fda..89249837a85 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProvider.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProvider.java @@ -34,6 +34,18 @@ */ public class ChangeNotifierProvider extends Provider { + /// The notifier `create` made belongs to this provider, so it is disposed with it + /// (overridden disposers stop their timers and streams), as provider does. A + /// `.value` notifier is not created here and so never reaches this. + @Override + protected void disposeCreated(com.codename1.flutter.BuildContext context, Object created) { + super.disposeCreated(context, created); + if (created instanceof com.codename1.flutter.foundation.ChangeNotifier) { + ((com.codename1.flutter.foundation.ChangeNotifier) created).dispose(); + } + } + + /** * Subscribes to the model and rebuilds this subtree when it notifies. * diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProviderElement.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProviderElement.java index 61d6f633581..3743a6fa356 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProviderElement.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ChangeNotifierProviderElement.java @@ -81,13 +81,21 @@ public void unmount() { } private void subscribe() { - Object value = widget() instanceof Provider ? ((Provider) widget()).getValue() : null; - if (value instanceof Listenable) { + // The field: a lazy value not read yet has nothing to listen to, and + // subscribing must not create it. onCreated subscribes once it exists. + Object value = widget() instanceof Provider ? ((Provider) widget()).value : null; + if (value instanceof Listenable && listened != value) { + unsubscribe(); listened = (Listenable) value; listened.addListener(handler); } } + @Override + protected void onCreated(Object value) { + subscribe(); + } + private void unsubscribe() { if (listened != null) { listened.removeListener(handler); diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/Provider.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/Provider.java index 68559ed785a..c19710bf9d8 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/Provider.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/Provider.java @@ -28,6 +28,8 @@ import com.codename1.flutter.Key; import com.codename1.flutter.Widget; +import dart.runtime.Dyn; + /** * provider's {@code Provider}: publishes {@code value} to its subtree by * runtime type. {@link #of(BuildContext, boolean, Class)} walks the element tree @@ -35,8 +37,8 @@ * Dart {@code Provider.of(context)} witness is threaded in as {@code type} by * the transpiler. * - *

The {@code create} form (a lazily-invoked factory) is accepted for API - * shape but not exercised by the gallery, which uses the {@code .value} form.

+ *

The {@code create} form is run once by the provider's element, with the + * element as its context, and the value it returns is disposed with it.

*/ public class Provider extends SingleChildWidget implements InheritedValueProvider { @@ -54,11 +56,45 @@ public void create(Object v) { this.create = v; } + /** + * {@code create: (context) { ... }} written as a block or cascade: the lambda needs + * a functional type to be a lambda at all, which the Object overload cannot give it. + */ + public void create(dart.runtime.Funcs.Func1 v) { + this.create = v; + } + public void lazy(boolean v) { this.lazy = v; } + protected Object dispose; + + /// provider's `dispose: (context, value) => ...`, run when the element that + /// created the value leaves the tree. + public void dispose(Object v) { + this.dispose = v; + } + + /// Releases a value this provider's `create` made. Only a created value: one + /// handed in through `.value` belongs to whoever made it. + protected void disposeCreated(BuildContext context, Object created) { + if (dispose != null) { + Dyn.invoke(dispose, context, created); + } + } + + /// The element that runs `create` the first time something reads this provider: + /// provider's `lazy` (the default) defers creation until the first lookup, so an + /// unread provider never starts its requests or allocates its bloc. + ProviderElement pendingCreator; + public Object getValue() { + if (value == null && pendingCreator != null) { + ProviderElement e = pendingCreator; + pendingCreator = null; + e.createNow(this); + } return value; } @@ -80,7 +116,7 @@ public com.codename1.flutter.Element createElement() { @Override public Object providedValueFor(Class type) { - return providesType(type) ? value : null; + return providesType(type) ? getValue() : null; } @Override @@ -88,6 +124,10 @@ public boolean providesType(Class type) { if (type == null) { return false; } + if (value == null && providedType == null && pendingCreator != null) { + // Nothing but the value can say what an untyped provider provides. + getValue(); + } if (value != null) { return type.isInstance(value); } diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ProviderElement.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ProviderElement.java index 93a7e4d8c48..8607b77e364 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ProviderElement.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/ProviderElement.java @@ -28,6 +28,7 @@ import com.codename1.flutter.Widget; import dart.runtime.DartRuntime; +import dart.runtime.Dyn; /** * Element for a {@link Provider}: when a rebuild hands it a different value, the widgets @@ -43,16 +44,81 @@ public ProviderElement(StatelessWidget widget) { super(widget); } + /// The value this element made by calling the provider's `create`, kept across + /// rebuilds: the parent hands down a new Provider widget every build, and Flutter + /// creates once per element, not once per widget. + private Object created; + private boolean hasCreated; + + @Override + protected void firstBuild() { + // After Element.mount linked the parent, so create(context) can read the + // providers above it -- `create: (c) => Cubit(c.read())`. + supplyCreated(widget()); + super.firstBuild(); + } + + private void supplyCreated(Widget w) { + if (!(w instanceof Provider)) { + return; + } + Provider p = (Provider) w; + if (p.getValue() != null || p.create == null) { + return; + } + if (hasCreated) { + p.value(created); + } else if (p.lazy) { + p.pendingCreator = this; + } else { + createNow(p); + } + } + + /// Runs `create` for {@code p}: during the first build when `lazy: false`, else + /// on the first lookup (see {@link Provider#getValue()}). + void createNow(Provider p) { + if (!hasCreated) { + created = Dyn.invoke(p.create, this); + hasCreated = true; + } + p.value(created); + onCreated(created); + } + + /// The value now exists; a subclass that listens to it subscribes here. + protected void onCreated(Object value) { + } + + @Override + public void unmount() { + forgetPending(widget()); + if (hasCreated && widget() instanceof Provider) { + ((Provider) widget()).disposeCreated(this, created); + } + super.unmount(); + } + @Override public void update(Widget newWidget) { Object before = valueOf(widget()); + forgetPending(widget()); + supplyCreated(newWidget); super.update(newWidget); if (!DartRuntime.eq(before, valueOf(widget()))) { rebuildProviderDependents(); } } + /// An old or unmounted widget must not create through this element any more. + private void forgetPending(Widget w) { + if (w instanceof Provider && ((Provider) w).pendingCreator == this) { + ((Provider) w).pendingCreator = null; + } + } + private static Object valueOf(Widget w) { - return w instanceof Provider ? ((Provider) w).getValue() : null; + // The field, not getValue(): comparing must not create a lazy value. + return w instanceof Provider ? ((Provider) w).value : null; } } diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/FutureBuilder.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/FutureBuilder.java index 6d80b02b792..a0c915a8833 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/FutureBuilder.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/FutureBuilder.java @@ -40,7 +40,7 @@ public class FutureBuilder extends StatelessWidget { private Object future; private T initialData; - private Funcs.Func2 builder; + private Funcs.Func2, Widget> builder; public void future(Object v) { this.future = v; @@ -50,7 +50,7 @@ public void initialData(T v) { this.initialData = v; } - public void builder(Funcs.Func2 v) { + public void builder(Funcs.Func2, Widget> v) { this.builder = v; } @@ -73,6 +73,7 @@ public Widget build(BuildContext context) { ? new AsyncSnapshot(ConnectionState.none, initialData, null, null) : new AsyncSnapshot()); } + @SuppressWarnings("unchecked") Widget buildWith(BuildContext context, AsyncSnapshot snapshot) { return builder == null ? null : builder.call(context, snapshot); } diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/Image.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/Image.java index a7eec280f5b..60bd9ce0b1c 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/Image.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/Image.java @@ -199,6 +199,15 @@ public static Image network(String src, Key key, Double width, Double height, Bo return i; } + /// {@link #network} with request headers, for Java-side wrappers such as + /// CachedNetworkImage(httpHeaders:) that have no NetworkImage provider to carry them. + public static Image networkWithHeaders(String src, Key key, Double width, Double height, BoxFit fit, + java.util.Map headers) { + Image i = network(src, key, width, height, fit); + i.headers = headers == null || headers.isEmpty() ? null : headers; + return i; + } + /// The decode size a ResizeImage asked for, in raw pixels, or null. private Long resizeWidthPx; private Long resizeHeightPx; diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/PopScope.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/PopScope.java new file mode 100644 index 00000000000..3180354b50d --- /dev/null +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/PopScope.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.widgets; + +import com.codename1.flutter.Element; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/** + * Flutter's PopScope: {@code canPop: false} vetoes a back gesture or {@code maybePop} + * on the route it sits in, and {@code onPopInvokedWithResult} (or the older + * {@code onPopInvoked}) hears every attempt, vetoed or not. {@code Navigator.pop} itself + * is never vetoed, as in Flutter. + */ +public class PopScope extends Widget implements HasChild { + + private Widget child; + private boolean canPop = true; + private Funcs.VoidFunc1 onPopInvoked; + private Funcs.VoidFunc2 onPopInvokedWithResult; + + public void child(Widget v) { + this.child = v; + } + + public void canPop(boolean v) { + this.canPop = v; + } + + public void onPopInvoked(Funcs.VoidFunc1 v) { + this.onPopInvoked = v; + } + + public void onPopInvokedWithResult(Funcs.VoidFunc2 v) { + this.onPopInvokedWithResult = v; + } + + public boolean getCanPop() { + return canPop; + } + + /** Tells the app a pop was attempted; {@code didPop} is false when this scope vetoed it. */ + public void invoked(boolean didPop, Object result) { + if (onPopInvokedWithResult != null) { + onPopInvokedWithResult.call(Boolean.valueOf(didPop), result); + } else if (onPopInvoked != null) { + onPopInvoked.call(Boolean.valueOf(didPop)); + } + } + + @Override + public Widget getChild() { + return child; + } + + @Override + public Element createElement() { + return new PassThroughRenderElement(this); + } +} diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/StreamBuilder.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/StreamBuilder.java new file mode 100644 index 00000000000..d15cb3cdee6 --- /dev/null +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/StreamBuilder.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.widgets; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.StatelessWidget; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/** + * Flutter's {@code StreamBuilder}: rebuilds from the latest event of a Stream -- + * {@code waiting} until the first, {@code active} after each, {@code done} once the + * stream closes. The usual home of {@code FirebaseAuth.authStateChanges()}. + */ +public class StreamBuilder extends StatelessWidget { + private Object stream; + private T initialData; + private Funcs.Func2, Widget> builder; + + public void stream(Object v) { + this.stream = v; + } + + public void initialData(T v) { + this.initialData = v; + } + + public void builder(Funcs.Func2, Widget> v) { + this.builder = v; + } + + Object getStream() { + return stream; + } + + T getInitialData() { + return initialData; + } + + @Override + public com.codename1.flutter.Element createElement() { + return new StreamBuilderElement(this); + } + + @Override + public Widget build(BuildContext context) { + return buildWith(context, new AsyncSnapshot(ConnectionState.none, initialData, null, null)); + } + + @SuppressWarnings("unchecked") + Widget buildWith(BuildContext context, AsyncSnapshot snapshot) { + return builder == null ? null : builder.call(context, snapshot); + } +} diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/StreamBuilderElement.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/StreamBuilderElement.java new file mode 100644 index 00000000000..5ab17e8a2a3 --- /dev/null +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/StreamBuilderElement.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.widgets; + +import com.codename1.flutter.ComposedElement; +import com.codename1.flutter.Element; +import com.codename1.flutter.Widget; + +import dart.async.Stream; +import dart.async.StreamSubscription; +import dart.runtime.Funcs; + +/** + * Holds the subscription behind a {@link StreamBuilder}. A new stream on rebuild + * cancels the old subscription and keeps the last data, as Flutter's does; unmount + * cancels it. + */ +public class StreamBuilderElement extends ComposedElement { + + private AsyncSnapshot snapshot; + private Object subscribedTo; + private StreamSubscription subscription; + /** Identifies the live subscription; an event carrying another one is stale. */ + private Object activeToken; + private boolean subscribing; + + public StreamBuilderElement(StreamBuilder widget) { + super(widget); + } + + @Override + public void mount(Element parent, int slot) { + StreamBuilder w = (StreamBuilder) widget(); + snapshot = new AsyncSnapshot(ConnectionState.none, w.getInitialData(), null, null); + subscribe(w.getStream()); + super.mount(parent, slot); + } + + @Override + public void update(Widget newWidget) { + Object next = ((StreamBuilder) newWidget).getStream(); + if (next != subscribedTo) { + cancel(); + // The error is kept on purpose: Flutter's StreamBuilderBase moves the snapshot + // with AsyncSnapshot.inState(none) and then inState(waiting), and inState + // carries data, error and stackTrace alike. Clearing it here would make a + // builder that tests hasError render differently than it does on Flutter. + snapshot = new AsyncSnapshot(ConnectionState.none, snapshot.data(), snapshot.error(), + snapshot.stackTrace()); + subscribe(next); + } + super.update(newWidget); + } + + @Override + public void unmount() { + cancel(); + subscribedTo = null; + super.unmount(); + } + + private void cancel() { + activeToken = null; + if (subscription != null) { + subscription.cancel(); + subscription = null; + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void subscribe(Object stream) { + subscribedTo = stream; + if (!(stream instanceof Stream)) { + return; + } + final Object token = new Object(); + activeToken = token; + snapshot = new AsyncSnapshot(ConnectionState.waiting, snapshot.data(), snapshot.error(), + snapshot.stackTrace()); + subscribing = true; + try { + subscription = ((Stream) stream).listen(new Funcs.VoidFunc1() { + @Override + public void call(Object value) { + settle(token, new AsyncSnapshot(ConnectionState.active, value, null, null)); + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object error) { + settle(token, new AsyncSnapshot(ConnectionState.active, null, error, null)); + } + }, new Funcs.VoidFunc0() { + @Override + public void call() { + AsyncSnapshot last = snapshot; + settle(token, new AsyncSnapshot(ConnectionState.done, last.data(), last.error(), + last.stackTrace())); + } + }); + } finally { + subscribing = false; + } + } + + private void settle(final Object token, final AsyncSnapshot result) { + if (subscribing) { + // a stream that emits as it is listened to (authStateChanges does) answers + // before this element has built: build with that event + if (activeToken == token) { + snapshot = result; + } + return; + } + Runnable apply = new Runnable() { + @Override + public void run() { + if (activeToken != token) { + return; // a replaced stream, or an unmounted element + } + snapshot = result; + markNeedsBuild(); + } + }; + if (com.codename1.ui.Display.isInitialized() && !com.codename1.ui.CN.isEdt()) { + com.codename1.ui.CN.callSerially(apply); + } else { + apply.run(); + } + } + + @Override + protected Widget build() { + return ((StreamBuilder) widget()).buildWith(this, snapshot); + } +} diff --git a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/WidgetsBinding.java b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/WidgetsBinding.java index e488c6b30c5..1a2e84dd77b 100644 --- a/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/WidgetsBinding.java +++ b/maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/WidgetsBinding.java @@ -50,11 +50,26 @@ public static final class PlatformDispatcher { /** The primary display's device-pixel ratio. */ public double devicePixelRatio = 1.0; + + /** Dart's {@code platformBrightness} getter, as the typed stub calls it. */ + public Brightness platformBrightness() { + return platformBrightness; + } + + /** Dart's {@code devicePixelRatio} getter. */ + public double devicePixelRatio() { + return devicePixelRatio; + } } /** Dart's {@code WidgetsBinding.instance}. */ public static final WidgetsBinding instance = new WidgetsBinding(); + /** {@code WidgetsFlutterBinding.ensureInitialized()}: the Display is up before main() runs. */ + public static WidgetsBinding ensureInitialized() { + return instance; + } + private final PlatformDispatcher platformDispatcher = new PlatformDispatcher(); private WidgetsBinding() { diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/applinks/AppLinks.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/applinks/AppLinks.java new file mode 100644 index 00000000000..285e07708dd --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/applinks/AppLinks.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.applinks; + +import com.codename1.ui.Display; + +import dart.async.Future; +import dart.async.Stream; +import dart.async.StreamController; +import dart.core.DartUri; + +/** + * app_links's {@code AppLinks}, over the {@code AppArg} property through which Codename + * One hands an app the URL that opened it. The imported app's entry point calls + * {@link #appStarted} from {@code Lifecycle.start()}, so a link that resumes a running + * app arrives on the streams as well as the first one. + */ +public class AppLinks { + private static final StreamController LINKS = StreamController.broadcast(); + private static String initial; + private static boolean initialRead; + /// The most recent link received, launch or resume; getLatestLink answers it. + private static String latest; + + public AppLinks() { + } + + /** Takes the pending {@code AppArg}, if any, and publishes it. */ + public static void appStarted() { + if (!Display.isInitialized()) { + return; + } + String arg = Display.getInstance().getProperty("AppArg", null); + // Only the first start can carry the launch link: a cold start without one + // settles the slot, so a later resume link is never replayed as initial. + boolean first = !initialRead; + initialRead = true; + if (arg == null || arg.length() == 0) { + return; + } + Display.getInstance().setProperty("AppArg", null); + if (first) { + initial = arg; + } + latest = arg; + LINKS.add(arg); + } + + private static String initialLink() { + if (!initialRead) { + appStarted(); + } + return initial; + } + + public Future getInitialLink() { + String s = initialLink(); + return Future.value(s == null ? null : DartUri.parse(s)); + } + + public Future getInitialLinkString() { + return Future.value(initialLink()); + } + + public Future getLatestLink() { + initialLink(); + return Future.value(latest == null ? null : DartUri.parse(latest)); + } + + public Future getLatestLinkString() { + initialLink(); + return Future.value(latest); + } + + public Stream stringLinkStream() { + return LINKS.stream(); + } + + public Stream uriLinkStream() { + return LINKS.stream().map(new dart.runtime.Funcs.Func1() { + @Override + public DartUri call(String s) { + return DartUri.parse(s); + } + }); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Bloc.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Bloc.java new file mode 100644 index 00000000000..a0630cccfa9 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Bloc.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import java.util.ArrayList; +import java.util.List; + +import dart.async.Future; +import dart.runtime.Dyn; +import dart.runtime.Funcs; + +/// bloc's `Bloc`: events go in through [#add], and the handler +/// registered for that event type with [#on] emits the states that come out. +/// +/// The Dart `on` type argument is erased in Java, so the transpiler passes it +/// as `type` (see the stub's `witness` parameter). A handler registered without +/// one -- `on` inside a generic class -- sees every event. +public abstract class Bloc extends BlocBase { + + /// `Bloc.observer`: the app-wide [BlocObserver]. + public static BlocObserver observer = new BlocObserver(); + + private final List> types = new ArrayList>(); + private final List handlers = new ArrayList(); + + protected Bloc(S initialState) { + super(initialState); + } + + /// `on(handler)`. The handler answers whatever its body does -- a Future for an + /// async one -- so it is typed to return Object; Emitter.call answers null so an arrow + /// handler that only emits fits too. + public void on(Funcs.Func2, Object> handler, Class type) { + types.add(type); + handlers.add(handler); + } + + public void on(Funcs.Func2, Object> handler, Object transformer, Class type) { + on(handler, type); + } + + public void add(E event) { + if (isClosed()) { + throw new dart.core.StateError("Cannot add new events after calling close"); + } + onEvent(event); + boolean handled = false; + for (int i = 0; i < handlers.size(); i++) { + Class t = types.get(i); + if (t != null && !t.isInstance(event)) { + continue; + } + handled = true; + final Emitter emitter = new Emitter(this, event); + synchronized (emitters) { + emitters.add(emitter); + } + Object result; + try { + result = Dyn.invoke(handlers.get(i), event, emitter); + } catch (RuntimeException err) { + emitter.complete(); + onError(err, null); + // bloc's add only enqueues: a handler's error goes to onError and the + // zone, never to the code that called add (a gesture callback, say). + reportUncaught(err); + continue; + } + if (result instanceof Future) { + // A handler that fails later reaches onError (and the observer) as a + // synchronous throw does; nothing else would ever see that future's error. + @SuppressWarnings("unchecked") + Future pending = (Future) result; + pending.then(new dart.runtime.Funcs.VoidFunc1() { + @Override + public void call(Object v) { + emitter.complete(); + } + }, new dart.runtime.Funcs.VoidFunc1() { + @Override + public void call(Object err) { + emitter.complete(); + onError(err, null); + // Bloc notifies onError and then rethrows into the zone. Throwing + // here would only fail the future then() returns, which nobody + // holds, so report it as uncaught the way the zone would. + reportUncaught(dart.runtime.DartRuntime.asError(err)); + } + }); + } else { + emitter.complete(); + } + } + if (!handled) { + dart.core.StateError err = new dart.core.StateError("add(" + event + ") was called without a registered " + + "event handler. Make sure to register a handler via on<" + + (event == null ? "?" : event.getClass().getSimpleName()) + ">((event, emit) {...})"); + onError(err, null); + // Bloc notifies onError and then throws the StateError at the caller of add. + throw err; + } + } + + /** + * Logs an asynchronous handler failure that nothing awaits. Logging needs an + * installed implementation, which a headless test lacks, and reporting must + * never throw. + */ + private static void reportUncaught(Throwable t) { + try { + com.codename1.io.Log.e(t); + } catch (Throwable loggingFailed) { + // Nothing to log through; nothing more to do. + } + } + + /// The handlers' emitters. Completing one detaches it; [#close] completes the rest, + /// which cancels any `emit.forEach`/`onEach` subscription still running, so a + /// closed bloc does not keep a periodic source alive. + private final List> emitters = new ArrayList>(); + + void emitterDone(Emitter e) { + synchronized (emitters) { + emitters.remove(e); + } + } + + @Override + public Future close() { + List> live; + synchronized (emitters) { + live = new ArrayList>(emitters); + emitters.clear(); + } + for (Emitter e : live) { + e.complete(); + } + return super.close(); + } + + public void onEvent(E event) { + observer.onEvent(this, event); + } + + public void onTransition(Transition transition) { + observer.onTransition(this, transition); + } + + @SuppressWarnings("unchecked") + void emitTransition(Object event, S next) { + if (isClosed()) { + return; + } + if (!dart.runtime.DartRuntime.eq(next, state())) { + onTransition(new Transition(state(), (E) event, next)); + } + emit(next); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocBase.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocBase.java new file mode 100644 index 00000000000..c5f41bdbc9b --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocBase.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.foundation.ChangeNotifier; + +import dart.async.Future; +import dart.async.Stream; +import dart.async.StreamController; +import dart.core.StateError; +import dart.runtime.DartRuntime; + +/// bloc's `BlocBase`: the current state, the stream of later states, and +/// `emit`. It is also a [ChangeNotifier], so the provider machinery the runtime +/// already has -- `context.watch`, `context.select`, rebuilding readers -- works on a +/// bloc unchanged, the same way `flutter_bloc`'s providers sit on `provider`. +public abstract class BlocBase implements ChangeNotifier { + + private S state; + private boolean emitted; + private boolean closed; + private final StreamController controller = StreamController.broadcast(); + + protected BlocBase(S initialState) { + this.state = initialState; + Bloc.observer.onCreate(this); + } + + public S state() { + return state; + } + + public Stream stream() { + return controller.stream(); + } + + public boolean isClosed() { + return closed; + } + + /// Replaces the state and tells everyone. Like bloc, an `==` state is + /// dropped once something has been emitted, and emitting after `close` + /// is an error. + public void emit(S next) { + if (closed) { + throw new StateError("Cannot emit new states after calling close"); + } + if (emitted && DartRuntime.eq(next, state)) { + return; + } + onChange(new Change(state, next)); + state = next; + emitted = true; + controller.add(next); + notifyListeners(); + } + + public void onChange(Change change) { + Bloc.observer.onChange(this, change); + } + + public void addError(Object error, Object stackTrace) { + onError(error, stackTrace); + } + + public void addError(Object error) { + onError(error, null); + } + + public void onError(Object error, Object stackTrace) { + Bloc.observer.onError(this, error, stackTrace); + } + + public Future close() { + if (closed) { + return Future.value(null); + } + closed = true; + Bloc.observer.onClose(this); + return controller.close(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocBuilder.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocBuilder.java new file mode 100644 index 00000000000..41620d7f5f4 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.State; +import com.codename1.flutter.StatefulWidget; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/// flutter_bloc's `BlocBuilder`: rebuilds `builder` whenever the bloc's +/// state changes (and `buildWhen`, if given, agrees). +public class BlocBuilder extends StatefulWidget { + BlocBase bloc; + Funcs.Func2 builder; + Funcs.Func2 buildWhen; + Class providedType; + + public void bloc(BlocBase v) { + bloc = v; + } + + public void builder(Funcs.Func2 v) { + builder = v; + } + + public void buildWhen(Funcs.Func2 v) { + buildWhen = v; + } + + public void providedType(Class v) { + providedType = v; + } + + @Override + public State createState() { + return new BlocWidgetState>() { + private Object shown; + private boolean hasShown; + + @Override + protected BlocBase explicitBloc() { + return widget().bloc; + } + + @Override + protected Class blocType() { + return widget().providedType; + } + + @Override + protected void onBlocChanged() { + hasShown = false; + shown = null; + } + + @Override + protected void onState(Object before, Object next) { + if (test(widget().buildWhen, before, next)) { + shown = next; + hasShown = true; + rebuild(); + } + } + + @Override + @SuppressWarnings("unchecked") + public Widget build(BuildContext context) { + Object s = currentState(); + return widget().builder.call(context, (S) (hasShown ? shown : s)); + } + }; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocConsumer.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocConsumer.java new file mode 100644 index 00000000000..d95e537e543 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocConsumer.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.State; +import com.codename1.flutter.StatefulWidget; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/// flutter_bloc's `BlocConsumer`: a BlocListener and a BlocBuilder on one bloc. +public class BlocConsumer extends StatefulWidget { + BlocBase bloc; + Funcs.Func2 builder; + Funcs.VoidFunc2 listener; + Funcs.Func2 buildWhen; + Funcs.Func2 listenWhen; + Class providedType; + + public void bloc(BlocBase v) { + bloc = v; + } + + public void builder(Funcs.Func2 v) { + builder = v; + } + + public void listener(Funcs.VoidFunc2 v) { + listener = v; + } + + public void buildWhen(Funcs.Func2 v) { + buildWhen = v; + } + + public void listenWhen(Funcs.Func2 v) { + listenWhen = v; + } + + public void providedType(Class v) { + providedType = v; + } + + @Override + public State createState() { + return new BlocWidgetState>() { + private Object shown; + private boolean hasShown; + + @Override + protected BlocBase explicitBloc() { + return widget().bloc; + } + + @Override + protected Class blocType() { + return widget().providedType; + } + + @Override + protected void onBlocChanged() { + hasShown = false; + shown = null; + } + + @Override + @SuppressWarnings("unchecked") + protected void onState(Object before, Object next) { + BlocConsumer w = widget(); + if (w.listener != null && test(w.listenWhen, before, next)) { + w.listener.call(context(), (S) next); + } + if (test(w.buildWhen, before, next)) { + shown = next; + hasShown = true; + rebuild(); + } + } + + @Override + @SuppressWarnings("unchecked") + public Widget build(BuildContext context) { + Object s = currentState(); + return widget().builder.call(context, (S) (hasShown ? shown : s)); + } + }; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocListener.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocListener.java new file mode 100644 index 00000000000..5f4f1805162 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocListener.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.State; +import com.codename1.flutter.StatefulWidget; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/// flutter_bloc's `BlocListener`: runs `listener` once per state change +/// (that `listenWhen` accepts) and shows `child` unchanged. +public class BlocListener extends StatefulWidget { + BlocBase bloc; + Funcs.VoidFunc2 listener; + Funcs.Func2 listenWhen; + Widget child; + Class providedType; + + public void bloc(BlocBase v) { + bloc = v; + } + + public void listener(Funcs.VoidFunc2 v) { + listener = v; + } + + public void listenWhen(Funcs.Func2 v) { + listenWhen = v; + } + + public void child(Widget v) { + child = v; + } + + public void providedType(Class v) { + providedType = v; + } + + @Override + public State createState() { + return new BlocWidgetState>() { + @Override + protected BlocBase explicitBloc() { + return widget().bloc; + } + + @Override + protected Class blocType() { + return widget().providedType; + } + + @Override + @SuppressWarnings("unchecked") + protected void onState(Object before, Object next) { + if (widget().listener != null && test(widget().listenWhen, before, next)) { + widget().listener.call(context(), (S) next); + } + } + + @Override + public Widget build(BuildContext context) { + bloc(); + return widget().child; + } + }; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocObserver.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocObserver.java new file mode 100644 index 00000000000..ec6bc5a6d11 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocObserver.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +/// bloc's `BlocObserver`: every hook does nothing until an app overrides it and +/// installs its subclass with `Bloc.observer = ...`. +public class BlocObserver { + public void onCreate(BlocBase bloc) { + } + + public void onEvent(Bloc bloc, Object event) { + } + + public void onChange(BlocBase bloc, Change change) { + } + + public void onTransition(Bloc bloc, Transition transition) { + } + + public void onError(BlocBase bloc, Object error, Object stackTrace) { + } + + public void onClose(BlocBase bloc) { + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocProvider.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocProvider.java new file mode 100644 index 00000000000..b9c207e6d22 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.Key; +import com.codename1.flutter.Widget; +import com.codename1.flutter.provider.ChangeNotifierProvider; + +/// flutter_bloc's `BlocProvider`: provides a bloc to its subtree. A bloc is a +/// ChangeNotifier here, so this is a ChangeNotifierProvider that also closes the +/// bloc it created when it leaves the tree -- never one handed in with `.value`. +public class BlocProvider extends ChangeNotifierProvider { + + @Override + public BlocProvider providedType(Class v) { + super.providedType(v); + return this; + } + + @Override + protected void disposeCreated(BuildContext context, Object created) { + super.disposeCreated(context, created); + if (created instanceof BlocBase) { + ((BlocBase) created).close(); + } + } + + public static BlocProvider value(Key key, Object value, Widget child) { + BlocProvider p = new BlocProvider(); + p.key(key); + p.value(value); + p.child(child); + return p; + } + + /// `BlocProvider.of(context, listen: false)`. + @SuppressWarnings("unchecked") + public static T of(BuildContext context, boolean listen, Class type) { + return (T) context.providerValueOfType(type == null ? BlocBase.class : type, listen); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocSelector.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocSelector.java new file mode 100644 index 00000000000..21895be84dc --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocSelector.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.State; +import com.codename1.flutter.StatefulWidget; +import com.codename1.flutter.Widget; + +import dart.runtime.DartRuntime; +import dart.runtime.Funcs; + +/// flutter_bloc's `BlocSelector`: rebuilds only when `selector(state)` +/// gives a different value. +public class BlocSelector extends StatefulWidget { + BlocBase bloc; + Funcs.Func1 selector; + Funcs.Func2 builder; + Class providedType; + + public void bloc(BlocBase v) { + bloc = v; + } + + public void selector(Funcs.Func1 v) { + selector = v; + } + + public void builder(Funcs.Func2 v) { + builder = v; + } + + public void providedType(Class v) { + providedType = v; + } + + @Override + public State createState() { + return new BlocWidgetState>() { + private T selected; + private boolean hasSelected; + + @Override + protected BlocBase explicitBloc() { + return widget().bloc; + } + + @Override + protected Class blocType() { + return widget().providedType; + } + + @Override + protected void onBlocChanged() { + hasSelected = false; + selected = null; + } + + @Override + @SuppressWarnings("unchecked") + protected void onState(Object before, Object next) { + T v = widget().selector.call((S) next); + if (!hasSelected || !DartRuntime.eq(v, selected)) { + selected = v; + hasSelected = true; + rebuild(); + } + } + + @Override + @SuppressWarnings("unchecked") + public Widget build(BuildContext context) { + // currentState() first: it is what notices a replaced bloc and clears + // the cached selection. + Object s = currentState(); + if (!hasSelected) { + selected = widget().selector.call((S) s); + hasSelected = true; + } + return widget().builder.call(context, selected); + } + }; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocWidgetState.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocWidgetState.java new file mode 100644 index 00000000000..b4b883fe4e6 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/BlocWidgetState.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.State; +import com.codename1.flutter.StatefulWidget; + +import dart.runtime.Funcs; + +/// What BlocBuilder, BlocListener, BlocConsumer and BlocSelector share: find the +/// bloc (the widget's own, or the nearest provided one of the widget's `B`), +/// listen to it while mounted, and let the subclass decide what a new state means. +abstract class BlocWidgetState extends State { + + private BlocBase bloc; + private Object previous; + + private final Funcs.VoidFunc0 listener = new Funcs.VoidFunc0() { + @Override + public void call() { + if (bloc == null || !mounted()) { + return; + } + Object next = bloc.state(); + Object before = previous; + previous = next; + onState(before, next); + } + }; + + /// The widget's explicit `bloc:`, or null to look it up. + protected abstract BlocBase explicitBloc(); + + private boolean fromExplicit; + + /// The widget's `B`, threaded in by the transpiler; null when it was not known. + protected abstract Class blocType(); + + /// A state after `before`. Called while mounted, outside build. + protected abstract void onState(Object before, Object next); + + /// Called from build only. A provided bloc is looked up on every build, with a + /// dependency on the provider: when an ancestor BlocProvider swaps its bloc while + /// this widget stays in place, the provider rebuilds us and the subscription moves + /// to the new bloc, as flutter_bloc's context.select on the bloc does. Before, the + /// lookup was made once without a dependency and kept the old, possibly closed, bloc. + @SuppressWarnings("unchecked") + protected final BlocBase bloc() { + BlocBase b = explicitBloc(); + if (b == null) { + Class type = blocType(); + Object found = context().providerValueOfType(type == null ? BlocBase.class : type, true); + b = found instanceof BlocBase ? (BlocBase) found : null; + } + if (b != bloc) { + release(); + if (b != null) { + fromExplicit = b == explicitBloc(); + bloc = (BlocBase) b; + previous = bloc.state(); + bloc.addListener(listener); + } + onBlocChanged(); + } + return bloc; + } + + /// The bloc this widget follows was replaced (a new provider or `bloc:`). A + /// subclass drops whatever it cached from the old bloc's states here, so the next + /// build shows the new bloc's current state rather than the old one's last. + protected void onBlocChanged() { + } + + /// The state a builder should be shown, before any change arrives. + protected final Object currentState() { + BlocBase b = bloc(); + return b == null ? null : b.state(); + } + + protected final void rebuild() { + setState(new Funcs.VoidFunc0() { + @Override + public void call() { + } + }); + } + + @Override + public void didUpdateWidget(W oldWidget) { + super.didUpdateWidget(oldWidget); + BlocBase explicit = explicitBloc(); + // A bloc the widget passed and then dropped (bloc: x -> bloc: null) is released + // too, so the next build looks the provider up instead of keeping the old one. + if (explicit != null ? explicit != bloc : fromExplicit) { + release(); + } + } + + @Override + public void dispose() { + release(); + super.dispose(); + } + + private void release() { + if (bloc != null) { + bloc.removeListener(listener); + bloc = null; + } + } + + @SuppressWarnings("unchecked") + static boolean test(Funcs.Func2 when, Object before, Object next) { + return when == null || Boolean.TRUE.equals(when.call((S) before, (S) next)); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Change.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Change.java new file mode 100644 index 00000000000..54732f89ba3 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Change.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import dart.runtime.DartRuntime; + +/// bloc's `Change`: one state replacing another. +public class Change { + private final S currentState; + private final S nextState; + + public Change(S currentState, S nextState) { + this.currentState = currentState; + this.nextState = nextState; + } + + public S currentState() { + return currentState; + } + + public S nextState() { + return nextState; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Change)) { + return false; + } + Change c = (Change) o; + return DartRuntime.eq(currentState, c.currentState) && DartRuntime.eq(nextState, c.nextState); + } + + @Override + public int hashCode() { + return (currentState == null ? 0 : currentState.hashCode()) * 31 + + (nextState == null ? 0 : nextState.hashCode()); + } + + @Override + public String toString() { + return "Change { currentState: " + currentState + ", nextState: " + nextState + " }"; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Cubit.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Cubit.java new file mode 100644 index 00000000000..e95bc95a5cc --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Cubit.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +/// bloc's `Cubit`: a [BlocBase] driven by calling its own methods. +public abstract class Cubit extends BlocBase { + protected Cubit(S initialState) { + super(initialState); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Emitter.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Emitter.java new file mode 100644 index 00000000000..4c99fc3fd26 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Emitter.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import dart.async.Future; +import dart.async.Stream; +import dart.runtime.Dyn; +import dart.runtime.Funcs; + +/// bloc's `Emitter`, handed to every event handler. Dart calls it as a +/// function (`emit(state)`), which reaches [#call]; it answers null rather than +/// void so that an arrow handler `(e, emit) => emit(s)` still fits the +/// value-returning handler type. +public class Emitter { + private final Bloc bloc; + private final Object event; + private boolean done; + + Emitter(Bloc bloc, Object event) { + this.bloc = bloc; + this.event = event; + } + + public Object call(S state) { + if (!done) { + bloc.emitTransition(event, state); + } + return null; + } + + public boolean isDone() { + return done; + } + + /// Finishes this emitter: later emits are ignored, and a forEach/onEach + /// subscription is cancelled and its future completed, as bloc does on close. + void complete() { + dart.async.StreamSubscription s; + dart.async.Completer r; + synchronized (this) { + done = true; + s = activeSub; + r = activeResult; + activeSub = null; + activeResult = null; + } + bloc.emitterDone(this); + if (s != null) { + s.cancel(); + } + if (r != null && !r.isCompleted()) { + r.complete(null); + } + } + + private dart.async.StreamSubscription activeSub; + private dart.async.Completer activeResult; + + /// `await emit.forEach(stream, onData: (d) => state, onError: (e, st) => state)`: + /// emits what onData maps each event to, until the stream ends. A source error + /// goes to onError, whose state is emitted, as Bloc does; with no onError the + /// error fails the handler. + public Future forEach(Stream stream, final Funcs.Func1 onData, final Object onError) { + return drive(stream, onData, onError, true); + } + + /// `await emit.onEach(stream, onData: (d) { ... }, onError: (e, st) { ... })`. + public Future onEach(Stream stream, final Funcs.VoidFunc1 onData, final Object onError) { + return drive(stream, onData, onError, false); + } + + @SuppressWarnings("unchecked") + private Future drive(Stream stream, final Object onData, final Object onError, + final boolean emitResults) { + final dart.async.Completer result = new dart.async.Completer(); + final dart.async.StreamSubscription[] sub = new dart.async.StreamSubscription[1]; + sub[0] = stream.listen(new Funcs.VoidFunc1() { + @Override + public void call(T value) { + try { + Object v = Dyn.invoke(onData, value); + if (emitResults) { + Emitter.this.call((S) v); + } + } catch (RuntimeException t) { + fail(sub[0], result, t); + } + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object err) { + if (onError == null) { + fail(sub[0], result, err); + return; + } + try { + Object e = err instanceof Throwable ? dart.runtime.DartRuntime.caught((Throwable) err) : err; + Object v = invokeOnError(onError, e); + if (emitResults) { + Emitter.this.call((S) v); + } + } catch (RuntimeException t) { + fail(sub[0], result, t); + } + } + }, new Funcs.VoidFunc0() { + @Override + public void call() { + if (!result.isCompleted()) { + result.complete(null); + } + } + }, Boolean.FALSE); + boolean already; + synchronized (this) { + already = done; + if (!already) { + activeSub = sub[0]; + activeResult = result; + } + } + if (already) { + sub[0].cancel(); + if (!result.isCompleted()) { + result.complete(null); + } + } + return result.future(); + } + + private void fail(dart.async.StreamSubscription sub, dart.async.Completer result, Object err) { + if (sub != null) { + sub.cancel(); + } + if (!result.isCompleted()) { + result.completeError(err); + } + } + + private static Object invokeOnError(Object handler, Object err) { + if (handler instanceof Funcs.Func2 || handler instanceof Funcs.VoidFunc2) { + return Dyn.invoke(handler, err, null); + } + return Dyn.invoke(handler, err); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/MultiBlocListener.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/MultiBlocListener.java new file mode 100644 index 00000000000..e5e0be8125a --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/MultiBlocListener.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import java.util.List; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.StatelessWidget; +import com.codename1.flutter.Widget; + +/// flutter_bloc's `MultiBlocListener`: nests its listeners around `child`. +public class MultiBlocListener extends StatelessWidget { + private List listeners; + private Widget child; + + public void listeners(List v) { + listeners = v; + } + + public void child(Widget v) { + child = v; + } + + @Override + public Widget build(BuildContext context) { + Widget acc = child; + if (listeners != null) { + for (int i = listeners.size() - 1; i >= 0; i--) { + Widget l = listeners.get(i); + if (l instanceof BlocListener) { + ((BlocListener) l).child(acc); + acc = l; + } + } + } + return acc; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/MultiBlocProvider.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/MultiBlocProvider.java new file mode 100644 index 00000000000..46d98a68661 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/MultiBlocProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.provider.MultiProvider; + +/// flutter_bloc's `MultiBlocProvider`, which nests its providers exactly as +/// provider's MultiProvider does. MultiRepositoryProvider is the same widget. +public class MultiBlocProvider extends MultiProvider { +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/RepositoryProvider.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/RepositoryProvider.java new file mode 100644 index 00000000000..a7be779927e --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/RepositoryProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.Key; +import com.codename1.flutter.Widget; +import com.codename1.flutter.provider.Provider; + +/// flutter_bloc's `RepositoryProvider`: a plain [Provider] under bloc's name. +public class RepositoryProvider extends Provider { + + @Override + public RepositoryProvider providedType(Class v) { + super.providedType(v); + return this; + } + + public static RepositoryProvider value(Key key, Object value, Widget child) { + RepositoryProvider p = new RepositoryProvider(); + p.key(key); + p.value(value); + p.child(child); + return p; + } + + @SuppressWarnings("unchecked") + public static T of(BuildContext context, boolean listen, Class type) { + return (T) context.providerValueOfType(type, listen); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Transition.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Transition.java new file mode 100644 index 00000000000..bca3de644af --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/bloc/Transition.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.bloc; + +/// bloc's `Transition`: a [Change] together with the event that caused it. +public class Transition extends Change { + private final E event; + + public Transition(S currentState, E event, S nextState) { + super(currentState, nextState); + this.event = event; + } + + public E event() { + return event; + } + + @Override + public boolean equals(Object o) { + return o instanceof Transition && super.equals(o) + && dart.runtime.DartRuntime.eq(event, ((Transition) o).event); + } + + @Override + public int hashCode() { + return super.hashCode() * 31 + (event == null ? 0 : event.hashCode()); + } + + @Override + public String toString() { + return "Transition { currentState: " + currentState() + ", event: " + event + + ", nextState: " + nextState() + " }"; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/cachednetworkimage/CachedNetworkImage.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/cachednetworkimage/CachedNetworkImage.java new file mode 100644 index 00000000000..f2e229f55fd --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/cachednetworkimage/CachedNetworkImage.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.cachednetworkimage; + +import com.codename1.flutter.BoxFit; +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.StatelessWidget; +import com.codename1.flutter.Widget; +import com.codename1.flutter.widgets.Image; + +import dart.runtime.Funcs; + +/** + * package:cached_network_image's {@code CachedNetworkImage}. {@code Image.network} + * already caches what it downloads, so this is that widget under the plugin's + * parameter names. The placeholder and error builders are accepted; the network + * image shows its own placeholder while loading. + */ +public class CachedNetworkImage extends StatelessWidget { + private String imageUrl; + private Double width; + private Double height; + private BoxFit fit; + + public CachedNetworkImage() { + } + + public void imageUrl(String v) { + this.imageUrl = v; + } + + public void width(Double v) { + this.width = v; + } + + private java.util.Map httpHeaders; + + public void height(Double v) { + this.height = v; + } + + public void fit(BoxFit v) { + this.fit = v; + } + + /** Accepted for source compatibility; see the class comment. */ + public void placeholder(Funcs.Func2 v) { + } + + /** Accepted for source compatibility; see the class comment. */ + public void errorWidget(Funcs.Func3 v) { + } + + /// Sent with the image request, so an endpoint behind Authorization or a cookie + /// serves the image instead of refusing a bare request. + public void httpHeaders(Object v) { + this.httpHeaders = v instanceof java.util.Map ? (java.util.Map) v : null; + } + + /** Accepted for source compatibility. */ + public void memCacheWidth(Object v) { + } + + /** Accepted for source compatibility. */ + public void memCacheHeight(Object v) { + } + + /** Accepted for source compatibility. */ + public void fadeInDuration(Object v) { + } + + /** Accepted for source compatibility. */ + public void cacheKey(String v) { + } + + @Override + public Widget build(BuildContext context) { + return Image.networkWithHeaders(imageUrl == null ? "" : imageUrl, null, width, height, fit, httpHeaders); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/connectivity/Connectivity.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/connectivity/Connectivity.java new file mode 100644 index 00000000000..54964719e4b --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/connectivity/Connectivity.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.connectivity; + +import com.codename1.io.NetworkManager; +import com.codename1.io.NetworkTypeListener; +import com.codename1.ui.Display; + +import dart.async.Future; +import dart.async.Stream; +import dart.async.StreamController; +import dart.core.DartList; + +/** + * connectivity_plus's {@code Connectivity}, over NetworkManager's network type and its + * change listener. Without a display (a headless run) there is no platform to ask, and + * the answer is {@code none}. + */ +public class Connectivity { + private static StreamController> changes; + private static boolean listening; + + public Connectivity() { + } + + static DartList results(int type, boolean vpn) { + DartList out = new DartList(); + switch (type) { + case NetworkManager.NETWORK_TYPE_WIFI: + out.add(ConnectivityResult.wifi); + break; + case NetworkManager.NETWORK_TYPE_CELLULAR: + out.add(ConnectivityResult.mobile); + break; + case NetworkManager.NETWORK_TYPE_ETHERNET: + out.add(ConnectivityResult.ethernet); + break; + case NetworkManager.NETWORK_TYPE_BLUETOOTH: + out.add(ConnectivityResult.bluetooth); + break; + case NetworkManager.NETWORK_TYPE_NONE: + out.add(ConnectivityResult.none); + break; + default: + out.add(ConnectivityResult.other); + break; + } + if (vpn && type != NetworkManager.NETWORK_TYPE_NONE) { + out.add(ConnectivityResult.vpn); + } + return out; + } + + public Future> checkConnectivity() { + if (!Display.isInitialized()) { + return Future.value(results(NetworkManager.NETWORK_TYPE_NONE, false)); + } + NetworkManager nm = NetworkManager.getInstance(); + return Future.value(results(nm.getCurrentNetworkType(), nm.isVPNActive())); + } + + public Stream> onConnectivityChanged() { + synchronized (Connectivity.class) { + if (changes == null) { + changes = StreamController.broadcast(); + } + // Registered on the first call made once Display is up, not only on the very + // first call: a stream requested before then would otherwise never fire. + if (!listening && Display.isInitialized()) { + listening = true; + NetworkManager.getInstance().addNetworkTypeListener(new NetworkTypeListener() { + @Override + public void onNetworkTypeChanged(int oldType, int newType, final boolean vpnActive) { + final int type = newType; + // The platform reports from its own thread; listeners call setState. + Display.getInstance().callSerially(new Runnable() { + @Override + public void run() { + changes.add(results(type, vpnActive)); + } + }); + } + }); + } + return changes.stream(); + } + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/connectivity/ConnectivityResult.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/connectivity/ConnectivityResult.java new file mode 100644 index 00000000000..12c9d4c8baf --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/connectivity/ConnectivityResult.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.connectivity; + +/** connectivity_plus's {@code ConnectivityResult}. */ +public enum ConnectivityResult { + bluetooth, wifi, ethernet, mobile, none, vpn, other +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/crossfile/XFile.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/crossfile/XFile.java new file mode 100644 index 00000000000..0e5ea752652 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/crossfile/XFile.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.crossfile; + +import dart.async.Future; +import dart.io.File; +import dart.typed_data.Uint8List; + +/** + * package:cross_file's {@code XFile}, which image_picker returns and share_plus takes: + * a file by path, read through dart:io's File. A file made from bytes is held in + * memory. + */ +public class XFile { + private final String path; + private String mimeType; + private String name; + private final Uint8List bytes; + + public XFile(String path, String mimeType, String name, Object length, Uint8List bytes, + Object lastModified) { + this.path = path; + this.mimeType = mimeType; + this.name = name; + this.bytes = bytes; + } + + public XFile(String path) { + this(path, null, null, null, null, null); + } + + /** {@code XFile.fromData(bytes, {mimeType, name, length, lastModified, path})}. */ + public static XFile fromData(Uint8List bytes, String mimeType, String name, Object length, + Object lastModified, String path) { + return new XFile(path == null ? "" : path, mimeType, name, length, bytes, lastModified); + } + + /** The stub's named {@code mimeType:} argument. */ + public void mimeType(String v) { + mimeType = v; + } + + /** The stub's named {@code name:} argument. */ + public void name(String v) { + name = v; + } + + public String path() { + return path; + } + + public String name() { + if (name != null) { + return name; + } + int slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); + return slash < 0 ? path : path.substring(slash + 1); + } + + public String mimeType() { + return mimeType; + } + + /// A path a platform API can open. A file made with fromData lives only in + /// memory (its path is usually empty), so its bytes are written to a file in the + /// app's cache directory first, named after the file, and that path is returned. + public String materializedPath() { + if (bytes == null || !com.codename1.ui.Display.isInitialized()) { + return path; + } + com.codename1.io.FileSystemStorage fs = com.codename1.io.FileSystemStorage.getInstance(); + if (path.length() > 0 && fs.exists(path)) { + return path; + } + String dir = fs.getCachesDir(); + if (dir == null || dir.length() == 0) { + dir = fs.getAppHomePath(); + } + if (!dir.endsWith("/")) { + dir += "/"; + } + String n = name(); + if (n == null || n.length() == 0) { + n = "shared-" + System.currentTimeMillis(); + } + String target = dir + n.replace('/', '_').replace('\\', '_'); + java.io.OutputStream out = null; + try { + out = fs.openOutputStream(target); + out.write(bytes.toBytes()); + } catch (java.io.IOException e) { + return path; + } finally { + com.codename1.io.Util.cleanup(out); + } + return target; + } + + public Future readAsBytes() { + return bytes != null ? Future.value(bytes) : new File(path).readAsBytes(); + } + + public Future readAsString() { + if (bytes != null) { + try { + return Future.value(new String(bytes.toBytes(), "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + return Future.error(e); + } + } + return new File(path).readAsString(); + } + + public Future length() { + return bytes != null ? Future.value(Long.valueOf(bytes.length())) : new File(path).length(); + } + + public Future saveTo(String target) { + final File out = new File(target); + return readAsBytes().then(new dart.runtime.Funcs.Func1() { + @Override + public Object call(Uint8List b) { + return out.writeAsBytes(b); + } + }); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/AndroidBuildVersion.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/AndroidBuildVersion.java new file mode 100644 index 00000000000..656fc4d182d --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/AndroidBuildVersion.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.deviceinfo; + +/** {@code AndroidBuildVersion}: the OS release and, when it parses, its API level. */ +public class AndroidBuildVersion { + public String release() { + return DeviceInfoPlugin.prop("OSVer", ""); + } + + public long sdkInt() { + String v = DeviceInfoPlugin.prop("SDK_INT", ""); + long n = 0; + for (int i = 0; i < v.length(); i++) { + char c = v.charAt(i); + if (c < '0' || c > '9') { + break; + } + n = n * 10 + (c - '0'); + } + return n; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/AndroidDeviceInfo.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/AndroidDeviceInfo.java new file mode 100644 index 00000000000..4330cf3d184 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/AndroidDeviceInfo.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.deviceinfo; + +/** {@code AndroidDeviceInfo}, with {@code version} flattened to its own object. */ +public class AndroidDeviceInfo extends BaseDeviceInfo { + private final AndroidBuildVersion version = new AndroidBuildVersion(); + + AndroidDeviceInfo() { + data.put("model", model()); + data.put("manufacturer", manufacturer()); + data.put("isPhysicalDevice", Boolean.valueOf(isPhysicalDevice())); + } + + public String model() { + return DeviceInfoPlugin.prop("Platform", ""); + } + + public String brand() { + return manufacturer(); + } + + public String manufacturer() { + return DeviceInfoPlugin.prop("Manufacturer", ""); + } + + public String device() { + return model(); + } + + public String id() { + return DeviceInfoPlugin.prop("UDID", ""); + } + + public boolean isPhysicalDevice() { + return DeviceInfoPlugin.physical(); + } + + public AndroidBuildVersion version() { + return version; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/BaseDeviceInfo.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/BaseDeviceInfo.java new file mode 100644 index 00000000000..91f8b67470a --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/BaseDeviceInfo.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.deviceinfo; + +import dart.core.DartMap; + +/** Shared by both info objects. */ +public abstract class BaseDeviceInfo { + final DartMap data = new DartMap(); + + public DartMap data() { + return data; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/DeviceInfoPlugin.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/DeviceInfoPlugin.java new file mode 100644 index 00000000000..28a7e4e63ff --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/DeviceInfoPlugin.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.deviceinfo; + +import com.codename1.ui.Display; + +import dart.async.Future; + +/** + * device_info_plus's {@code DeviceInfoPlugin}, from Codename One's device properties. + * Each platform's info object carries the fields apps commonly read -- model, OS + * version, whether the device is physical -- and its {@code data} map holds them all. + */ +public class DeviceInfoPlugin { + + public DeviceInfoPlugin() { + } + + static String prop(String key, String def) { + return Display.isInitialized() ? Display.getInstance().getProperty(key, def) : def; + } + + static String platform() { + return Display.isInitialized() ? Display.getInstance().getPlatformName() : "se"; + } + + static boolean physical() { + return Display.isInitialized() && !Display.getInstance().isSimulator(); + } + + public Future androidInfo() { + return Future.value(new AndroidDeviceInfo()); + } + + public Future iosInfo() { + return Future.value(new IosDeviceInfo()); + } + + /** The info of whichever platform this is. */ + public Future deviceInfo() { + String p = platform(); + if ("ios".equals(p)) { + return Future.value((BaseDeviceInfo) new IosDeviceInfo()); + } + return Future.value((BaseDeviceInfo) new AndroidDeviceInfo()); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/IosDeviceInfo.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/IosDeviceInfo.java new file mode 100644 index 00000000000..2fde7a7807d --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/deviceinfo/IosDeviceInfo.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.deviceinfo; + +/** {@code IosDeviceInfo}. */ +public class IosDeviceInfo extends BaseDeviceInfo { + IosDeviceInfo() { + data.put("model", model()); + data.put("systemVersion", systemVersion()); + data.put("isPhysicalDevice", Boolean.valueOf(isPhysicalDevice())); + } + + public String name() { + return DeviceInfoPlugin.prop("Platform", ""); + } + + public String model() { + return DeviceInfoPlugin.prop("Platform", ""); + } + + public String systemName() { + return "iOS"; + } + + public String systemVersion() { + return DeviceInfoPlugin.prop("OSVer", ""); + } + + public String identifierForVendor() { + return DeviceInfoPlugin.prop("UDID", null); + } + + public boolean isPhysicalDevice() { + return DeviceInfoPlugin.physical(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/BaseOptions.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/BaseOptions.java new file mode 100644 index 00000000000..d1ab9c7113c --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/BaseOptions.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import java.util.Map; + +import dart.core.DartMap; +import dart.core.Duration; + +/// dio's `BaseOptions`: what every request of one Dio instance starts from. +public class BaseOptions extends OptionsBase { + String baseUrl = ""; + Duration connectTimeout; + DartMap queryParameters; + + public String baseUrl() { + return baseUrl; + } + + public void baseUrl(String v) { + baseUrl = v == null ? "" : v; + } + + public Duration connectTimeout() { + return connectTimeout; + } + + public void connectTimeout(Duration v) { + connectTimeout = v; + } + + public DartMap queryParameters() { + if (queryParameters == null) { + queryParameters = new DartMap(); + } + return queryParameters; + } + + public void queryParameters(Map v) { + queryParameters = v == null ? null : copy(v); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/CancelToken.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/CancelToken.java new file mode 100644 index 00000000000..a3107ff5532 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/CancelToken.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import com.codename1.io.ConnectionRequest; + +/// dio's `CancelToken`: cancels the requests it was passed to. +public class CancelToken { + private boolean cancelled; + private Object reason; + /// Every request this token was handed, with its cancel callback: one token is + /// often shared by several concurrent requests, and cancel() stops all of them. A + /// request that already finished ignores both (its completion is guarded). + private final java.util.List requests = new java.util.ArrayList(); + private final java.util.List callbacks = new java.util.ArrayList(); + + public CancelToken() { + } + + public boolean isCancelled() { + return cancelled; + } + + public Object cancelError() { + return reason; + } + + public void cancel() { + cancel(null); + } + + public void cancel(Object why) { + if (cancelled) { + return; + } + cancelled = true; + reason = why; + ConnectionRequest[] reqs = requests.toArray(new ConnectionRequest[requests.size()]); + Runnable[] cbs = callbacks.toArray(new Runnable[callbacks.size()]); + requests.clear(); + callbacks.clear(); + for (int i = 0; i < reqs.length; i++) { + reqs[i].kill(); + cbs[i].run(); + } + } + + /// A settled request leaves the token, so a long-lived token reused for many + /// requests does not keep every finished one (and its callback) alive. + void detach(ConnectionRequest req) { + // by identity: ConnectionRequest.equals matches duplicate requests (same URL) + for (int i = 0; i < requests.size(); i++) { + if (requests.get(i) == req) { + requests.remove(i); + callbacks.remove(i); + return; + } + } + } + + void attach(ConnectionRequest req, Runnable cancelled) { + if (this.cancelled) { + // dio fails a request made with an already-cancelled token straight away. + req.kill(); + cancelled.run(); + return; + } + requests.add(req); + callbacks.add(cancelled); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Dio.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Dio.java new file mode 100644 index 00000000000..deeeb022c67 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Dio.java @@ -0,0 +1,594 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import com.codename1.io.ConnectionRequest; +import com.codename1.io.NetworkManager; +import com.codename1.ui.Display; + +import dart.async.Completer; +import dart.async.Future; +import dart.convert.Convert; +import dart.core.DartMap; +import dart.io.SocketException; +import dart.typed_data.Uint8List; + +/// dio's `Dio`, over Codename One's `ConnectionRequest`. +/// +/// A request runs as dio runs it: the request interceptors in order, the HTTP +/// exchange, a status check (`validateStatus`, 2xx by default), then the response +/// interceptors -- or, on any failure, the error interceptors -- and the future +/// completes with what comes out of the last of them. A JSON response (by content +/// type, for the default `ResponseType.json`) is decoded into Dart maps and lists. +/// With no Display (headless) there is no network, and every request fails with a +/// `connectionError`, so an app's offline path is what runs. +public class Dio { + + private BaseOptions options; + private final Interceptors interceptors = new Interceptors(); + + public Dio() { + this(null); + } + + private boolean closed; + + /// A header value as it goes on the wire: Dio accepts a list for a header with + /// several values, which joins with ", " per HTTP, never Java's "[a, b]". + static String headerText(Object v) { + if (!(v instanceof Iterable)) { + return String.valueOf(v); + } + StringBuilder b = new StringBuilder(); + for (Object o : (Iterable) v) { + if (b.length() > 0) { + b.append(", "); + } + b.append(o); + } + return b.toString(); + } + + public Dio(BaseOptions options) { + this.options = options == null ? new BaseOptions() : options; + } + + public BaseOptions options() { + return options; + } + + public void options(BaseOptions v) { + options = v == null ? new BaseOptions() : v; + } + + public Interceptors interceptors() { + return interceptors; + } + + public void close() { + close(null); + } + + /// Dio marks a closed client and fails every later request; a delayed callback + /// reusing it after teardown must not still reach the network. + public void close(Boolean force) { + closed = true; + } + + public Future> get(String path, Object data, Map queryParameters, + Options options, CancelToken cancelToken, Object onReceiveProgress) { + return send("GET", path, data, queryParameters, options, cancelToken); + } + + public Future> post(String path, Object data, Map queryParameters, + Options options, CancelToken cancelToken, Object onSendProgress, Object onReceiveProgress) { + return send("POST", path, data, queryParameters, options, cancelToken); + } + + public Future> put(String path, Object data, Map queryParameters, + Options options, CancelToken cancelToken, Object onSendProgress, Object onReceiveProgress) { + return send("PUT", path, data, queryParameters, options, cancelToken); + } + + public Future> patch(String path, Object data, Map queryParameters, + Options options, CancelToken cancelToken, Object onSendProgress, Object onReceiveProgress) { + return send("PATCH", path, data, queryParameters, options, cancelToken); + } + + public Future> delete(String path, Object data, Map queryParameters, + Options options, CancelToken cancelToken) { + return send("DELETE", path, data, queryParameters, options, cancelToken); + } + + public Future> head(String path, Object data, Map queryParameters, + Options options, CancelToken cancelToken) { + return send("HEAD", path, data, queryParameters, options, cancelToken); + } + + public Future> request(String path, Object data, Map queryParameters, + CancelToken cancelToken, Options options, Object onSendProgress, Object onReceiveProgress) { + String method = options != null && options.method != null ? options.method : null; + return send(method, path, data, queryParameters, options, cancelToken); + } + + /// `dio.fetch(requestOptions)`: re-runs a fully built request -- the usual way + /// an error interceptor retries after refreshing a token. + @SuppressWarnings({"unchecked", "rawtypes"}) + public Future> fetch(RequestOptions ro) { + if (closed) { + return Future.error(DioException.of(ro, DioExceptionType.connectionError, + "The connection errored: Dio can't establish a new connection after it was closed.", + new dart.core.StateError("Dio can't establish a new connection after it was closed."), null)); + } + Call call = new Call(ro); + call.request(0, ro); + return (Future) call.done.future(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Future> send(String method, String path, Object data, + Map query, Options opts, CancelToken cancelToken) { + RequestOptions ro = compose(method, path, data, query, opts); + ro.cancelToken = cancelToken; + return fetch(ro); + } + + private RequestOptions compose(String method, String path, Object data, Map query, + Options opts) { + BaseOptions b = options; + RequestOptions ro = new RequestOptions(); + ro.path(path); + ro.baseUrl(b.baseUrl); + ro.data = data; + ro.connectTimeout = b.connectTimeout; + DartMap q = new DartMap(); + if (b.queryParameters != null) { + q.putAll(b.queryParameters); + } + if (query != null) { + q.putAll(query); + } + ro.queryParameters = q; + DartMap h = new DartMap(); + if (b.headers != null) { + h.putAll(b.headers); + } + DartMap x = new DartMap(); + if (b.extra != null) { + x.putAll(b.extra); + } + ro.method = pick(method, opts == null ? null : opts.method, b.method, "GET"); + ro.contentType = b.contentType; + ro.responseType = b.responseType; + ro.sendTimeout = b.sendTimeout; + ro.receiveTimeout = b.receiveTimeout; + ro.validateStatus = b.validateStatus; + ro.followRedirects = b.followRedirects; + if (opts != null) { + if (opts.headers != null) { + putHeaders(h, opts.headers); + } + if (opts.extra != null) { + x.putAll(opts.extra); + } + if (opts.contentType != null) { + ro.contentType = opts.contentType; + } + if (opts.responseType != null) { + ro.responseType = opts.responseType; + } + if (opts.sendTimeout != null) { + ro.sendTimeout = opts.sendTimeout; + } + if (opts.receiveTimeout != null) { + ro.receiveTimeout = opts.receiveTimeout; + } + if (opts.validateStatus != null) { + ro.validateStatus = opts.validateStatus; + } + if (opts.followRedirects != null) { + ro.followRedirects = opts.followRedirects; + } + } + ro.headers = h; + ro.extra = x; + if (ro.responseType == null) { + ro.responseType = ResponseType.json; + } + return ro; + } + + /// Header names are case-insensitive: a request option's `authorization` + /// replaces a base option's `Authorization` rather than sitting beside it. + private static void putHeaders(DartMap into, java.util.Map from) { + for (java.util.Map.Entry e : from.entrySet()) { + String k = e.getKey(); + if (k != null) { + for (java.util.Iterator it = into.keySet().iterator(); it.hasNext();) { + String have = it.next(); + if (have != null && have.equalsIgnoreCase(k)) { + it.remove(); + } + } + } + into.put(k, e.getValue()); + } + } + + private static String pick(String a, String b, String c, String d) { + return a != null ? a : b != null ? b : c != null ? c : d; + } + + /// One request travelling through the interceptors. + final class Call { + final Completer> done = new Completer>(); + private final RequestOptions original; + + Call(RequestOptions original) { + this.original = original; + } + + void request(int index, RequestOptions ro) { + if (done.isCompleted()) { + return; + } + if (index < interceptors.size()) { + try { + interceptors.get(index).onRequest(ro, new RequestInterceptorHandler(this, index)); + } catch (RuntimeException e) { + error(0, wrap(ro, e)); + } + return; + } + transmit(ro, this); + } + + void response(int index, Response r) { + if (done.isCompleted()) { + return; + } + if (index < interceptors.size()) { + try { + interceptors.get(index).onResponse(r, new ResponseInterceptorHandler(this, index)); + } catch (RuntimeException e) { + error(0, wrap(r.requestOptions, e)); + } + return; + } + succeed(r); + } + + void error(int index, DioException e) { + if (done.isCompleted()) { + return; + } + if (index < interceptors.size()) { + try { + interceptors.get(index).onError(e, new ErrorInterceptorHandler(this, index)); + } catch (RuntimeException ex) { + fail(wrap(e.requestOptions, ex)); + } + return; + } + fail(e); + } + + void succeed(Response r) { + if (!done.isCompleted()) { + done.complete(r); + } + } + + void fail(DioException e) { + if (!done.isCompleted()) { + done.completeError(e); + } + } + + private DioException wrap(RequestOptions ro, RuntimeException e) { + if (e instanceof DioException) { + return (DioException) e; + } + return DioException.of(ro != null ? ro : original, DioExceptionType.unknown, e.getMessage(), e, null); + } + } + + private static boolean valid(RequestOptions ro, long status) { + if (ro.validateStatus != null) { + return Boolean.TRUE.equals(ro.validateStatus.call(Long.valueOf(status))); + } + return status >= 200 && status < 300; + } + + private void transmit(final RequestOptions ro, final Call call) { + final CancelToken token = ro.cancelToken; + if (token != null && token.isCancelled()) { + call.error(0, DioException.of(ro, DioExceptionType.cancel, "The request was cancelled.", + token.cancelError(), null)); + return; + } + if (!Display.isInitialized()) { + call.error(0, DioException.of(ro, DioExceptionType.connectionError, + "The connection errored: no network, Codename One is not initialized", + new SocketException("No network: Codename One is not initialized"), null)); + return; + } + final String[] contentType = {headerValue(ro, "content-type")}; + if (contentType[0] == null) { + contentType[0] = ro.contentType; + } + final byte[] payload; + try { + payload = encodeBody(ro, contentType); + } catch (RuntimeException e) { + // dio reports an unencodable body through the request's future (and the + // error interceptors), never as a throw out of dio.post itself. + call.error(0, DioException.of(ro, DioExceptionType.unknown, e.getMessage(), e, null)); + return; + } + final Headers responseHeaders = new Headers(); + final ByteArrayOutputStream received = new ByteArrayOutputStream(); + ConnectionRequest req = new ConnectionRequest() { + private Exception failure; + + @Override + protected void readHeaders(Object connection) throws IOException { + String[] names = getHeaderFieldNames(connection); + if (names != null) { + for (String n : names) { + if (n == null) { + continue; + } + String[] vs = getHeaders(connection, n); + if (vs != null) { + for (String v : vs) { + responseHeaders.add(n, v); + } + } + } + } + } + + @Override + protected void buildRequestBody(OutputStream os) throws IOException { + if (payload != null) { + os.write(payload); + } + } + + @Override + protected void readResponse(InputStream input) throws IOException { + byte[] buf = new byte[8192]; + int n; + while ((n = input.read(buf)) > 0) { + received.write(buf, 0, n); + } + } + + @Override + protected void handleErrorResponseCode(int code, String message) { + // The status reaches validateStatus through the response, as in dio. + } + + private boolean settled; + + /// A transport failure (DNS, TLS, a socket timeout) never reaches + /// postResponse -- NetworkManager calls only this -- so it settles the call + /// here, on the EDT like a response. With failSilently set, NetworkManager + /// would only log it and the call would stay pending forever. + @Override + protected void handleException(Exception err) { + failure = err; + if (Display.isInitialized() && !Display.getInstance().isEdt()) { + Display.getInstance().callSerially(new Runnable() { + @Override + public void run() { + postResponse(); + } + }); + } else { + postResponse(); + } + } + + @Override + protected void postResponse() { + if (settled) { + return; + } + settled = true; + if (token != null) { + token.detach(this); + } + if (failure != null) { + String m = String.valueOf(failure.getMessage()); + boolean timeout = failure.getClass().getName().indexOf("Timeout") >= 0 + || Headers.fold(m).indexOf("timed out") >= 0; + call.error(0, DioException.of(ro, timeout ? DioExceptionType.connectionTimeout + : DioExceptionType.connectionError, m, new SocketException(m), null)); + return; + } + Response r = new Response(); + r.requestOptions = ro; + r.statusCode = Long.valueOf(getResponseCode()); + r.statusMessage = getResponseErrorMessage(); + r.headers = responseHeaders; + try { + r.data = decode(ro, responseHeaders, received.toByteArray(), r.statusCode); + } catch (RuntimeException e) { + call.error(0, DioException.of(ro, DioExceptionType.unknown, e.getMessage(), e, r)); + return; + } + if (!valid(ro, getResponseCode())) { + call.error(0, DioException.of(ro, DioExceptionType.badResponse, + "This exception was thrown because the response has a status code of " + + getResponseCode() + " and RequestOptions.validateStatus was configured " + + "to throw for this status code.", null, r)); + return; + } + call.response(0, r); + } + }; + req.setUrl(ro.url()); + req.setHttpMethod(ro.method); + req.setPost(payload != null); + // Not failSilently: that makes NetworkManager swallow transport errors without + // calling handleException, which is where this request settles them. + req.setReadResponseForErrors(true); + req.setDuplicateSupported(true); + if (ro.followRedirects != null) { + req.setFollowRedirects(ro.followRedirects.booleanValue()); + } + if (ro.connectTimeout != null) { + req.setTimeout((int) ro.connectTimeout.inMilliseconds()); + } + if (ro.receiveTimeout != null) { + req.setReadTimeout((int) ro.receiveTimeout.inMilliseconds()); + } + for (Map.Entry e : ro.headers().entrySet()) { + if (e.getValue() != null && !"content-type".equals(Headers.fold(e.getKey()))) { + req.addRequestHeader(e.getKey(), headerText(e.getValue())); + } + } + if (contentType[0] != null && payload != null) { + req.setContentType(contentType[0]); + } + if (token != null) { + token.attach(req, new Runnable() { + @Override + public void run() { + call.error(0, DioException.of(ro, DioExceptionType.cancel, "The request was cancelled.", + token.cancelError(), null)); + } + }); + if (token.isCancelled()) { + return; + } + } + NetworkManager.getInstance().addToQueue(req); + } + + private static String headerValue(RequestOptions ro, String folded) { + for (Map.Entry e : ro.headers().entrySet()) { + if (folded.equals(Headers.fold(e.getKey())) && e.getValue() != null) { + return String.valueOf(e.getValue()); + } + } + return null; + } + + /// The request body: FormData as multipart, a String as is, bytes raw, and a + /// map or list as JSON -- or as a form when the content type says so. + private static byte[] encodeBody(RequestOptions ro, String[] contentType) { + Object data = ro.data; + if (data == null) { + return null; + } + try { + if (data instanceof FormData) { + contentType[0] = ((FormData) data).contentType(); + return ((FormData) data).encode(); + } + if (data instanceof Uint8List) { + return ((Uint8List) data).toBytes(); + } + if (data instanceof String) { + if (contentType[0] == null) { + contentType[0] = Headers.textPlainContentType; + } + return ((String) data).getBytes("UTF-8"); + } + if (data instanceof Map && contentType[0] != null + && contentType[0].startsWith(Headers.formUrlEncodedContentType)) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry e : ((Map) data).entrySet()) { + if (sb.length() > 0) { + sb.append('&'); + } + sb.append(com.codename1.io.Util.encodeUrl(String.valueOf(e.getKey()))).append('=') + .append(com.codename1.io.Util.encodeUrl(dart.runtime.DartRuntime.str(e.getValue()))); + } + return sb.toString().getBytes("UTF-8"); + } + if (data instanceof List && contentType[0] == null && isByteList((List) data)) { + List l = (List) data; + byte[] out = new byte[l.size()]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) ((Number) l.get(i)).intValue(); + } + return out; + } + if (contentType[0] == null) { + contentType[0] = Headers.jsonContentType; + } + return Convert.jsonEncode(data).getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + private static boolean isByteList(List l) { + if (l.isEmpty()) { + return false; + } + for (Object o : l) { + if (!(o instanceof Long) && !(o instanceof Integer)) { + return false; + } + } + return true; + } + + private static Object decode(RequestOptions ro, Headers headers, byte[] body, Long status) { + ResponseType type = ro.responseType == null ? ResponseType.json : ro.responseType; + if (type == ResponseType.stream) { + return new ResponseBody(body, status); + } + if (type == ResponseType.bytes) { + return Uint8List.fromBytes(body); + } + String text; + try { + text = new String(body, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + text = new String(body); + } + if (type == ResponseType.json) { + String ct = headers.value("content-type"); + boolean json = ct != null && Headers.fold(ct).indexOf("json") >= 0; + if (json) { + return text.trim().isEmpty() ? null : Convert.jsonDecode(text); + } + } + return text; + } + +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/DioException.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/DioException.java new file mode 100644 index 00000000000..2378b52e7f8 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/DioException.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// dio's `DioException` (and the deprecated `DioError`, which names the same class). +public class DioException extends RuntimeException { + RequestOptions requestOptions; + Response response; + DioExceptionType type = DioExceptionType.unknown; + Object error; + String message; + Object stackTrace; + + public DioException() { + super(); + } + + public RequestOptions requestOptions() { + return requestOptions; + } + + public void requestOptions(RequestOptions v) { + requestOptions = v; + } + + public Response response() { + return response; + } + + @SuppressWarnings("unchecked") + public void response(Response v) { + response = (Response) v; + } + + public DioExceptionType type() { + return type; + } + + public void type(DioExceptionType v) { + type = v == null ? DioExceptionType.unknown : v; + } + + public Object error() { + return error; + } + + public void error(Object v) { + error = v; + } + + public String message() { + return message; + } + + public void message(String v) { + message = v; + } + + public Object stackTrace() { + return stackTrace; + } + + public void stackTrace(Object v) { + stackTrace = v; + } + + @Override + public String getMessage() { + return message != null ? message : (error != null ? String.valueOf(error) : type.name()); + } + + @Override + public String toString() { + return "DioException [" + type.name() + "]: " + getMessage(); + } + + static DioException of(RequestOptions ro, DioExceptionType type, String message, Object error, + Response response) { + DioException e = new DioException(); + e.requestOptions = ro; + e.type(type); + e.message = message; + e.error = error; + e.response(response); + return e; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/DioExceptionType.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/DioExceptionType.java new file mode 100644 index 00000000000..70e52f7c029 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/DioExceptionType.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// dio's `DioExceptionType`. +public enum DioExceptionType { + connectionTimeout, + sendTimeout, + receiveTimeout, + badCertificate, + badResponse, + cancel, + connectionError, + unknown +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ErrorInterceptorHandler.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ErrorInterceptorHandler.java new file mode 100644 index 00000000000..e7387735a30 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ErrorInterceptorHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// What an `onError` hook calls: [#next] to pass the error on, [#resolve] to +/// recover with a response (a token refresh that retried), [#reject] to fail now. +public class ErrorInterceptorHandler { + private final Dio.Call call; + private final int index; + private boolean used; + + ErrorInterceptorHandler(Dio.Call call, int index) { + this.call = call; + this.index = index; + } + + private boolean first() { + if (used) { + return false; + } + used = true; + return true; + } + + public void next(DioException error) { + if (first()) { + call.error(index + 1, error); + } + } + + @SuppressWarnings("unchecked") + public void resolve(Response response) { + if (first()) { + call.succeed((Response) response); + } + } + + public void reject(DioException error) { + if (first()) { + call.fail(error); + } + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/FormData.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/FormData.java new file mode 100644 index 00000000000..7ede938ac28 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/FormData.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import dart.core.MapEntry; +import dart.runtime.DartRuntime; + +/// dio's `FormData`: a multipart/form-data body of fields and files. +public class FormData { + private final List fields = new ArrayList(); + private final List files = new ArrayList(); + final String boundary = "--dio-boundary-" + Long.toString(System.currentTimeMillis(), 36) + + Integer.toString((int) (Math.random() * 1e6), 36); + + public FormData() { + } + + /// `FormData.fromMap({...})`: a MultipartFile value is a file part, a list + /// repeats its key, anything else is a text field. + public static FormData fromMap(Map map, Object collectionFormat, Object camelCaseContentDisposition) { + FormData f = new FormData(); + if (map != null) { + for (Map.Entry e : map.entrySet()) { + f.addValue(e.getKey(), e.getValue()); + } + } + return f; + } + + private void addValue(String key, Object v) { + if (v instanceof MultipartFile) { + files.add(new Object[] {key, v}); + } else if (v instanceof List) { + for (Object o : (List) v) { + addValue(key, o); + } + } else if (v != null) { + fields.add(new String[] {key, DartRuntime.str(v)}); + } + } + + /// `formData.fields.add(MapEntry(k, v))` and `formData.files.add(MapEntry(k, file))` + /// go through these. + public void addField(MapEntry e) { + fields.add(new String[] {e.key(), e.value()}); + } + + public void addFile(MapEntry e) { + files.add(new Object[] {e.key(), e.value()}); + } + + String contentType() { + return "multipart/form-data; boundary=" + boundary; + } + + byte[] encode() { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (String[] f : fields) { + write(out, "--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + f[0] + + "\"\r\n\r\n" + f[1] + "\r\n"); + } + for (Object[] f : files) { + MultipartFile mf = (MultipartFile) f[1]; + write(out, "--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + f[0] + + "\"; filename=\"" + (mf.filename == null ? "file" : mf.filename) + "\"\r\n" + + "Content-Type: " + mf.contentType + "\r\n\r\n"); + out.write(mf.bytes); + write(out, "\r\n"); + } + write(out, "--" + boundary + "--\r\n"); + return out.toByteArray(); + } catch (IOException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + private static void write(ByteArrayOutputStream out, String s) throws IOException { + out.write(s.getBytes("UTF-8")); + } + + public long length() { + return encode().length; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Headers.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Headers.java new file mode 100644 index 00000000000..a3297cee35a --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Headers.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import java.util.List; +import java.util.Map; + +import dart.core.DartList; +import dart.core.DartMap; + +/// dio's `Headers`: response headers, names folded to lower case. +public class Headers { + public static final String contentTypeHeader = "content-type"; + public static final String jsonContentType = "application/json; charset=utf-8"; + public static final String formUrlEncodedContentType = "application/x-www-form-urlencoded"; + public static final String textPlainContentType = "text/plain"; + + private final DartMap> map = new DartMap>(); + + public Headers() { + } + + static String fold(String name) { + StringBuilder sb = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + sb.append(c >= 'A' && c <= 'Z' ? (char) (c + 32) : c); + } + return sb.toString(); + } + + public DartMap> map() { + return map; + } + + public DartList $index(String name) { + return map.get(fold(name)); + } + + /// The single value of `name`, joined with ", " when there are several. + public String value(String name) { + List vs = map.get(fold(name)); + if (vs == null || vs.isEmpty()) { + return null; + } + if (vs.size() == 1) { + return vs.get(0); + } + StringBuilder sb = new StringBuilder(); + for (String v : vs) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(v); + } + return sb.toString(); + } + + public void add(String name, String value) { + String k = fold(name); + DartList vs = map.get(k); + if (vs == null) { + vs = new DartList(); + map.put(k, vs); + } + vs.add(value); + } + + public void set(String name, Object value) { + DartList vs = new DartList(); + if (value instanceof List) { + for (Object o : (List) value) { + vs.add(String.valueOf(o)); + } + } else if (value != null) { + vs.add(String.valueOf(value)); + } + map.put(fold(name), vs); + } + + public void removeAll(String name) { + map.remove(fold(name)); + } + + public void forEach(dart.runtime.Funcs.VoidFunc2> f) { + for (Map.Entry> e : map.entrySet()) { + f.call(e.getKey(), e.getValue()); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Map.Entry> e : map.entrySet()) { + sb.append(e.getKey()).append(": ").append(value(e.getKey())).append('\n'); + } + return sb.toString(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Interceptor.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Interceptor.java new file mode 100644 index 00000000000..4b010363262 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Interceptor.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// dio's `Interceptor`: each hook passes its value on unchanged until overridden. +/// A hook must end by calling exactly one of its handler's next/resolve/reject, +/// now or later (an async hook calls it when its own work is done). +public class Interceptor { + public Interceptor() { + } + + public void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + handler.next(options); + } + + public void onResponse(Response response, ResponseInterceptorHandler handler) { + handler.next(response); + } + + public void onError(DioException err, ErrorInterceptorHandler handler) { + handler.next(err); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Interceptors.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Interceptors.java new file mode 100644 index 00000000000..316d817ad7d --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Interceptors.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import dart.core.DartList; + +/// dio's `Interceptors`: the instance's interceptor list, run in order. +public class Interceptors extends DartList { + public Interceptors() { + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/InterceptorsWrapper.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/InterceptorsWrapper.java new file mode 100644 index 00000000000..798b25dab61 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/InterceptorsWrapper.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import dart.runtime.Funcs; + +/// dio's `InterceptorsWrapper`: an Interceptor made of closures. A hook that was +/// not given passes its value straight on. +public class InterceptorsWrapper extends Interceptor { + private Funcs.VoidFunc2 onRequest; + private Funcs.VoidFunc2, ResponseInterceptorHandler> onResponse; + private Funcs.VoidFunc2 onError; + + public InterceptorsWrapper() { + } + + public void onRequest(Funcs.VoidFunc2 v) { + onRequest = v; + } + + public void onResponse(Funcs.VoidFunc2, ResponseInterceptorHandler> v) { + onResponse = v; + } + + public void onError(Funcs.VoidFunc2 v) { + onError = v; + } + + @Override + public void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + if (onRequest == null) { + handler.next(options); + } else { + onRequest.call(options, handler); + } + } + + @Override + public void onResponse(Response response, ResponseInterceptorHandler handler) { + if (onResponse == null) { + handler.next(response); + } else { + onResponse.call(response, handler); + } + } + + @Override + public void onError(DioException err, ErrorInterceptorHandler handler) { + if (onError == null) { + handler.next(err); + } else { + onError.call(err, handler); + } + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/LogInterceptor.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/LogInterceptor.java new file mode 100644 index 00000000000..f42af663384 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/LogInterceptor.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import dart.runtime.DartRuntime; +import dart.runtime.Funcs; + +/// dio's `LogInterceptor`: prints each request, response and error through +/// `logPrint` (Codename One's log when none is given). +public class LogInterceptor extends Interceptor { + private boolean request = true; + private boolean requestHeader = true; + private boolean requestBody; + private boolean responseHeader = true; + private boolean responseBody; + private boolean error = true; + private Funcs.VoidFunc1 logPrint; + + public LogInterceptor() { + } + + public void request(boolean v) { + request = v; + } + + public void requestHeader(boolean v) { + requestHeader = v; + } + + public void requestBody(boolean v) { + requestBody = v; + } + + public void responseHeader(boolean v) { + responseHeader = v; + } + + public void responseBody(boolean v) { + responseBody = v; + } + + public void error(boolean v) { + error = v; + } + + public void logPrint(Funcs.VoidFunc1 v) { + logPrint = v; + } + + private void log(String s) { + if (logPrint != null) { + logPrint.call(s); + } else { + com.codename1.io.Log.p(s); + } + } + + @Override + public void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + if (request) { + log("*** Request ***"); + log("uri: " + options.url()); + log("method: " + options.method()); + if (requestHeader) { + log("headers: " + DartRuntime.str(options.headers())); + } + if (requestBody) { + log("data: " + DartRuntime.str(options.data())); + } + } + handler.next(options); + } + + @Override + public void onResponse(Response response, ResponseInterceptorHandler handler) { + log("*** Response ***"); + log("statusCode: " + response.statusCode()); + if (responseHeader) { + log("headers: " + response.headers()); + } + if (responseBody) { + log("data: " + DartRuntime.str(response.data())); + } + handler.next(response); + } + + @Override + public void onError(DioException err, ErrorInterceptorHandler handler) { + if (error) { + log("*** DioException ***: " + err); + } + handler.next(err); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/MultipartFile.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/MultipartFile.java new file mode 100644 index 00000000000..413e732a68a --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/MultipartFile.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import dart.async.Future; +import dart.io.File; + +/// dio's `MultipartFile`: a file part of a FormData upload. +public class MultipartFile { + final byte[] bytes; + final String filename; + final String contentType; + + MultipartFile(byte[] bytes, String filename, String contentType) { + this.bytes = bytes; + this.filename = filename; + this.contentType = contentType == null ? "application/octet-stream" : contentType; + } + + public static Future fromFile(String path, String filename, Object contentType, + Object headers) { + try { + return Future.value(fromFileSync(path, filename, contentType, headers)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + + public static MultipartFile fromFileSync(String path, String filename, Object contentType, Object headers) { + byte[] data = new File(path).readAsBytesSync().toBytes(); + String name = filename; + if (name == null) { + int slash = path.lastIndexOf('/'); + name = slash >= 0 ? path.substring(slash + 1) : path; + } + return new MultipartFile(data, name, contentType == null ? null : String.valueOf(contentType)); + } + + public static MultipartFile fromBytes(java.util.List value, String filename, Object contentType, + Object headers) { + byte[] data = new byte[value.size()]; + for (int i = 0; i < data.length; i++) { + Object o = value.get(i); + data[i] = (byte) (o instanceof Number ? ((Number) o).intValue() : 0); + } + return new MultipartFile(data, filename, contentType == null ? null : String.valueOf(contentType)); + } + + public static MultipartFile fromString(String value, String filename, Object contentType, Object headers) { + byte[] data; + try { + data = value.getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + data = value.getBytes(); + } + return new MultipartFile(data, filename, contentType == null ? "text/plain" : String.valueOf(contentType)); + } + + public String filename() { + return filename; + } + + public long length() { + return bytes.length; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Options.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Options.java new file mode 100644 index 00000000000..a4d01d0f8e3 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Options.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// dio's `Options`: per-request overrides of the instance's BaseOptions. +public class Options extends OptionsBase { +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/OptionsBase.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/OptionsBase.java new file mode 100644 index 00000000000..35c9af8b1a7 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/OptionsBase.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import java.util.Map; + +import dart.core.DartMap; +import dart.core.Duration; +import dart.runtime.Funcs; + +/// The settings BaseOptions, Options and RequestOptions share. A value left null +/// falls through to the next level: request, then the Dio instance's options. +public abstract class OptionsBase { + String method; + DartMap headers; + DartMap extra; + String contentType; + ResponseType responseType; + Duration sendTimeout; + Duration receiveTimeout; + Funcs.Func1 validateStatus; + Boolean followRedirects; + + public String method() { + return method; + } + + public void method(String v) { + method = v; + } + + public DartMap headers() { + if (headers == null) { + headers = new DartMap(); + } + return headers; + } + + public void headers(Map v) { + headers = v == null ? null : copy(v); + } + + public DartMap extra() { + if (extra == null) { + extra = new DartMap(); + } + return extra; + } + + public void extra(Map v) { + extra = v == null ? null : copy(v); + } + + public String contentType() { + return contentType; + } + + public void contentType(String v) { + contentType = v; + } + + public ResponseType responseType() { + return responseType; + } + + public void responseType(ResponseType v) { + responseType = v; + } + + public Duration sendTimeout() { + return sendTimeout; + } + + public void sendTimeout(Duration v) { + sendTimeout = v; + } + + public Duration receiveTimeout() { + return receiveTimeout; + } + + public void receiveTimeout(Duration v) { + receiveTimeout = v; + } + + public Funcs.Func1 validateStatus() { + return validateStatus; + } + + public void validateStatus(Funcs.Func1 v) { + validateStatus = v; + } + + public Boolean followRedirects() { + return followRedirects; + } + + public void followRedirects(Boolean v) { + followRedirects = v; + } + + static DartMap copy(Map m) { + DartMap out = new DartMap(); + if (m != null) { + out.putAll(m); + } + return out; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/RequestInterceptorHandler.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/RequestInterceptorHandler.java new file mode 100644 index 00000000000..dea2e516ac7 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/RequestInterceptorHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// What an `onRequest` hook calls to continue: [#next] with the (possibly +/// changed) options, [#resolve] to answer without sending, or [#reject] to fail. +public class RequestInterceptorHandler { + private final Dio.Call call; + private final int index; + private boolean used; + + RequestInterceptorHandler(Dio.Call call, int index) { + this.call = call; + this.index = index; + } + + private boolean first() { + if (used) { + return false; + } + used = true; + return true; + } + + public void next(RequestOptions options) { + if (first()) { + call.request(index + 1, options); + } + } + + public void resolve(Response response) { + resolve(response, false); + } + + @SuppressWarnings("unchecked") + public void resolve(Response response, Boolean callFollowingResponseInterceptor) { + if (first()) { + if (callFollowingResponseInterceptor != null && callFollowingResponseInterceptor.booleanValue()) { + call.response(0, (Response) response); + } else { + call.succeed((Response) response); + } + } + } + + public void reject(DioException error) { + reject(error, false); + } + + public void reject(DioException error, Boolean callFollowingErrorInterceptor) { + if (first()) { + if (callFollowingErrorInterceptor != null && callFollowingErrorInterceptor.booleanValue()) { + call.error(0, error); + } else { + call.fail(error); + } + } + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/RequestOptions.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/RequestOptions.java new file mode 100644 index 00000000000..88f6b2a0e37 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/RequestOptions.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import java.util.List; +import java.util.Map; + +import com.codename1.io.Util; + +import dart.core.DartMap; +import dart.core.DartUri; +import dart.core.Duration; + +/// dio's `RequestOptions`: one request, fully resolved -- what interceptors see +/// and may change before it is sent. +public class RequestOptions extends OptionsBase { + String path = ""; + String baseUrl = ""; + DartMap queryParameters; + Object data; + Duration connectTimeout; + CancelToken cancelToken; + + public String path() { + return path; + } + + public void path(String v) { + path = v == null ? "" : v; + } + + public String baseUrl() { + return baseUrl; + } + + public void baseUrl(String v) { + baseUrl = v == null ? "" : v; + } + + public DartMap queryParameters() { + if (queryParameters == null) { + queryParameters = new DartMap(); + } + return queryParameters; + } + + public void queryParameters(Map v) { + queryParameters = v == null ? null : copy(v); + } + + public Object data() { + return data; + } + + public void data(Object v) { + data = v; + } + + public Duration connectTimeout() { + return connectTimeout; + } + + public void connectTimeout(Duration v) { + connectTimeout = v; + } + + public CancelToken cancelToken() { + return cancelToken; + } + + /// The URL that will be requested: the path, against `baseUrl` unless it is + /// absolute, with the query parameters appended (a list value repeats its key). + public String url() { + String url = path; + if (!(path.startsWith("http://") || path.startsWith("https://"))) { + String base = baseUrl == null ? "" : baseUrl; + if (base.endsWith("/") && path.startsWith("/")) { + url = base + path.substring(1); + } else if (!base.isEmpty() && !base.endsWith("/") && !path.isEmpty() && !path.startsWith("/")) { + url = base + "/" + path; + } else { + url = base + path; + } + } + StringBuilder q = new StringBuilder(); + if (queryParameters != null) { + for (Map.Entry e : queryParameters.entrySet()) { + Object v = e.getValue(); + if (v instanceof List) { + for (Object o : (List) v) { + appendParam(q, e.getKey(), o); + } + } else if (v != null) { + appendParam(q, e.getKey(), v); + } + } + } + if (q.length() == 0) { + return url; + } + return url + (url.indexOf('?') >= 0 ? "&" : "?") + q; + } + + private static void appendParam(StringBuilder q, String key, Object value) { + if (q.length() > 0) { + q.append('&'); + } + q.append(Util.encodeUrl(key)).append('=').append(Util.encodeUrl(dart.runtime.DartRuntime.str(value))); + } + + public DartUri uri() { + return DartUri.parse(url()); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Response.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Response.java new file mode 100644 index 00000000000..06491097c9f --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/Response.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import dart.runtime.DartRuntime; + +/// dio's `Response`. +public class Response { + T data; + Long statusCode; + String statusMessage; + Headers headers = new Headers(); + RequestOptions requestOptions; + dart.core.DartMap extra; + + public Response() { + } + + public T data() { + return data; + } + + public void data(T v) { + data = v; + } + + public Long statusCode() { + return statusCode; + } + + public void statusCode(Long v) { + statusCode = v; + } + + public String statusMessage() { + return statusMessage; + } + + public void statusMessage(String v) { + statusMessage = v; + } + + public Headers headers() { + return headers; + } + + public void headers(Headers v) { + headers = v == null ? new Headers() : v; + } + + public RequestOptions requestOptions() { + return requestOptions; + } + + public void requestOptions(RequestOptions v) { + requestOptions = v; + } + + public dart.core.DartMap extra() { + if (extra == null) { + extra = new dart.core.DartMap(); + } + return extra; + } + + public dart.core.DartUri realUri() { + return requestOptions == null ? null : requestOptions.uri(); + } + + @Override + public String toString() { + return DartRuntime.str(data); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseBody.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseBody.java new file mode 100644 index 00000000000..b5f9b361ee8 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseBody.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +import dart.async.Stream; +import dart.typed_data.Uint8List; + +/// dio's `ResponseBody`: what `ResponseType.stream` delivers as `response.data`. +/// ConnectionRequest has already read the whole body when the response completes, +/// so the stream carries it as a single chunk; code written against +/// `data.stream.listen(...)` or `await for` still sees every byte, in order. +public class ResponseBody { + private final Stream stream; + private final Long statusCode; + private final Long contentLength; + + public ResponseBody(byte[] body, Long statusCode) { + Uint8List bytes = Uint8List.fromBytes(body); + this.stream = Stream.value(bytes); + this.statusCode = statusCode; + this.contentLength = Long.valueOf(bytes.length()); + } + + public Stream stream() { + return stream; + } + + public Long statusCode() { + return statusCode; + } + + public Long contentLength() { + return contentLength; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseInterceptorHandler.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseInterceptorHandler.java new file mode 100644 index 00000000000..2ff50552245 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseInterceptorHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// What an `onResponse` hook calls to continue. +public class ResponseInterceptorHandler { + private final Dio.Call call; + private final int index; + private boolean used; + + ResponseInterceptorHandler(Dio.Call call, int index) { + this.call = call; + this.index = index; + } + + private boolean first() { + if (used) { + return false; + } + used = true; + return true; + } + + @SuppressWarnings("unchecked") + public void next(Response response) { + if (first()) { + call.response(index + 1, (Response) response); + } + } + + @SuppressWarnings("unchecked") + public void resolve(Response response) { + if (first()) { + call.succeed((Response) response); + } + } + + public void reject(DioException error) { + reject(error, false); + } + + public void reject(DioException error, Boolean callFollowingErrorInterceptor) { + if (first()) { + if (callFollowingErrorInterceptor != null && callFollowingErrorInterceptor.booleanValue()) { + call.error(0, error); + } else { + call.fail(error); + } + } + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseType.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseType.java new file mode 100644 index 00000000000..b97682240ac --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/dio/ResponseType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.dio; + +/// dio's `ResponseType`: how a response body reaches `Response.data`. +public enum ResponseType { + json, + stream, + plain, + bytes +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/equatable/Equatable.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/equatable/Equatable.java new file mode 100644 index 00000000000..6a18cc74fd5 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/equatable/Equatable.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.equatable; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import dart.core.DartList; +import dart.runtime.DartRuntime; + +/// equatable's `Equatable`: value equality over [#props], the way the package +/// defines it -- same runtime type, and props equal element by element, with lists, +/// sets and maps inside them compared by content rather than identity. +public abstract class Equatable { + + public abstract DartList props(); + + /// `stringify`: null defers to the global setting, which is on (debug builds + /// are where an app reads its states' toString). + public Boolean stringify() { + return null; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + return other instanceof Equatable && other.getClass() == getClass() + && deepEquals(props(), ((Equatable) other).props()); + } + + @Override + public int hashCode() { + return getClass().hashCode() ^ deepHash(props()); + } + + @Override + public String toString() { + Boolean s = stringify(); + String name = getClass().getSimpleName(); + if (s != null && !s.booleanValue()) { + return name; + } + StringBuilder sb = new StringBuilder(name).append('('); + List p = props(); + if (p != null) { + for (int i = 0; i < p.size(); i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(DartRuntime.str(p.get(i))); + } + } + return sb.append(')').toString(); + } + + static boolean deepEquals(Object a, Object b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + if (a instanceof List && b instanceof List) { + List x = (List) a; + List y = (List) b; + if (x.size() != y.size()) { + return false; + } + for (int i = 0; i < x.size(); i++) { + if (!deepEquals(x.get(i), y.get(i))) { + return false; + } + } + return true; + } + if (a instanceof Set && b instanceof Set) { + Set x = (Set) a; + Set y = (Set) b; + if (x.size() != y.size()) { + return false; + } + for (Object o : x) { + if (!containsDeep(y, o)) { + return false; + } + } + return true; + } + if (a instanceof Map && b instanceof Map) { + Map x = (Map) a; + Map y = (Map) b; + if (x.size() != y.size()) { + return false; + } + for (Map.Entry e : x.entrySet()) { + if (!y.containsKey(e.getKey()) || !deepEquals(e.getValue(), y.get(e.getKey()))) { + return false; + } + } + return true; + } + return DartRuntime.eq(a, b); + } + + private static boolean containsDeep(Set s, Object o) { + if (s.contains(o)) { + return true; + } + for (Iterator it = s.iterator(); it.hasNext();) { + if (deepEquals(it.next(), o)) { + return true; + } + } + return false; + } + + static int deepHash(Object o) { + if (o == null) { + return 0; + } + if (o instanceof List) { + int h = 1; + for (Object e : (List) o) { + h = h * 31 + deepHash(e); + } + return h; + } + if (o instanceof Set) { + int h = 0; + for (Object e : (Set) o) { + h += deepHash(e); + } + return h; + } + if (o instanceof Map) { + int h = 0; + for (Map.Entry e : ((Map) o).entrySet()) { + h += deepHash(e.getKey()) ^ deepHash(e.getValue()); + } + return h; + } + // deepEquals compares through DartRuntime.eq, where 1 == 1.0; the hash must agree. + return dart.runtime.DartRuntime.hashOf(o); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/AuthorizationStatus.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/AuthorizationStatus.java new file mode 100644 index 00000000000..7d171e988cb --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/AuthorizationStatus.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** firebase_messaging's AuthorizationStatus. */ +public enum AuthorizationStatus { + authorized, denied, notDetermined, provisional +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/Firebase.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/Firebase.java new file mode 100644 index 00000000000..6b4717f6c4c --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/Firebase.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +import dart.async.Future; +import dart.core.DartList; + +/** + * firebase_core's {@code Firebase}. Initialization holds the options: Firebase Auth talks + * to Google's REST API with their {@code apiKey}, the other services need nothing more. + */ +public final class Firebase { + public static final String DEFAULT_APP_NAME = "[DEFAULT]"; + + /** {@code Firebase.apps}. */ + public static final DartList apps = new DartList(); + + private Firebase() { + } + + public static Future initializeApp(String name, FirebaseOptions options) { + String n = name == null ? DEFAULT_APP_NAME : name; + FirebaseApp existing = find(n); + if (existing != null) { + return Future.value(existing); + } + FirebaseApp app = new FirebaseApp(n, options); + apps.add(app); + return Future.value(app); + } + + public static FirebaseApp app(String name) { + String n = name == null ? DEFAULT_APP_NAME : name; + FirebaseApp a = find(n); + if (a == null) { + throw new FirebaseException("core", "no-app", + "No Firebase App '" + n + "' has been created - call Firebase.initializeApp()"); + } + return a; + } + + static FirebaseApp find(String name) { + for (FirebaseApp a : apps) { + if (a.name().equals(name)) { + return a; + } + } + return null; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAnalytics.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAnalytics.java new file mode 100644 index 00000000000..5b0c4122760 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAnalytics.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +import java.util.Map; + +import com.codename1.analytics.AnalyticsService; +import com.codename1.io.Log; + +import dart.async.Future; + +/** + * firebase_analytics. Screen views go to Codename One's {@link AnalyticsService} + * (when the app has initialized one); every event is also written to the log, so an + * app that only logs analytics keeps running unchanged. + */ +public class FirebaseAnalytics { + /** {@code FirebaseAnalytics.instance}. */ + public static final FirebaseAnalytics instance = new FirebaseAnalytics(); + + private boolean enabled = true; + private String userId; + + FirebaseAnalytics() { + } + + public Future logEvent(String name, Map parameters, Object callOptions) { + if (enabled) { + Log.p("[analytics] " + name + (parameters == null ? "" : " " + parameters)); + } + return Future.value(null); + } + + public Future logScreenView(String screenClass, String screenName, Map parameters, + Object callOptions) { + String page = screenName != null ? screenName : screenClass; + if (enabled && page != null && AnalyticsService.isEnabled()) { + AnalyticsService.visit(page, null); + } + return logEvent("screen_view", parameters, null); + } + + public Future logLogin(String loginMethod, Map parameters, Object callOptions) { + return logEvent("login", parameters, null); + } + + public Future logSignUp(String signUpMethod, Map parameters, Object callOptions) { + return logEvent("sign_up", parameters, null); + } + + public Future logAppOpen(Map parameters, Object callOptions) { + return logEvent("app_open", parameters, null); + } + + public Future setUserId(String id, Object callOptions) { + userId = id; + return Future.value(null); + } + + public String userId() { + return userId; + } + + public Future setUserProperty(String name, String value, Object callOptions) { + return Future.value(null); + } + + public Future setAnalyticsCollectionEnabled(boolean on) { + enabled = on; + return Future.value(null); + } + + public Future setCurrentScreen(String screenName, String screenClassOverride, Object callOptions) { + return logScreenView(screenClassOverride, screenName, null, null); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseApp.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseApp.java new file mode 100644 index 00000000000..d579633778d --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseApp.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** firebase_core's {@code FirebaseApp}: a name and the options it was initialized with. */ +public class FirebaseApp { + private final String name; + private final FirebaseOptions options; + + FirebaseApp(String name, FirebaseOptions options) { + this.name = name; + this.options = options == null ? new FirebaseOptions() : options; + } + + public String name() { + return name; + } + + public FirebaseOptions options() { + return options; + } + + public Boolean isAutomaticDataCollectionEnabled() { + return Boolean.TRUE; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAuth.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAuth.java new file mode 100644 index 00000000000..fb970dcf07d --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAuth.java @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.codename1.io.ConnectionRequest; +import com.codename1.io.NetworkManager; +import com.codename1.io.Preferences; +import com.codename1.ui.Display; + +import dart.async.Completer; +import dart.async.Future; +import dart.async.Stream; +import dart.async.StreamController; +import dart.convert.Convert; +import dart.core.DartMap; +import dart.runtime.Funcs; + +/** + * firebase_auth over Google's Identity Toolkit REST API, authenticated with the + * {@code apiKey} the app passed to {@code Firebase.initializeApp}. Email/password and + * anonymous accounts are supported; the signed-in session (its refresh token) survives + * a restart in Preferences. Providers that need a platform SDK -- Google, Apple, phone + * -- fail with {@code operation-not-allowed}. + */ +public class FirebaseAuth { + /** {@code FirebaseAuth.instance}. */ + public static final FirebaseAuth instance = new FirebaseAuth(Firebase.DEFAULT_APP_NAME); + + /// One instance per FirebaseApp: a secondary app authenticates against its own + /// project (its own apiKey) and keeps its own current user and persisted session. + private static final Map BY_APP = new java.util.HashMap(); + + private static final String TOOLKIT = "https://identitytoolkit.googleapis.com/v1/accounts:"; + private static final String SECURE_TOKEN = "https://securetoken.googleapis.com/v1/token?key="; + + private final List> listeners = new ArrayList>(); + private User current; + private boolean restored; + private final String appName; + /// Preferences key prefix; the default app keeps the original keys so existing + /// sessions survive, other apps get their own. + private final String pref; + + FirebaseAuth(String appName) { + this.appName = appName; + this.pref = Firebase.DEFAULT_APP_NAME.equals(appName) ? "cn1_firebase_auth_" + : "cn1_firebase_auth_" + appName + "_"; + } + + public static FirebaseAuth instanceFor(FirebaseApp app, Object persistence) { + String name = app == null ? Firebase.DEFAULT_APP_NAME : app.name(); + if (Firebase.DEFAULT_APP_NAME.equals(name)) { + return instance; + } + synchronized (BY_APP) { + FirebaseAuth a = BY_APP.get(name); + if (a == null) { + a = new FirebaseAuth(name); + BY_APP.put(name, a); + } + return a; + } + } + + public User currentUser() { + restore(); + return current; + } + + public Stream authStateChanges() { + return changes(); + } + + public Stream idTokenChanges() { + return changes(); + } + + public Stream userChanges() { + return changes(); + } + + /** A stream that starts with the current user, as firebase_auth's does. */ + private Stream changes() { + final StreamController c = new StreamController(); + // Registered only while listened to: a stream every rebuild asks for and then + // cancels must not stay on the singleton for the life of the process. + c.onListen(new Funcs.VoidFunc0() { + @Override + public void call() { + synchronized (listeners) { + listeners.add(c); + } + c.add(currentUser()); + } + }); + c.onCancel(new Funcs.VoidFunc0() { + @Override + public void call() { + synchronized (listeners) { + listeners.remove(c); + } + } + }); + return c.stream(); + } + + private void publish() { + List> copy; + synchronized (listeners) { + copy = new ArrayList>(listeners); + } + for (StreamController c : copy) { + if (c.isClosed()) { + synchronized (listeners) { + listeners.remove(c); + } + } else if (c.hasListener()) { + c.add(current); + } + } + } + + // --- sign in / out -------------------------------------------------------- + + public Future signInWithEmailAndPassword(String email, String password) { + return signIn("signInWithPassword", DartMap.of("email", email, "password", password, + "returnSecureToken", Boolean.TRUE), false); + } + + public Future createUserWithEmailAndPassword(String email, String password) { + return signIn("signUp", DartMap.of("email", email, "password", password, + "returnSecureToken", Boolean.TRUE), false); + } + + public Future signInAnonymously() { + return signIn("signUp", DartMap.of("returnSecureToken", Boolean.TRUE), true); + } + + public Future signInWithCredential(Object credential) { + return Future.error(new FirebaseAuthException("operation-not-allowed", + "Only email/password and anonymous sign-in are supported in Codename One")); + } + + public Future signInWithProvider(Object provider) { + return signInWithCredential(provider); + } + + public Future signOut() { + current = null; + persist(); + publish(); + return Future.value(null); + } + + public Future sendPasswordResetEmail(String email, Object actionCodeSettings) { + return sendOob(null, "PASSWORD_RESET", email); + } + + public Future setLanguageCode(String code) { + return Future.value(null); + } + + private Future signIn(String op, Map body, final boolean anonymous) { + final Completer done = new Completer(); + call(TOOLKIT + op + "?key=", body, new Funcs.VoidFunc1>() { + @Override + public void call(Map r) { + User u = new User(FirebaseAuth.this, str(r.get("localId")), str(r.get("email")), anonymous); + u.displayName = str(r.get("displayName")); + u.idToken = str(r.get("idToken")); + u.refreshToken = str(r.get("refreshToken")); + u.expiresAt = expiry(r.get("expiresIn")); + current = u; + persist(); + publish(); + done.complete(new UserCredential(u)); + } + }, done); + return done.future(); + } + + Future sendOob(User user, String type, String email) { + final Completer done = new Completer(); + final DartMap body = DartMap.of("requestType", type); + if (email != null) { + body.put("email", email); + } + final Funcs.VoidFunc1> sent = new Funcs.VoidFunc1>() { + @Override + public void call(Map r) { + done.complete(null); + } + }; + if (user == null) { + call(TOOLKIT + "sendOobCode?key=", body, sent, done); + } else { + withFreshToken(user, done, new Funcs.VoidFunc1() { + @Override + public void call(String token) { + body.put("idToken", token); + FirebaseAuth.this.call(TOOLKIT + "sendOobCode?key=", body, sent, done); + } + }); + } + return done.future(); + } + + Future updateProfile(final User user, final String name, final String photo) { + final Completer done = new Completer(); + final DartMap body = DartMap.of("returnSecureToken", Boolean.FALSE); + if (name != null) { + body.put("displayName", name); + } + if (photo != null) { + body.put("photoUrl", photo); + } + withFreshToken(user, done, new Funcs.VoidFunc1() { + @Override + public void call(String token) { + body.put("idToken", token); + FirebaseAuth.this.call(TOOLKIT + "update?key=", body, new Funcs.VoidFunc1>() { + @Override + public void call(Map r) { + user.displayName = name; + user.photoURL = photo; + persist(); + publish(); + done.complete(null); + } + }, done); + } + }); + return done.future(); + } + + Future reloadUser(final User user) { + final Completer done = new Completer(); + withFreshToken(user, done, new Funcs.VoidFunc1() { + @Override + public void call(String token) { + FirebaseAuth.this.call(TOOLKIT + "lookup?key=", DartMap.of("idToken", token), + new Funcs.VoidFunc1>() { + @Override + public void call(Map r) { + Object users = r.get("users"); + Object first = users instanceof java.util.List && !((java.util.List) users).isEmpty() + ? ((java.util.List) users).get(0) : null; + if (first instanceof Map) { + Map u = (Map) first; + user.email = str(u.get("email")); + user.displayName = str(u.get("displayName")); + user.photoURL = str(u.get("photoUrl")); + user.emailVerified = Boolean.TRUE.equals(u.get("emailVerified")); + persist(); + publish(); + } + done.complete(null); + } + }, done); + } + }); + return done.future(); + } + + Future deleteUser(User user) { + final Completer done = new Completer(); + withFreshToken(user, done, new Funcs.VoidFunc1() { + @Override + public void call(String token) { + FirebaseAuth.this.call(TOOLKIT + "delete?key=", DartMap.of("idToken", token), + new Funcs.VoidFunc1>() { + @Override + public void call(Map r) { + current = null; + persist(); + publish(); + done.complete(null); + } + }, done); + } + }); + return done.future(); + } + + /// Runs {@code then} with a usable ID token. A session restored from Preferences + /// keeps only the refresh token, and a live one expires after an hour, so the + /// account calls go through {@link #idToken} -- which refreshes when needed -- + /// rather than reading {@code user.idToken}, which may be null or stale. + private void withFreshToken(User user, final Completer failed, final Funcs.VoidFunc1 then) { + idToken(user, false).then(then, new Funcs.VoidFunc1() { + @Override + public void call(Object err) { + failed.completeError(err); + } + }); + } + + Future idToken(final User user, boolean force) { + if (!force && user.idToken != null && System.currentTimeMillis() < user.expiresAt - 60000) { + return Future.value(user.idToken); + } + final Completer done = new Completer(); + String form = "grant_type=refresh_token&refresh_token=" + user.refreshToken; + call(SECURE_TOKEN, form, new Funcs.VoidFunc1>() { + @Override + public void call(Map r) { + user.idToken = str(r.get("id_token")); + user.refreshToken = str(r.get("refresh_token")); + user.expiresAt = expiry(r.get("expires_in")); + persist(); + done.complete(user.idToken); + } + }, done); + return done.future(); + } + + // --- transport -------------------------------------------------------------- + + private String apiKey() { + FirebaseApp app = Firebase.find(appName); + return app == null ? null : app.options().apiKey(); + } + + /** + * POSTs {@code body} (a map sent as JSON, or a form string) to {@code url + apiKey} + * and hands the decoded JSON reply to {@code ok}, or fails {@code failed} with the + * FirebaseAuthException the reply's error code maps to. + */ + private void call(String url, Object body, final Funcs.VoidFunc1> ok, + final Completer failed) { + String key = apiKey(); + if (key == null) { + failed.completeError(new FirebaseAuthException("no-app", + "Call Firebase.initializeApp with FirebaseOptions that carry an apiKey first")); + return; + } + if (!Display.isInitialized()) { + failed.completeError(new FirebaseAuthException("network-request-failed", + "A network error has occurred: Codename One is not initialized")); + return; + } + final boolean form = body instanceof String; + final byte[] payload; + try { + payload = (form ? (String) body : Convert.jsonEncode(body)).getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + failed.completeError(e); + return; + } + final ByteArrayOutputStream received = new ByteArrayOutputStream(); + ConnectionRequest req = new ConnectionRequest() { + private Exception failure; + + @Override + protected void buildRequestBody(OutputStream os) throws IOException { + os.write(payload); + } + + @Override + protected void readResponse(InputStream input) throws IOException { + byte[] buf = new byte[4096]; + int n; + while ((n = input.read(buf)) > 0) { + received.write(buf, 0, n); + } + } + + @Override + protected void handleErrorResponseCode(int code, String message) { + // the error body carries Firebase's code; read below + } + + /// Transport failures reach only this (NetworkManager never calls + /// postResponse after one), so they settle here, on the EDT. With + /// failSilently set they were only logged and the call stayed pending. + @Override + protected void handleException(Exception err) { + failure = err; + if (com.codename1.ui.Display.isInitialized() && !com.codename1.ui.Display.getInstance().isEdt()) { + com.codename1.ui.Display.getInstance().callSerially(new Runnable() { + @Override + public void run() { + postResponse(); + } + }); + } else { + postResponse(); + } + } + + private boolean settled; + + @Override + protected void postResponse() { + if (settled) { + return; + } + settled = true; + if (failure != null) { + failed.completeError(new FirebaseAuthException("network-request-failed", + String.valueOf(failure.getMessage()))); + return; + } + Map r; + try { + Object decoded = Convert.jsonDecode(new String(received.toByteArray(), "UTF-8")); + r = decoded instanceof Map ? asMap(decoded) : new DartMap(); + } catch (Exception e) { + failed.completeError(new FirebaseAuthException("internal-error", String.valueOf(e))); + return; + } + Object err = r.get("error"); + if (err instanceof Map || getResponseCode() >= 400) { + String raw = err instanceof Map ? str(((Map) err).get("message")) : null; + failed.completeError(mapError(raw == null ? "HTTP " + getResponseCode() : raw)); + return; + } + ok.call(r); + } + }; + req.setUrl(url + key); + req.setPost(true); + req.setHttpMethod("POST"); + req.setContentType(form ? "application/x-www-form-urlencoded" : "application/json"); + // Not failSilently: it keeps transport errors away from handleException. + req.setReadResponseForErrors(true); + req.setDuplicateSupported(true); + NetworkManager.getInstance().addToQueue(req); + } + + @SuppressWarnings("unchecked") + private static Map asMap(Object o) { + return (Map) o; + } + + /** Identity Toolkit's error strings ("EMAIL_NOT_FOUND : ...") to firebase_auth codes. */ + static FirebaseAuthException mapError(String raw) { + int sp = raw.indexOf(' '); + String key = sp < 0 ? raw : raw.substring(0, sp); + String code; + if (key.equals("EMAIL_NOT_FOUND")) { + code = "user-not-found"; + } else if (key.equals("INVALID_PASSWORD")) { + code = "wrong-password"; + } else if (key.equals("INVALID_LOGIN_CREDENTIALS")) { + code = "invalid-credential"; + } else if (key.equals("EMAIL_EXISTS")) { + code = "email-already-in-use"; + } else if (key.equals("WEAK_PASSWORD")) { + code = "weak-password"; + } else if (key.equals("INVALID_EMAIL")) { + code = "invalid-email"; + } else if (key.equals("USER_DISABLED")) { + code = "user-disabled"; + } else if (key.equals("TOO_MANY_ATTEMPTS_TRY_LATER")) { + code = "too-many-requests"; + } else if (key.equals("OPERATION_NOT_ALLOWED")) { + code = "operation-not-allowed"; + } else if (key.equals("TOKEN_EXPIRED") || key.equals("INVALID_ID_TOKEN") + || key.equals("INVALID_REFRESH_TOKEN")) { + code = "user-token-expired"; + } else { + code = "unknown"; + } + return new FirebaseAuthException(code, raw); + } + + private static String str(Object o) { + return o == null ? null : String.valueOf(o); + } + + private static long expiry(Object seconds) { + long s = 3600; + if (seconds != null) { + try { + s = Long.parseLong(String.valueOf(seconds)); + } catch (NumberFormatException ignore) { + // keep the hour Firebase issues + } + } + return System.currentTimeMillis() + s * 1000; + } + + // --- persistence ------------------------------------------------------------ + + private void persist() { + if (!Display.isInitialized()) { + return; + } + if (current == null) { + Preferences.delete(pref + "uid"); + return; + } + Preferences.set(pref + "uid", current.uid); + Preferences.set(pref + "email", current.email == null ? "" : current.email); + Preferences.set(pref + "name", current.displayName == null ? "" : current.displayName); + Preferences.set(pref + "refresh", current.refreshToken == null ? "" : current.refreshToken); + Preferences.set(pref + "anonymous", current.anonymous ? "true" : "false"); + Preferences.set(pref + "verified", current.emailVerified ? "true" : "false"); + } + + private void restore() { + if (restored || !Display.isInitialized()) { + return; + } + restored = true; + String uid = Preferences.get(pref + "uid", null); + if (uid == null || current != null) { + return; + } + User u = new User(this, uid, blankToNull(Preferences.get(pref + "email", "")), + "true".equals(Preferences.get(pref + "anonymous", "false"))); + u.displayName = blankToNull(Preferences.get(pref + "name", "")); + u.emailVerified = "true".equals(Preferences.get(pref + "verified", "false")); + u.refreshToken = blankToNull(Preferences.get(pref + "refresh", "")); + current = u; + } + + private static String blankToNull(String s) { + return s == null || s.length() == 0 ? null : s; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAuthException.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAuthException.java new file mode 100644 index 00000000000..503d505a35d --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseAuthException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** firebase_auth's {@code FirebaseAuthException}, with Firebase's kebab-case codes. */ +public class FirebaseAuthException extends FirebaseException { + public FirebaseAuthException(String code, String message) { + super("firebase_auth", code, message); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseException.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseException.java new file mode 100644 index 00000000000..3dff7baf5ca --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseException.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** firebase_core's {@code FirebaseException}: a plugin, a code and a message. */ +public class FirebaseException extends RuntimeException { + private final String plugin; + private final String code; + + public FirebaseException(String plugin, String code, String message) { + super(message); + this.plugin = plugin; + this.code = code; + } + + public String plugin() { + return plugin; + } + + public String code() { + return code; + } + + public String message() { + return getMessage(); + } + + @Override + public String toString() { + return "[" + plugin + "/" + code + "] " + getMessage(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseMessaging.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseMessaging.java new file mode 100644 index 00000000000..b58e10dfb88 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseMessaging.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +import com.codename1.ui.Display; + +import dart.async.Future; +import dart.async.Stream; +import dart.async.StreamController; +import dart.core.DartMap; +import dart.runtime.Funcs; + +/** + * firebase_messaging over Codename One push. {@link #getToken} answers the device's + * push key once {@code Display.registerPush} has run. Messages reach the Dart streams + * through {@link #deliver}, which the app's {@code PushCallback.push} forwards to; + * a message that arrives while the app is not running is not replayed. + */ +public class FirebaseMessaging { + /** {@code FirebaseMessaging.instance}. */ + public static final FirebaseMessaging instance = new FirebaseMessaging(); + + /** {@code FirebaseMessaging.onMessage}. */ + public static final Stream onMessage; + /** {@code FirebaseMessaging.onMessageOpenedApp}. */ + public static final Stream onMessageOpenedApp; + + private static final StreamController MESSAGES = StreamController.broadcast(); + private static final StreamController OPENED = StreamController.broadcast(); + private static final StreamController TOKENS = StreamController.broadcast(); + private static int received; + + static { + onMessage = MESSAGES.stream(); + onMessageOpenedApp = OPENED.stream(); + // Cold-start pushes after the first (which getInitialMessage hands over) wait + // for the app's first onMessageOpenedApp listener; sent at once, no Dart + // listener existed yet and the broadcast stream dropped them. + OPENED.onListen(new Funcs.VoidFunc0() { + @Override + public void call() { + new dart.async.Timer(dart.core.Duration.zero, new Funcs.VoidFunc0() { + @Override + public void call() { + String[] held; + synchronized (FirebaseMessaging.class) { + held = HELD.toArray(new String[HELD.size()]); + HELD.clear(); + } + for (String p : held) { + deliver(p, true); + } + } + }); + } + }); + } + + FirebaseMessaging() { + } + + /** Called with a push payload; plain text becomes the notification body. */ + /// Codename One's push callback, as the imported entry point forwards it. The + /// callback does not say whether a tap opened the app; a push arriving while the + /// app is minimized (the tap resumes it) or before it has started (the tap + /// launched it) is one the user opened, so it goes to onMessageOpenedApp. + /// + /// A push that arrives before the first form is shown launched the app. No Dart + /// listener can be on the broadcast stream yet, so it is held for + /// getInitialMessage, which is how firebase_messaging hands over a cold-start tap + /// (and it is not also sent to onMessageOpenedApp, as on Flutter). + public static void pushReceived(String payload) { + boolean cold = !com.codename1.ui.Display.isInitialized() + || com.codename1.ui.Display.getInstance().getCurrent() == null; + if (cold) { + synchronized (FirebaseMessaging.class) { + if (!initialTaken && initialMessage == null) { + initialMessage = message(payload); + return; + } + if (!OPENED.hasListener()) { + HELD.add(payload); + return; + } + } + } + deliver(payload, cold || com.codename1.ui.Display.getInstance().isMinimized()); + } + + private static final java.util.List HELD = new java.util.ArrayList(); + private static RemoteMessage initialMessage; + private static boolean initialTaken; + + private static RemoteMessage message(String payload) { + received++; + DartMap data = new DartMap(); + data.put("payload", payload); + return new RemoteMessage("cn1-" + received, data, new RemoteNotification(null, payload)); + } + + public static void deliver(String payload, boolean opened) { + RemoteMessage m = message(payload); + (opened ? OPENED : MESSAGES).add(m); + } + + /** Called when the push key is registered. */ + public static void tokenRegistered(String token) { + settlePending(token, null); + TOKENS.add(token); + } + + /** Called when push registration fails: a getToken waiting on it fails too. */ + public static void registrationFailed(String error) { + settlePending(null, error); + } + + /// getToken calls waiting for the registration they started. + private static final java.util.List> PENDING = + new java.util.ArrayList>(); + + private static void settlePending(String token, String error) { + dart.async.Completer[] waiting; + synchronized (PENDING) { + @SuppressWarnings("unchecked") + dart.async.Completer[] w = PENDING.toArray(new dart.async.Completer[PENDING.size()]); + waiting = w; + PENDING.clear(); + } + for (dart.async.Completer c : waiting) { + if (error != null) { + c.completeError(new dart.core.StateError("Push registration failed: " + error)); + } else { + c.complete(token); + } + } + } + + public static void onBackgroundMessage(Funcs.Func1 handler) { + // background isolates do not exist here; foreground delivery uses onMessage + } + + /// Where getToken reads the push key from, installed by the import-generated entry + /// point. This class must not name com.codename1.push itself: the Android builder + /// turns push on (and then demands google-services.json) for any app whose classes + /// reference that package, and this runtime ships in every imported app -- the + /// Flutter gallery, with no Firebase at all, stopped building. The entry point + /// references Push only when the app depends on firebase_messaging. + private static Funcs.Func0 tokenSource; + + public static void tokenSource(Funcs.Func0 source) { + tokenSource = source; + } + + public Future getToken(String vapidKey) { + Funcs.Func0 src = tokenSource; + if (!Display.isInitialized() || src == null) { + return Future.value(null); + } + String known = src.call(); + if (known != null) { + return Future.value(known); + } + // A fresh install has no key until registration runs, and on platforms with no + // permission prompt nothing else starts it: getToken is what obtains the token + // in Firebase, so it registers and completes when registeredForPush answers. + dart.async.Completer c = new dart.async.Completer(); + synchronized (PENDING) { + PENDING.add(c); + } + Display.getInstance().registerPush(); + return c.future(); + } + + public Future getAPNSToken() { + return getToken(null); + } + + public Stream onTokenRefresh() { + return TOKENS.stream(); + } + + public Future deleteToken() { + return Future.value(null); + } + + public Future requestPermission(Boolean alert, Boolean announcement, Boolean badge, + Boolean carPlay, Boolean criticalAlert, Boolean provisional, Boolean sound) { + if (Display.isInitialized()) { + Display.getInstance().registerPush(); + } + return Future.value(new NotificationSettings(AuthorizationStatus.authorized)); + } + + public Future getNotificationSettings() { + return Future.value(new NotificationSettings(AuthorizationStatus.authorized)); + } + + /** The message whose tap launched the app, once; null afterwards, as on Flutter. */ + public Future getInitialMessage() { + RemoteMessage m; + synchronized (FirebaseMessaging.class) { + m = initialMessage; + initialMessage = null; + initialTaken = true; + } + return Future.value(m); + } + + /// Codename One's Push topic API (FCM topics on Android), reached through a hook the + /// import-generated entry point installs -- like tokenSource, and for the same reason: + /// naming com.codename1.push here put it in every app that kept this package (an + /// auth-only Firebase app included), and the Android builder then turned on FCM and + /// required google-services.json. Arguments: the topic, and true to subscribe. With + /// no hook (no firebase_messaging dependency) or a port without topics it is ignored, + /// as it is for a native Codename One app. + private static Funcs.VoidFunc2 topicSink; + + public static void topicSink(Funcs.VoidFunc2 sink) { + topicSink = sink; + } + + public Future subscribeToTopic(String topic) { + return topic(topic, true); + } + + public Future unsubscribeFromTopic(String topic) { + return topic(topic, false); + } + + private static Future topic(String topic, boolean subscribe) { + Funcs.VoidFunc2 sink = topicSink; + if (sink != null) { + try { + sink.call(topic, Boolean.valueOf(subscribe)); + } catch (RuntimeException e) { + return Future.error(e); + } + } + return Future.value(null); + } + + public Future setForegroundNotificationPresentationOptions(Boolean alert, Boolean badge, + Boolean sound) { + return Future.value(null); + } + + public Future setAutoInitEnabled(boolean enabled) { + return Future.value(null); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseOptions.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseOptions.java new file mode 100644 index 00000000000..491cb68edcc --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/FirebaseOptions.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** firebase_core's {@code FirebaseOptions}, as flutterfire's firebase_options.dart builds it. */ +public class FirebaseOptions { + private String apiKey; + private String appId; + private String messagingSenderId; + private String projectId; + private String authDomain; + private String databaseURL; + private String storageBucket; + private String measurementId; + private String trackingId; + private String deepLinkURLScheme; + private String androidClientId; + private String iosClientId; + private String iosBundleId; + private String appGroupId; + + public FirebaseOptions() { + } + + public void apiKey(String v) { + this.apiKey = v; + } + + public String apiKey() { + return apiKey; + } + + public void appId(String v) { + this.appId = v; + } + + public String appId() { + return appId; + } + + public void messagingSenderId(String v) { + this.messagingSenderId = v; + } + + public String messagingSenderId() { + return messagingSenderId; + } + + public void projectId(String v) { + this.projectId = v; + } + + public String projectId() { + return projectId; + } + + public void authDomain(String v) { + this.authDomain = v; + } + + public String authDomain() { + return authDomain; + } + + public void databaseURL(String v) { + this.databaseURL = v; + } + + public String databaseURL() { + return databaseURL; + } + + public void storageBucket(String v) { + this.storageBucket = v; + } + + public String storageBucket() { + return storageBucket; + } + + public void measurementId(String v) { + this.measurementId = v; + } + + public String measurementId() { + return measurementId; + } + + public void trackingId(String v) { + this.trackingId = v; + } + + public String trackingId() { + return trackingId; + } + + public void deepLinkURLScheme(String v) { + this.deepLinkURLScheme = v; + } + + public String deepLinkURLScheme() { + return deepLinkURLScheme; + } + + public void androidClientId(String v) { + this.androidClientId = v; + } + + public String androidClientId() { + return androidClientId; + } + + public void iosClientId(String v) { + this.iosClientId = v; + } + + public String iosClientId() { + return iosClientId; + } + + public void iosBundleId(String v) { + this.iosBundleId = v; + } + + public String iosBundleId() { + return iosBundleId; + } + + public void appGroupId(String v) { + this.appGroupId = v; + } + + public String appGroupId() { + return appGroupId; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/NotificationSettings.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/NotificationSettings.java new file mode 100644 index 00000000000..3d2229e2cf4 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/NotificationSettings.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** firebase_messaging's NotificationSettings. */ +public class NotificationSettings { + private final AuthorizationStatus authorizationStatus; + + public NotificationSettings(AuthorizationStatus status) { + this.authorizationStatus = status; + } + + public AuthorizationStatus authorizationStatus() { + return authorizationStatus; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/RemoteMessage.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/RemoteMessage.java new file mode 100644 index 00000000000..953950903b4 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/RemoteMessage.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +import dart.core.DartMap; + +/** A push message, as firebase_messaging's RemoteMessage. */ +public class RemoteMessage { + private final String messageId; + private final DartMap data; + private final RemoteNotification notification; + + public RemoteMessage(String messageId, DartMap data, RemoteNotification notification) { + this.messageId = messageId; + this.data = data == null ? new DartMap() : data; + this.notification = notification; + } + + public String messageId() { + return messageId; + } + + public DartMap data() { + return data; + } + + public RemoteNotification notification() { + return notification; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/RemoteNotification.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/RemoteNotification.java new file mode 100644 index 00000000000..2ab3daadcef --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/RemoteNotification.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** The visible part of a push message. */ +public class RemoteNotification { + private final String title; + private final String body; + + public RemoteNotification(String title, String body) { + this.title = title; + this.body = body; + } + + public String title() { + return title; + } + + public String body() { + return body; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/User.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/User.java new file mode 100644 index 00000000000..6935bba9f13 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/User.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +import dart.async.Future; + +/** firebase_auth's {@code User}, from the Identity Toolkit's account record. */ +public class User { + final String uid; + String email; + String displayName; + String photoURL; + final boolean anonymous; + boolean emailVerified; + String idToken; + String refreshToken; + long expiresAt; + /// The FirebaseAuth that signed this user in or restored it: a secondary app's user + /// must refresh, update, reload and delete with that app's key and session, not the + /// default instance's. + final FirebaseAuth auth; + + User(FirebaseAuth auth, String uid, String email, boolean anonymous) { + this.auth = auth; + this.uid = uid; + this.email = email; + this.anonymous = anonymous; + } + + public String uid() { + return uid; + } + + public String email() { + return email; + } + + public String displayName() { + return displayName; + } + + public String photoURL() { + return photoURL; + } + + public boolean isAnonymous() { + return anonymous; + } + + public boolean emailVerified() { + return emailVerified; + } + + public String phoneNumber() { + return null; + } + + public String refreshToken() { + return refreshToken; + } + + /** The ID token, refreshed first when it has expired or {@code forceRefresh} asks. */ + public Future getIdToken(Boolean forceRefresh) { + return auth.idToken(this, forceRefresh != null && forceRefresh.booleanValue()); + } + + public Future updateDisplayName(String name) { + return auth.updateProfile(this, name, photoURL); + } + + public Future updatePhotoURL(String url) { + return auth.updateProfile(this, displayName, url); + } + + /** Re-reads the account (accounts:lookup), so a verified email shows as verified. */ + public Future reload() { + return auth.reloadUser(this); + } + + public Future delete() { + return auth.deleteUser(this); + } + + public Future sendEmailVerification(Object actionCodeSettings) { + return auth.sendOob(this, "VERIFY_EMAIL", null); + } + + @Override + public String toString() { + return "User(uid: " + uid + ", email: " + email + ")"; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/UserCredential.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/UserCredential.java new file mode 100644 index 00000000000..baac64f75af --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/firebase/UserCredential.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.firebase; + +/** firebase_auth's {@code UserCredential}. */ +public class UserCredential { + private final User user; + + UserCredential(User user) { + this.user = user; + } + + public User user() { + return user; + } + + public Object additionalUserInfo() { + return null; + } + + public Object credential() { + return null; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/Geolocator.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/Geolocator.java new file mode 100644 index 00000000000..fa4b0c1805f --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/Geolocator.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.geolocator; + +import com.codename1.location.Location; +import com.codename1.location.LocationListener; +import com.codename1.location.LocationManager; +import com.codename1.ui.Display; + +import dart.async.Completer; +import dart.async.Future; +import dart.async.Stream; +import dart.async.StreamController; + +/** + * geolocator, over Codename One's LocationManager. The platform asks for the location + * permission itself the first time a location is requested, so a running app reports + * {@code whileInUse}; without a display there is no location service at all, so + * permission is {@code denied} and a position request fails with + * LocationServiceDisabledException, as geolocator does when services are off. + */ +public final class Geolocator { + private static final double EARTH_RADIUS_M = 6378137.0; + + private Geolocator() { + } + + private static LocationManager manager() { + return Display.isInitialized() ? LocationManager.getLocationManager() : null; + } + + public static Future isLocationServiceEnabled() { + LocationManager lm = manager(); + boolean on = lm != null && (!lm.isGPSDetectionSupported() || lm.isGPSEnabled()); + return Future.value(Boolean.valueOf(on)); + } + + public static Future checkPermission() { + return Future.value(manager() == null ? LocationPermission.denied : LocationPermission.whileInUse); + } + + public static Future requestPermission() { + return checkPermission(); + } + + public static Future getCurrentPosition(Object desiredAccuracy, Object forceAndroidLocationManager, + Object timeLimit, LocationSettings locationSettings) { + final LocationManager lm = manager(); + if (lm == null) { + return Future.error(new LocationServiceDisabledException()); + } + // The explicit timeLimit wins over LocationSettings.timeLimit, as in the plugin. + // With a limit, a missing fix is a TimeoutException at that deadline; without + // one the plugin waits indefinitely, which here is a 30 s cap reported as the + // service being unavailable. + final Object limit = timeLimit != null ? timeLimit + : locationSettings == null ? null : locationSettings.timeLimit; + final long waitMs = limit instanceof dart.core.Duration + ? Math.max(0L, ((dart.core.Duration) limit).inMilliseconds()) : 30000L; + final Completer done = new Completer(); + Display.getInstance().scheduleBackgroundTask(new Runnable() { + @Override + public void run() { + final Location l = lm.getCurrentLocationSync(waitMs); + Display.getInstance().callSerially(new Runnable() { + @Override + public void run() { + if (l == null) { + done.completeError(limit != null + ? new dart.async.TimeoutException("Time limit reached while waiting for position update.", limit) + : new LocationServiceDisabledException()); + } else { + done.complete(Position.of(l)); + } + } + }); + } + }); + return done.future(); + } + + public static Future getLastKnownPosition(Object forceAndroidLocationManager) { + LocationManager lm = manager(); + Location l = lm == null ? null : lm.getLastKnownLocation(); + return Future.value(l == null ? null : Position.of(l)); + } + + /// The position streams someone is listening to. LocationManager holds a single + /// listener for the process, so one listener fans out to all of them; it is + /// installed with the first and removed with the last, which stops GPS updates + /// once every subscription is cancelled. + private static final java.util.List> LISTENING = + new java.util.ArrayList>(); + + private static final LocationListener FAN_OUT = new LocationListener() { + @Override + public void locationUpdated(Location location) { + Position p = Position.of(location); + for (StreamController c : new java.util.ArrayList>(LISTENING)) { + c.add(p); + } + } + + @Override + public void providerStateChanged(int newState) { + } + }; + + /** Positions as the device reports them, while anything listens. */ + public static Stream getPositionStream(LocationSettings locationSettings) { + final StreamController out = StreamController.broadcast(); + out.onListen(new dart.runtime.Funcs.VoidFunc0() { + @Override + public void call() { + LISTENING.add(out); + LocationManager lm = manager(); + if (LISTENING.size() == 1 && lm != null) { + lm.setLocationListener(FAN_OUT); + } + } + }); + out.onCancel(new dart.runtime.Funcs.VoidFunc0() { + @Override + public void call() { + LISTENING.remove(out); + LocationManager lm = manager(); + if (LISTENING.isEmpty() && lm != null) { + lm.setLocationListener(null); + } + } + }); + return out.stream(); + } + + /** Codename One has no portable way to open the system settings; answers false. */ + public static Future openAppSettings() { + return Future.value(Boolean.FALSE); + } + + public static Future openLocationSettings() { + return openAppSettings(); + } + + /** The great-circle distance in meters, by the haversine formula geolocator uses. */ + public static double distanceBetween(double startLatitude, double startLongitude, + double endLatitude, double endLongitude) { + double dLat = Math.toRadians(endLatitude - startLatitude); + double dLon = Math.toRadians(endLongitude - startLongitude); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(startLatitude)) * Math.cos(Math.toRadians(endLatitude)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + double c = 2 * com.codename1.util.MathUtil.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return EARTH_RADIUS_M * c; + } + + /** The initial bearing in degrees from the start to the end point. */ + public static double bearingBetween(double startLatitude, double startLongitude, + double endLatitude, double endLongitude) { + double lat1 = Math.toRadians(startLatitude); + double lat2 = Math.toRadians(endLatitude); + double dLon = Math.toRadians(endLongitude - startLongitude); + double y = Math.sin(dLon) * Math.cos(lat2); + double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon); + return Math.toDegrees(com.codename1.util.MathUtil.atan2(y, x)); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationAccuracy.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationAccuracy.java new file mode 100644 index 00000000000..d949a34d229 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationAccuracy.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.geolocator; + +/** geolocator's {@code LocationAccuracy}. */ +public enum LocationAccuracy { + lowest, low, medium, high, best, bestForNavigation, reduced +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationPermission.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationPermission.java new file mode 100644 index 00000000000..5a1e7ab8cf0 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationPermission.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.geolocator; + +/** geolocator's {@code LocationPermission}. */ +public enum LocationPermission { + denied, deniedForever, whileInUse, always, unableToDetermine +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationServiceDisabledException.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationServiceDisabledException.java new file mode 100644 index 00000000000..0697b384552 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationServiceDisabledException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.geolocator; + +/** geolocator's {@code LocationServiceDisabledException}. */ +public class LocationServiceDisabledException extends RuntimeException { + public LocationServiceDisabledException() { + super("The location service on the device is disabled."); + } + + @Override + public String toString() { + return getMessage(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationSettings.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationSettings.java new file mode 100644 index 00000000000..3d37422e970 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/LocationSettings.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.geolocator; + +/** geolocator's {@code LocationSettings}: accuracy, distance filter and time limit. */ +public class LocationSettings { + LocationAccuracy accuracy = LocationAccuracy.best; + long distanceFilter; + Object timeLimit; + + public LocationSettings() { + } + + public void accuracy(LocationAccuracy v) { + this.accuracy = v == null ? LocationAccuracy.best : v; + } + + public void distanceFilter(long v) { + this.distanceFilter = v; + } + + public void timeLimit(Object v) { + this.timeLimit = v; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/PermissionDeniedException.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/PermissionDeniedException.java new file mode 100644 index 00000000000..18dd88144bf --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/PermissionDeniedException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.geolocator; + +/** geolocator's {@code PermissionDeniedException}. */ +public class PermissionDeniedException extends RuntimeException { + public PermissionDeniedException(String message) { + super(message); + } + + @Override + public String toString() { + return getMessage(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/Position.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/Position.java new file mode 100644 index 00000000000..f93b6c25044 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/geolocator/Position.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.geolocator; + +import dart.core.DartMap; +import dart.core.DateTime; + +/** geolocator's {@code Position}. */ +public class Position { + private final double latitude; + private final double longitude; + private final DateTime timestamp; + private final double accuracy; + private final double altitude; + private final double heading; + private final double speed; + + public Position(double latitude, double longitude, DateTime timestamp, double accuracy, + double altitude, double altitudeAccuracy, double heading, double headingAccuracy, + double speed, double speedAccuracy, Object floor, Boolean isMocked) { + this.latitude = latitude; + this.longitude = longitude; + this.timestamp = timestamp; + this.accuracy = accuracy; + this.altitude = altitude; + this.heading = heading; + this.speed = speed; + } + + static Position of(com.codename1.location.Location l) { + return new Position(l.getLatitude(), l.getLongitude(), + DateTime.fromMillisecondsSinceEpoch(l.getTimeStamp(), false), l.getAccuracy(), + l.getAltitude(), 0, l.getDirection(), 0, l.getVelocity(), 0, null, Boolean.FALSE); + } + + public double latitude() { + return latitude; + } + + public double longitude() { + return longitude; + } + + public DateTime timestamp() { + return timestamp; + } + + public double accuracy() { + return accuracy; + } + + public double altitude() { + return altitude; + } + + public double altitudeAccuracy() { + return 0; + } + + public double heading() { + return heading; + } + + public double headingAccuracy() { + return 0; + } + + public double speed() { + return speed; + } + + public double speedAccuracy() { + return 0; + } + + public boolean isMocked() { + return false; + } + + public DartMap toJson() { + return DartMap.of("latitude", Double.valueOf(latitude), "longitude", Double.valueOf(longitude), + "timestamp", timestamp == null ? null : Long.valueOf(timestamp.millisecondsSinceEpoch()), + "accuracy", Double.valueOf(accuracy), "altitude", Double.valueOf(altitude), + "heading", Double.valueOf(heading), "speed", Double.valueOf(speed)); + } + + @Override + public String toString() { + return "Latitude: " + latitude + ", Longitude: " + longitude; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/BitmapDescriptor.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/BitmapDescriptor.java new file mode 100644 index 00000000000..3c134b6c6c1 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/BitmapDescriptor.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +/** google_maps_flutter's BitmapDescriptor; markers keep the default pin. */ +public final class BitmapDescriptor { + public static final double hueRed = 0; + public static final double hueOrange = 30; + public static final double hueYellow = 60; + public static final double hueGreen = 120; + public static final double hueCyan = 180; + public static final double hueAzure = 210; + public static final double hueBlue = 240; + public static final double hueViolet = 270; + public static final double hueMagenta = 300; + public static final double hueRose = 330; + public static final BitmapDescriptor defaultMarker = new BitmapDescriptor(); + + private BitmapDescriptor() { + } + + public static BitmapDescriptor defaultMarkerWithHue(double hue) { + return defaultMarker; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/CameraPosition.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/CameraPosition.java new file mode 100644 index 00000000000..b745551c805 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/CameraPosition.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +/** google_maps_flutter's CameraPosition. */ +public class CameraPosition { + private LatLng target; + private double zoom; + private double bearing; + private double tilt; + + public CameraPosition() { + } + + public void target(LatLng v) { + target = v; + } + + public void zoom(double v) { + zoom = v; + } + + public void bearing(double v) { + bearing = v; + } + + public void tilt(double v) { + tilt = v; + } + + public LatLng target() { + return target; + } + + public double zoom() { + return zoom; + } + + public double bearing() { + return bearing; + } + + public double tilt() { + return tilt; + } + + static CameraPosition of(com.codename1.maps.CameraPosition p) { + CameraPosition c = new CameraPosition(); + c.target = LatLng.of(p.getTarget()); + c.zoom = p.getZoom(); + c.bearing = p.getBearing(); + c.tilt = p.getTilt(); + return c; + } + + com.codename1.maps.CameraPosition toCn1() { + return new com.codename1.maps.CameraPosition(target == null ? new com.codename1.maps.LatLng(0, 0) + : target.toCn1(), zoom, bearing, tilt); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/CameraUpdate.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/CameraUpdate.java new file mode 100644 index 00000000000..28e7c971833 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/CameraUpdate.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +/** google_maps_flutter's CameraUpdate factories. */ +public final class CameraUpdate { + final LatLng target; + final Double zoom; + final double zoomBy; + final CameraPosition position; + + private CameraUpdate(LatLng target, Double zoom, double zoomBy, CameraPosition position) { + this.target = target; + this.zoom = zoom; + this.zoomBy = zoomBy; + this.position = position; + } + + public static CameraUpdate newLatLng(LatLng target) { + return new CameraUpdate(target, null, 0, null); + } + + public static CameraUpdate newLatLngZoom(LatLng target, double zoom) { + return new CameraUpdate(target, Double.valueOf(zoom), 0, null); + } + + public static CameraUpdate newCameraPosition(CameraPosition p) { + return new CameraUpdate(null, null, 0, p); + } + + public static CameraUpdate zoomIn() { + return new CameraUpdate(null, null, 1, null); + } + + public static CameraUpdate zoomOut() { + return new CameraUpdate(null, null, -1, null); + } + + public static CameraUpdate zoomTo(double zoom) { + return new CameraUpdate(null, Double.valueOf(zoom), 0, null); + } + + public static CameraUpdate zoomBy(double amount, Object focus) { + return new CameraUpdate(null, null, amount, null); + } + + /** Applies this update to {@code p}, returning the camera it moves to. */ + CameraPosition applyTo(CameraPosition p) { + if (position != null) { + return position; + } + CameraPosition out = new CameraPosition(); + out.target(target != null ? target : p.target()); + out.zoom((zoom != null ? zoom.doubleValue() : p.zoom()) + zoomBy); + out.bearing(p.bearing()); + out.tilt(p.tilt()); + return out; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMap.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMap.java new file mode 100644 index 00000000000..5bf374a279e --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMap.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +import com.codename1.flutter.Element; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/** + * google_maps_flutter's GoogleMap, hosting Codename One's {@code MapView} -- native + * maps where the platform has them, vector tiles elsewhere. Markers, the camera, + * taps and camera moves are supported; polylines, polygons, circles and map styles + * are accepted and not drawn. + */ +public class GoogleMap extends Widget { + CameraPosition initialCameraPosition = new CameraPosition(); + Iterable markers; + MapType mapType = MapType.normal; + Funcs.VoidFunc1 onMapCreated; + Funcs.VoidFunc1 onTap; + Funcs.VoidFunc1 onLongPress; + Funcs.VoidFunc1 onCameraMove; + Funcs.VoidFunc0 onCameraIdle; + + public GoogleMap() { + } + + public void initialCameraPosition(CameraPosition v) { + initialCameraPosition = v; + } + + public void markers(Iterable v) { + markers = v; + } + + public void mapType(MapType v) { + mapType = v; + } + + public void onMapCreated(Funcs.VoidFunc1 v) { + onMapCreated = v; + } + + public void onTap(Funcs.VoidFunc1 v) { + onTap = v; + } + + public void onLongPress(Funcs.VoidFunc1 v) { + onLongPress = v; + } + + public void onCameraMove(Funcs.VoidFunc1 v) { + onCameraMove = v; + } + + public void onCameraIdle(Funcs.VoidFunc0 v) { + onCameraIdle = v; + } + + /** Accepted and ignored. */ + public void polylines(Object v) { + } + + public void polygons(Object v) { + } + + public void circles(Object v) { + } + + public void myLocationEnabled(boolean v) { + } + + public void myLocationButtonEnabled(boolean v) { + } + + public void zoomControlsEnabled(boolean v) { + } + + public void zoomGesturesEnabled(boolean v) { + } + + public void scrollGesturesEnabled(boolean v) { + } + + public void rotateGesturesEnabled(boolean v) { + } + + public void tiltGesturesEnabled(boolean v) { + } + + public void compassEnabled(boolean v) { + } + + public void mapToolbarEnabled(boolean v) { + } + + public void trafficEnabled(boolean v) { + } + + public void buildingsEnabled(boolean v) { + } + + public void liteModeEnabled(boolean v) { + } + + public void padding(Object v) { + } + + public void minMaxZoomPreference(Object v) { + } + + public void gestureRecognizers(Object v) { + } + + public void style(String v) { + } + + @Override + public Element createElement() { + return new GoogleMapRenderElement(this); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMapController.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMapController.java new file mode 100644 index 00000000000..9bc4b184c73 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMapController.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +import com.codename1.maps.MapView; + +import dart.async.Future; + +/** google_maps_flutter's GoogleMapController, driving the element's MapView. */ +public class GoogleMapController { + private final GoogleMapRenderElement element; + + GoogleMapController(GoogleMapRenderElement element) { + this.element = element; + } + + public Future animateCamera(CameraUpdate update, Object duration) { + return moveCamera(update); + } + + public Future moveCamera(CameraUpdate update) { + element.camera(update.applyTo(element.camera())); + return Future.value(null); + } + + public Future getZoomLevel() { + return Future.value(Double.valueOf(element.camera().zoom())); + } + + public Future showMarkerInfoWindow(MarkerId id) { + return Future.value(null); + } + + public Future hideMarkerInfoWindow(MarkerId id) { + return Future.value(null); + } + + public Future setMapStyle(String style) { + return Future.value(null); + } + + public void dispose() { + } + + MapView view() { + return element.view(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMapRenderElement.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMapRenderElement.java new file mode 100644 index 00000000000..e473180dfb0 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/GoogleMapRenderElement.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +import java.util.ArrayList; +import java.util.List; + +import com.codename1.flutter.RenderElement; +import com.codename1.flutter.rendering.BoxConstraints; +import com.codename1.flutter.rendering.Dp; +import com.codename1.flutter.rendering.Size; +import com.codename1.maps.CameraChangeListener; +import com.codename1.maps.MapSurface; +import com.codename1.maps.MapTapListener; +import com.codename1.maps.MapView; +import com.codename1.maps.MarkerOptions; +import com.codename1.ui.Component; +import com.codename1.ui.Display; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; + +import dart.runtime.Funcs; + +/** + * Hosts a {@link MapView} for {@link GoogleMap}. Headless (tests), no view is created: + * the camera is tracked here, so a controller still answers it. + */ +public class GoogleMapRenderElement extends RenderElement { + private static final double DEFAULT_SIZE_LP = 300; + + private MapView view; + private CameraPosition camera; + private final List placed = new ArrayList(); + private boolean created; + + public GoogleMapRenderElement(GoogleMap widget) { + super(widget); + camera = widget.initialCameraPosition; + } + + private GoogleMap map() { + return (GoogleMap) widget(); + } + + MapView view() { + return view; + } + + CameraPosition camera() { + if (view != null) { + camera = CameraPosition.of(view.getCameraPosition()); + } + return camera; + } + + void camera(CameraPosition p) { + camera = p; + if (view != null) { + view.setCameraPosition(p.toCn1()); + } + } + + @Override + protected Component createComponent() { + if (Display.isInitialized()) { + view = new MapView(); + view.setCameraPosition(camera.toCn1()); + view.addTapListener(new MapTapListener() { + @Override + public void mapTapped(MapSurface m, com.codename1.maps.LatLng at, int x, int y) { + Funcs.VoidFunc1 h = map().onTap; + if (h != null) { + h.call(LatLng.of(at)); + } + } + }); + view.addLongPressListener(new MapTapListener() { + @Override + public void mapTapped(MapSurface m, com.codename1.maps.LatLng at, int x, int y) { + Funcs.VoidFunc1 h = map().onLongPress; + if (h != null) { + h.call(LatLng.of(at)); + } + } + }); + // CameraChangeListener fires once the camera settles, which is onCameraIdle. + // onCameraMove must follow the gesture, so while a pointer is down (and until + // the camera settles) the camera is sampled every frame-ish tick and each + // change is reported as it happens. + ActionListener startTracking = new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + startMoveTracking(); + } + }; + view.asComponent().addPointerPressedListener(startTracking); + view.asComponent().addPointerDraggedListener(startTracking); + view.addCameraChangeListener(new CameraChangeListener() { + @Override + public void cameraChanged(MapSurface m, com.codename1.maps.CameraPosition p) { + stopMoveTracking(); + reportMove(p); + if (map().onCameraIdle != null) { + map().onCameraIdle.call(); + } + } + }); + placeMarkers(); + } + notifyCreated(); + return view == null ? null : view.asComponent(); + } + + private com.codename1.ui.util.UITimer moveTimer; + private com.codename1.maps.CameraPosition lastMove; + private int stillTicks; + + @Override + public void unmount() { + // the poll must not outlive the map, nor call map() on an unmounted element + stopMoveTracking(); + super.unmount(); + } + + private void startMoveTracking() { + if (moveTimer != null || view == null || map().onCameraMove == null) { + return; + } + Component c = view.asComponent(); + if (c.getComponentForm() == null) { + return; + } + moveTimer = new com.codename1.ui.util.UITimer(new Runnable() { + @Override + public void run() { + if (view == null) { + stopMoveTracking(); + return; + } + // A tap that never moves the camera fires no CameraChangeListener, so + // the poll also ends itself once the camera has been still ~500 ms. + if (reportMove(view.getCameraPosition())) { + stillTicks = 0; + } else if (++stillTicks >= 10) { + stopMoveTracking(); + } + } + }); + stillTicks = 0; + moveTimer.schedule(50, true, c.getComponentForm()); + } + + private void stopMoveTracking() { + if (moveTimer != null) { + moveTimer.cancel(); + moveTimer = null; + } + } + + /// Reports the camera to onCameraMove when it differs from the last report; + /// answers whether it did. + private boolean reportMove(com.codename1.maps.CameraPosition p) { + if (p == null) { + return false; + } + camera = CameraPosition.of(p); + if (lastMove != null && sameCamera(lastMove, p)) { + return false; + } + lastMove = p; + if (map().onCameraMove != null) { + map().onCameraMove.call(camera); + } + return true; + } + + private static boolean sameCamera(com.codename1.maps.CameraPosition a, com.codename1.maps.CameraPosition b) { + return a.getZoom() == b.getZoom() && a.getBearing() == b.getBearing() && a.getTilt() == b.getTilt() + && a.getTarget().getLatitude() == b.getTarget().getLatitude() + && a.getTarget().getLongitude() == b.getTarget().getLongitude(); + } + + @Override + protected void updateComponent(Component c) { + placeMarkers(); + } + + private void notifyCreated() { + if (created) { + return; + } + created = true; + final Funcs.VoidFunc1 h = map().onMapCreated; + if (h == null) { + return; + } + final GoogleMapController c = new GoogleMapController(this); + if (!Display.isInitialized()) { + h.call(c); + return; + } + // after this frame, as Flutter does: the callback commonly calls setState + Display.getInstance().callSerially(new Runnable() { + @Override + public void run() { + h.call(c); + } + }); + } + + private void placeMarkers() { + if (view == null) { + return; + } + for (com.codename1.maps.Marker m : placed) { + view.removeMarker(m); + } + placed.clear(); + if (map().markers == null) { + return; + } + for (Object o : map().markers) { + if (!(o instanceof Marker)) { + continue; + } + final Marker mk = (Marker) o; + if (!mk.visible()) { + continue; + } + MarkerOptions opts = new MarkerOptions(mk.position().toCn1()).draggable(mk.draggable()); + if (mk.infoWindow() != null) { + opts.title(mk.infoWindow().title()).snippet(mk.infoWindow().snippet()); + } + if (mk.tapHandler() != null) { + opts.onClick(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + mk.tapHandler().call(); + } + }); + } + placed.add(view.addMarker(opts)); + } + } + + @Override + protected Size performLayout(BoxConstraints constraints) { + double w = constraints.hasBoundedWidth() ? constraints.maxWidth() : Dp.px(DEFAULT_SIZE_LP); + double h = constraints.hasBoundedHeight() ? constraints.maxHeight() : Dp.px(DEFAULT_SIZE_LP); + return constraints.constrain(new Size(w, h)); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/InfoWindow.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/InfoWindow.java new file mode 100644 index 00000000000..2c02d63f285 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/InfoWindow.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +import dart.runtime.Funcs; + +/** google_maps_flutter's InfoWindow. */ +public class InfoWindow { + private String title; + private String snippet; + private Funcs.VoidFunc0 onTap; + + public InfoWindow() { + } + + public void title(String v) { + title = v; + } + + public void snippet(String v) { + snippet = v; + } + + public void onTap(Funcs.VoidFunc0 v) { + onTap = v; + } + + public String title() { + return title; + } + + public String snippet() { + return snippet; + } + + Funcs.VoidFunc0 tapHandler() { + return onTap; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/LatLng.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/LatLng.java new file mode 100644 index 00000000000..bb3eeee4c39 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/LatLng.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +/** google_maps_flutter's LatLng. */ +public class LatLng { + private final double latitude; + private final double longitude; + + public LatLng(double latitude, double longitude) { + this.latitude = Math.max(-90, Math.min(90, latitude)); + double lng = longitude; + while (lng > 180) { + lng -= 360; + } + while (lng < -180) { + lng += 360; + } + this.longitude = lng; + } + + public double latitude() { + return latitude; + } + + public double longitude() { + return longitude; + } + + com.codename1.maps.LatLng toCn1() { + return new com.codename1.maps.LatLng(latitude, longitude); + } + + static LatLng of(com.codename1.maps.LatLng l) { + return l == null ? null : new LatLng(l.getLatitude(), l.getLongitude()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof LatLng)) { + return false; + } + LatLng l = (LatLng) o; + return l.latitude == latitude && l.longitude == longitude; + } + + @Override + public int hashCode() { + long b = Double.doubleToLongBits(latitude) * 31 + Double.doubleToLongBits(longitude); + return (int) (b ^ (b >>> 32)); + } + + @Override + public String toString() { + return "LatLng(" + latitude + ", " + longitude + ")"; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/MapType.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/MapType.java new file mode 100644 index 00000000000..f1c8cfea9ce --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/MapType.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +/** google_maps_flutter's MapType. */ +public enum MapType { + none, normal, satellite, terrain, hybrid +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/Marker.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/Marker.java new file mode 100644 index 00000000000..bfddf08079a --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/Marker.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +import dart.runtime.Funcs; + +/** google_maps_flutter's Marker: an id, a position, and an info window. */ +public class Marker { + private MarkerId markerId; + private LatLng position = new LatLng(0, 0); + private InfoWindow infoWindow; + private boolean draggable; + private boolean visible = true; + private Funcs.VoidFunc0 onTap; + + public Marker() { + } + + public void markerId(MarkerId v) { + markerId = v; + } + + public void position(LatLng v) { + position = v; + } + + public void infoWindow(InfoWindow v) { + infoWindow = v; + } + + public void draggable(boolean v) { + draggable = v; + } + + public void visible(boolean v) { + visible = v; + } + + public void onTap(Funcs.VoidFunc0 v) { + onTap = v; + } + + /** Accepted and ignored: markers use the platform's default pin. */ + public void icon(Object v) { + } + + public void alpha(double v) { + } + + public void rotation(double v) { + } + + public void zIndex(double v) { + } + + public void onDragEnd(Funcs.VoidFunc1 v) { + } + + public MarkerId markerId() { + return markerId; + } + + public LatLng position() { + return position; + } + + public InfoWindow infoWindow() { + return infoWindow; + } + + public boolean draggable() { + return draggable; + } + + public boolean visible() { + return visible; + } + + Funcs.VoidFunc0 tapHandler() { + return onTap; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/MarkerId.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/MarkerId.java new file mode 100644 index 00000000000..43650d95d7a --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/googlemaps/MarkerId.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.googlemaps; + +/** google_maps_flutter's MarkerId. */ +public class MarkerId { + private final String value; + + public MarkerId(String value) { + this.value = value; + } + + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + return o instanceof MarkerId && String.valueOf(value).equals(String.valueOf(((MarkerId) o).value)); + } + + @Override + public int hashCode() { + return value == null ? 0 : value.hashCode(); + } + + @Override + public String toString() { + return "MarkerId(" + value + ")"; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoPage.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoPage.java new file mode 100644 index 00000000000..009a09054ac --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoPage.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.gorouter; + +import com.codename1.flutter.Widget; + +/** + * The {@code Page} a {@code pageBuilder} returns -- {@code MaterialPage}, + * {@code NoTransitionPage} and {@code CustomTransitionPage} all map here. Only the + * page's child is shown; its transition is the platform's own. + */ +public class GoPage { + private Widget child; + private Object key; + private String name; + private Object arguments; + + public GoPage() { + } + + public void child(Widget v) { + this.child = v; + } + + public Widget child() { + return child; + } + + public void key(Object v) { + this.key = v; + } + + public Object key() { + return key; + } + + public void name(String v) { + this.name = v; + } + + public String name() { + return name; + } + + public void arguments(Object v) { + this.arguments = v; + } + + public Object arguments() { + return arguments; + } + + /** Accepted for source compatibility; see the class comment. */ + public void transitionsBuilder(Object v) { + } + + /** Accepted for source compatibility. */ + public void transitionDuration(Object v) { + } + + /** Accepted for source compatibility. */ + public void fullscreenDialog(Object v) { + } + + /** Accepted for source compatibility. */ + public void maintainState(Object v) { + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRoute.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRoute.java new file mode 100644 index 00000000000..912e7b9193d --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRoute.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.gorouter; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/** + * go_router's {@code GoRoute}: a path pattern ({@code /pets/:id}, or relative to its + * parent when it has no leading slash) and the builder of the page shown for it. + */ +public class GoRoute extends RouteBase { + private String path; + private String name; + private Funcs.Func2 builder; + private Funcs.Func2 pageBuilder; + private Funcs.Func2 redirect; + + public GoRoute() { + } + + public void path(String v) { + this.path = v; + } + + public String path() { + return path; + } + + public void name(String v) { + this.name = v; + } + + public String name() { + return name; + } + + public void builder(Funcs.Func2 v) { + this.builder = v; + } + + /** + * A builder of a {@code Page}. Codename One has no page objects of its own, so the + * page's child is what gets shown -- see {@link GoRouter#buildPage}. + */ + public void pageBuilder(Funcs.Func2 v) { + this.pageBuilder = v; + } + + public void redirect(Funcs.Func2 v) { + this.redirect = v; + } + + Funcs.Func2 builder() { + return builder; + } + + Funcs.Func2 pageBuilder() { + return pageBuilder; + } + + Funcs.Func2 redirectFunc() { + return redirect; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouter.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouter.java new file mode 100644 index 00000000000..646948bafd5 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouter.java @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.gorouter; + +import java.util.ArrayList; +import java.util.List; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.Widget; +import com.codename1.flutter.navigation.MaterialPageRoute; +import com.codename1.flutter.navigation.Navigator; +import com.codename1.flutter.navigation.Route; +import com.codename1.flutter.navigation.RouteSettings; +import com.codename1.flutter.navigation.RouterConfigSource; + +import dart.async.Completer; +import dart.async.Future; +import dart.core.DartMap; +import dart.core.DartUri; +import dart.runtime.Funcs; + +/** + * go_router's {@code GoRouter}, over the Codename One Navigator: a location is matched + * against the route tree, its redirects are applied and the matched route's builder + * becomes a pushed page. {@code go} replaces the pushed pages, {@code push} stacks one. + * + * Nested routes are matched by joining their paths, but {@code go} to a nested + * location shows only the deepest page -- the parent pages are not stacked below it, + * so back from there returns to the app's first page. + */ +public class GoRouter implements RouterConfigSource { + private static final int DEFAULT_REDIRECT_LIMIT = 5; + private static GoRouter current; + + private final List routes = new ArrayList(); + private String initialLocation = "/"; + private Funcs.Func2 redirect; + private Funcs.Func2 errorBuilder; + private int redirectLimit = DEFAULT_REDIRECT_LIMIT; + /** + * The error of an asynchronous redirect that failed, consumed by the next routeFor. + * A failed auth guard must not read as "no redirect" and open the guarded page, so + * the navigation shows the error page (errorBuilder) instead, as go_router does. + */ + private Object redirectFailure; + /** Set by redirectOf when a redirect answered a Future not yet complete. */ + private boolean sawPendingRedirect; + private GoRouterState state; + /// Where the base (runApp) route landed after its redirects; null until it is built. + private String baseLocation; + /// The location routeFor's last call landed on after redirects. + private String resolvedLocation; + private BuildContext appContext; + + public GoRouter() { + current = this; + } + + public void routes(Object v) { + routes.clear(); + if (v instanceof Iterable) { + for (Object o : (Iterable) v) { + if (o instanceof RouteBase) { + routes.add((RouteBase) o); + } + } + } + } + + public void initialLocation(String v) { + this.initialLocation = v == null ? "/" : v; + } + + public void redirect(Funcs.Func2 v) { + this.redirect = v; + } + + public void errorBuilder(Funcs.Func2 v) { + this.errorBuilder = v; + } + + public void redirectLimit(long v) { + this.redirectLimit = (int) v; + } + + /** Accepted for source compatibility. */ + public void debugLogDiagnostics(boolean v) { + } + + /** Accepted for source compatibility. */ + public void navigatorKey(Object v) { + } + + /** Accepted for source compatibility; redirects are evaluated on each navigation. */ + public void refreshListenable(Object v) { + } + + /** The router of the running app: the one installed last. */ + public static GoRouter of(BuildContext context) { + return current; + } + + public static GoRouter maybeOf(BuildContext context) { + return current; + } + + /** The state of the location shown last. */ + public GoRouterState state() { + return state; + } + + @Override + public String initialLocation() { + return initialLocation; + } + + @Override + public void attached(BuildContext context) { + current = this; + appContext = context; + } + + @Override + public Funcs.Func1 routeGenerator() { + return new Funcs.Func1() { + @Override + public Route call(RouteSettings settings) { + final Object extra = settings.arguments(); + sawPendingRedirect = false; + Route r = routeFor(settings.name(), extra); + if (!sawPendingRedirect) { + baseLocation = resolvedLocation; + return r; + } + // The initial route had an asynchronous redirect still running (an auth + // check reading its session). Mounting what routeFor chose would show the + // guarded page; instead the page stays empty until the redirect chain + // resolves, then shows wherever it lands (or the error page if it failed). + final Completer landed = new Completer(); + whenRedirected(settings.name(), extra, new Funcs.VoidFunc1() { + @Override + public void call(String to) { + baseLocation = to; + landed.complete(to); + } + }); + MaterialPageRoute wait = new MaterialPageRoute(); + wait.settings(settings); + wait.builder(new Funcs.Func1() { + @Override + public Widget call(BuildContext ctx) { + com.codename1.flutter.widgets.FutureBuilder fb = + new com.codename1.flutter.widgets.FutureBuilder(); + fb.future(landed.future()); + fb.builder(new Funcs.Func2, Widget>() { + @Override + public Widget call(BuildContext c, + com.codename1.flutter.widgets.AsyncSnapshot snap) { + if (!snap.hasData()) { + return new com.codename1.flutter.widgets.SizedBox(); + } + Route real = routeFor(snap.data(), extra); + return ((MaterialPageRoute) real).getBuilder().call(c); + } + }); + return fb; + } + }); + return wait; + } + }; + } + + // --- navigation --------------------------------------------------------- + + public void go(String location) { + go(location, null); + } + + /** Shows {@code location} in place of every pushed page. */ + public void go(String location, final Object extra) { + while (Navigator.stackSize() > 0) { + Navigator.pop(null); + } + // Redirects run first, even for the initial location: '/' that sent a signed-out + // user to /login must reach the home page after sign-in, not re-expose the login + // page the base route still shows. Only a location that lands where the base + // page already is reuses it (with state updated to this navigation). + whenRedirected(location, extra, new Funcs.VoidFunc1() { + @Override + public void call(String loc) { + Route r = routeFor(loc, extra); + if (samePath(resolvedLocation, baseLocation != null ? baseLocation : initialLocation)) { + return; + } + if (r != null) { + Navigator.push(appContext, r); + } + } + }); + } + + public Future push(String location) { + return push(location, null); + } + + /** Pushes {@code location} above the current page. */ + public Future push(String location, final Object extra) { + final dart.async.Completer result = new dart.async.Completer(); + whenRedirected(location, extra, new Funcs.VoidFunc1() { + @Override + public void call(String loc) { + Route r = routeFor(loc, extra); + if (r == null) { + result.complete(null); + return; + } + Navigator.push(appContext, r).then(new Funcs.VoidFunc1() { + @Override + public void call(Object v) { + result.complete(v); + } + }, new Funcs.VoidFunc1() { + @Override + public void call(Object e) { + result.completeError(e); + } + }); + } + }); + return result.future(); + } + + /** + * Runs the redirect chain from {@code location}, waiting for any redirect that + * answers a Future (an auth check reading its session), then hands the final + * location to {@code then}. routeFor applies the same redirects again, now all + * already answered; without the wait an incomplete future read as "no redirect" + * and a guarded page opened. + */ + private void whenRedirected(final String location, final Object extra, final Funcs.VoidFunc1 then) { + resolveRedirects(location == null ? "/" : location, extra, 0, then); + } + + private void resolveRedirects(final String loc, final Object extra, final int depth, + final Funcs.VoidFunc1 then) { + if (depth > redirectLimit) { + then.call(loc); + return; + } + Match m = match(loc); + GoRouterState s = stateOf(loc, m, extra, null); + List> fs = + new java.util.ArrayList>(); + fs.add(redirect); + if (m != null) { + for (GoRoute r : m.chain) { + fs.add(r.redirectFunc()); + } + } + nextRedirect(fs, 0, s, loc, extra, depth, then); + } + + @SuppressWarnings("unchecked") + private void nextRedirect(final List> fs, final int i, + final GoRouterState s, final String loc, final Object extra, final int depth, + final Funcs.VoidFunc1 then) { + if (i >= fs.size()) { + then.call(loc); + return; + } + Funcs.Func2 f = fs.get(i); + Object to = f == null ? null : f.call(appContext, s); + Funcs.VoidFunc1 answered = new Funcs.VoidFunc1() { + @Override + public void call(Object v) { + if (v instanceof String && !samePath((String) v, loc)) { + resolveRedirects((String) v, extra, depth + 1, then); + } else { + nextRedirect(fs, i + 1, s, loc, extra, depth, then); + } + } + }; + if (to instanceof Future) { + ((Future) to).then(answered, new Funcs.VoidFunc1() { + @Override + public void call(Object e) { + redirectFailure = e == null ? new dart.core.DartException("redirect failed") : e; + then.call(loc); + } + }); + } else { + answered.call(to); + } + } + + public void pushReplacement(String location, Object extra) { + if (Navigator.stackSize() > 0) { + Navigator.pop(null); + } + push(location, extra); + } + + public void replace(String location, Object extra) { + pushReplacement(location, extra); + } + + public void pop() { + Navigator.pop(null, null); + } + + public void pop(Object result) { + Navigator.pop(null, result); + } + + public boolean canPop() { + // Upstream delegates to its navigator, so an open dialog counts: pop() below + // dismisses it, and `if (router.canPop()) router.pop()` must reach it. + return Navigator.canPopAnything(); + } + + public void goNamed(String name, Object pathParameters, Object queryParameters, Object extra) { + go(namedLocation(name, pathParameters, queryParameters), extra); + } + + public Future pushNamed(String name, Object pathParameters, Object queryParameters, Object extra) { + return push(namedLocation(name, pathParameters, queryParameters), extra); + } + + /** Builds the location of the route called {@code name}, filling its parameters. */ + public String namedLocation(String name, Object pathParameters, Object queryParameters) { + String pattern = findNamed(routes, "", name); + if (pattern == null) { + throw new dart.core.ArgumentError("no route named " + name); + } + StringBuilder sb = new StringBuilder(); + for (String seg : segments(pattern)) { + sb.append('/'); + if (seg.startsWith(":")) { + Object v = pathParameters instanceof java.util.Map + ? ((java.util.Map) pathParameters).get(seg.substring(1)) : null; + if (v == null) { + throw new dart.core.ArgumentError("missing param \"" + seg.substring(1) + + "\" for " + pattern); + } + sb.append(encode(String.valueOf(v))); + } else { + sb.append(seg); + } + } + if (sb.length() == 0) { + sb.append('/'); + } + if (queryParameters instanceof java.util.Map && !((java.util.Map) queryParameters).isEmpty()) { + char sep = '?'; + for (java.util.Map.Entry e : ((java.util.Map) queryParameters).entrySet()) { + sb.append(sep).append(encode(String.valueOf(e.getKey()))) + .append('=').append(encode(String.valueOf(e.getValue()))); + sep = '&'; + } + } + return sb.toString(); + } + + // --- matching ----------------------------------------------------------- + + /** The route for {@code location}, after redirects; an error page when nothing matches. */ + Route routeFor(String location, Object extra) { + String loc = location == null ? "/" : location; + Match m = null; + for (int i = 0; i <= redirectLimit; i++) { + m = match(loc); + GoRouterState s = stateOf(loc, m, extra, null); + String to = redirectOf(redirect, s); + if (to == null && m != null && redirectFailure == null) { + for (GoRoute r : m.chain) { + to = redirectOf(r.redirectFunc(), s); + if (to != null || redirectFailure != null) { + break; + } + } + } + if (to == null || samePath(to, loc) || redirectFailure != null) { + break; + } + if (i == redirectLimit) { + m = null; + break; + } + loc = to; + } + Object failure = redirectFailure; + redirectFailure = null; + if (failure != null) { + m = null; + } + resolvedLocation = loc; + final Match match = m; + final GoRouterState s = stateOf(loc, match, extra, failure != null ? failure + : match == null ? new dart.core.DartException("no routes for location: " + loc) : null); + state = s; + MaterialPageRoute route = new MaterialPageRoute(); + RouteSettings settings = new RouteSettings(); + settings.name(loc); + settings.arguments(extra); + route.settings(settings); + route.builder(new Funcs.Func1() { + @Override + public Widget call(BuildContext ctx) { + return buildPage(ctx, match, s); + } + }); + return route; + } + + private String redirectOf(Funcs.Func2 f, GoRouterState s) { + if (f == null) { + return null; + } + Object to = f.call(appContext, s); + if (to instanceof Future) { + // FutureOr: only an already-completed answer can steer a navigation + // that is happening now; a pending one is noted for routeGenerator. + if (!((Future) to).completedNow()) { + sawPendingRedirect = true; + } + Throwable failed = ((Future) to).completedError(); + if (failed != null) { + // An already-failed redirect (Future.error) is a navigation failure, + // exactly as one that fails later: routeFor shows the error page rather + // than building the guarded route as if no redirect existed. + redirectFailure = failed; + return null; + } + to = ((Future) to).completedValue(); + } + return to instanceof String ? (String) to : null; + } + + Widget buildPage(BuildContext ctx, Match m, GoRouterState s) { + if (m == null) { + if (errorBuilder != null) { + return errorBuilder.call(ctx, s); + } + return new com.codename1.flutter.widgets.Text(String.valueOf(s.error())); + } + Widget page; + GoRoute leaf = m.chain.get(m.chain.size() - 1); + if (leaf.builder() != null) { + page = leaf.builder().call(ctx, s); + } else if (leaf.pageBuilder() != null) { + Object p = leaf.pageBuilder().call(ctx, s); + page = p instanceof GoPage ? ((GoPage) p).child() : p instanceof Widget ? (Widget) p : null; + } else { + page = null; + } + for (int i = m.shells.size() - 1; i >= 0; i--) { + ShellRoute sh = m.shells.get(i); + if (sh.builder() != null) { + page = sh.builder().call(ctx, s, page); + } + } + return page; + } + + static final class Match { + final List chain = new ArrayList(); + final List shells = new ArrayList(); + final DartMap params = new DartMap(); + String fullPath; + } + + Match match(String location) { + DartUri uri = DartUri.parse(location); + List target = segments(uri.path()); + Match m = new Match(); + return matchIn(routes, new ArrayList(), target, m) ? m : null; + } + + private static boolean matchIn(List list, List prefix, List target, Match m) { + for (RouteBase rb : list) { + if (rb instanceof ShellRoute) { + m.shells.add((ShellRoute) rb); + if (matchIn(rb.routes(), prefix, target, m)) { + return true; + } + m.shells.remove(m.shells.size() - 1); + continue; + } + if (!(rb instanceof GoRoute)) { + continue; + } + GoRoute r = (GoRoute) rb; + String p = r.path() == null ? "" : r.path(); + List full = p.startsWith("/") ? segments(p) : join(prefix, segments(p)); + DartMap captured = new DartMap(); + if (!prefixMatches(full, target, captured)) { + continue; + } + m.chain.add(r); + if (full.size() == target.size()) { + m.params.putAll(captured); + m.fullPath = "/" + joinPath(full); + return true; + } + if (matchIn(r.routes(), full, target, m)) { + m.params.putAll(captured); + return true; + } + m.chain.remove(m.chain.size() - 1); + } + return false; + } + + private static boolean prefixMatches(List pattern, List target, DartMap captured) { + if (pattern.size() > target.size()) { + return false; + } + for (int i = 0; i < pattern.size(); i++) { + String p = pattern.get(i); + String t = target.get(i); + if (p.startsWith(":")) { + captured.put(p.substring(1), DartUri.decodeComponent(t)); + } else if (!p.equals(t)) { // go_router literals are case-sensitive by default + return false; + } + } + return true; + } + + private static String findNamed(List list, String prefix, String name) { + for (RouteBase rb : list) { + String here = prefix; + if (rb instanceof GoRoute) { + GoRoute r = (GoRoute) rb; + String p = r.path() == null ? "" : r.path(); + here = p.startsWith("/") ? p : (prefix.endsWith("/") ? prefix : prefix + "/") + p; + if (name.equals(r.name())) { + return here; + } + } + String found = findNamed(rb.routes(), here, name); + if (found != null) { + return found; + } + } + return null; + } + + private GoRouterState stateOf(String loc, Match m, Object extra, Object error) { + DartUri uri = DartUri.parse(loc); + DartMap params = m == null ? new DartMap() : m.params; + String fullPath = m == null ? null : m.fullPath; + String name = m == null || m.chain.isEmpty() ? null : m.chain.get(m.chain.size() - 1).name(); + return new GoRouterState(uri, uri.path(), fullPath, name, params, extra, error); + } + + /** Percent-encodes everything but RFC 3986's unreserved characters, as UTF-8. */ + static String encode(String s) { + StringBuilder sb = new StringBuilder(); + byte[] bytes; + try { + bytes = s.getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + bytes = s.getBytes(); + } + for (byte b : bytes) { + int c = b & 0xff; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || c == '-' || c == '_' || c == '.' || c == '~') { + sb.append((char) c); + } else { + sb.append('%').append("0123456789ABCDEF".charAt(c >> 4)) + .append("0123456789ABCDEF".charAt(c & 15)); + } + } + return sb.toString(); + } + + static List segments(String path) { + List out = new ArrayList(); + if (path == null) { + return out; + } + int start = 0; + int n = path.length(); + for (int i = 0; i <= n; i++) { + if (i == n || path.charAt(i) == '/') { + if (i > start) { + out.add(path.substring(start, i)); + } + start = i + 1; + } + } + return out; + } + + private static List join(List a, List b) { + List out = new ArrayList(a); + out.addAll(b); + return out; + } + + private static String joinPath(List segs) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < segs.size(); i++) { + if (i > 0) { + sb.append('/'); + } + sb.append(segs.get(i)); + } + return sb.toString(); + } + + private static boolean samePath(String a, String b) { + return segments(DartUri.parse(a).path()).equals(segments(DartUri.parse(b).path())) + && DartUri.parse(a).query().equals(DartUri.parse(b).query()); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouterHelper.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouterHelper.java new file mode 100644 index 00000000000..2fda2b5a606 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouterHelper.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.gorouter; + +import com.codename1.flutter.BuildContext; + +import dart.async.Future; + +/** + * go_router's {@code GoRouterHelper} extension on {@code BuildContext}: + * {@code context.go('/pets/1')}, {@code context.push(...)}, {@code context.pop()}. + */ +public final class GoRouterHelper { + private GoRouterHelper() { + } + + private static GoRouter router(BuildContext context) { + GoRouter r = GoRouter.of(context); + if (r == null) { + throw new dart.core.StateError("No GoRouter found in context"); + } + return r; + } + + public static void go(BuildContext context, String location, Object extra) { + router(context).go(location, extra); + } + + public static Future push(BuildContext context, String location, Object extra) { + return router(context).push(location, extra); + } + + public static void pushReplacement(BuildContext context, String location, Object extra) { + router(context).pushReplacement(location, extra); + } + + public static void replace(BuildContext context, String location, Object extra) { + router(context).replace(location, extra); + } + + public static void pop(BuildContext context, Object result) { + router(context).pop(result); + } + + public static boolean canPop(BuildContext context) { + return router(context).canPop(); + } + + public static void goNamed(BuildContext context, String name, Object pathParameters, + Object queryParameters, Object extra) { + router(context).goNamed(name, pathParameters, queryParameters, extra); + } + + public static Future pushNamed(BuildContext context, String name, Object pathParameters, + Object queryParameters, Object extra) { + return router(context).pushNamed(name, pathParameters, queryParameters, extra); + } + + public static String namedLocation(BuildContext context, String name, Object pathParameters, + Object queryParameters) { + return router(context).namedLocation(name, pathParameters, queryParameters); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouterState.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouterState.java new file mode 100644 index 00000000000..f083d9bff16 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/GoRouterState.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.gorouter; + +import dart.core.DartMap; +import dart.core.DartUri; + +/** + * go_router's {@code GoRouterState}: the location being shown, the parameters its + * pattern captured and the {@code extra} object it was navigated to with. + */ +public class GoRouterState { + private final DartUri uri; + private final String matchedLocation; + private final String fullPath; + private final String name; + private final DartMap pathParameters; + private final Object extra; + private final Object error; + + GoRouterState(DartUri uri, String matchedLocation, String fullPath, String name, + DartMap pathParameters, Object extra, Object error) { + this.uri = uri; + this.matchedLocation = matchedLocation; + this.fullPath = fullPath; + this.name = name; + this.pathParameters = pathParameters; + this.extra = extra; + this.error = error; + } + + public DartUri uri() { + return uri; + } + + public String matchedLocation() { + return matchedLocation; + } + + public String fullPath() { + return fullPath; + } + + public String name() { + return name; + } + + public String path() { + return fullPath; + } + + public DartMap pathParameters() { + return pathParameters; + } + + public DartMap queryParameters() { + return uri.queryParameters(); + } + + public Object extra() { + return extra; + } + + public Object error() { + return error; + } + + /** The router of the app -- {@code GoRouterState.of(context)} answers its current state. */ + public static GoRouterState of(com.codename1.flutter.BuildContext context) { + GoRouter r = GoRouter.of(context); + return r == null ? null : r.state(); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/RouteBase.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/RouteBase.java new file mode 100644 index 00000000000..fa79af4d295 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/RouteBase.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.gorouter; + +import dart.core.DartList; + +/** + * go_router's {@code RouteBase}: a node of the route tree, holding the routes nested + * below it. + */ +public abstract class RouteBase { + private final DartList routes = new DartList(); + + /** The routes nested below this one. */ + public void routes(Object v) { + routes.clear(); + if (v instanceof Iterable) { + for (Object o : (Iterable) v) { + if (o instanceof RouteBase) { + routes.add((RouteBase) o); + } + } + } + } + + public DartList routes() { + return routes; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/ShellRoute.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/ShellRoute.java new file mode 100644 index 00000000000..3750082a77e --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/gorouter/ShellRoute.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.gorouter; + +import com.codename1.flutter.BuildContext; +import com.codename1.flutter.Widget; + +import dart.runtime.Funcs; + +/** + * go_router's {@code ShellRoute}: wraps the page of whichever nested route matched in + * a shared frame -- typically a scaffold with a bottom navigation bar. + */ +public class ShellRoute extends RouteBase { + private Funcs.Func3 builder; + + public ShellRoute() { + } + + public void builder(Funcs.Func3 v) { + this.builder = v; + } + + /** Accepted for source compatibility; the shell has no navigator of its own. */ + public void navigatorKey(Object v) { + } + + Funcs.Func3 builder() { + return builder; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/CameraDevice.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/CameraDevice.java new file mode 100644 index 00000000000..2a9d5fbb505 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/CameraDevice.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.imagepicker; + +/** image_picker's CameraDevice; the platform camera UI chooses the lens. */ +public enum CameraDevice { + rear, front +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/ImagePicker.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/ImagePicker.java new file mode 100644 index 00000000000..030f8f14881 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/ImagePicker.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.imagepicker; + +import com.codename1.capture.Capture; +import com.codename1.flutter.plugins.crossfile.XFile; +import com.codename1.ui.Display; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; + +import dart.async.Completer; +import dart.async.Future; +import dart.core.DartList; + +/** + * image_picker's {@code ImagePicker}: the camera through Capture, the gallery through + * {@code Display.openGallery}. A cancelled pick, or one with no display to pick on, + * answers null (an empty list for the multi-pickers), as image_picker does. The size + * and quality options are accepted; the picked file is returned as captured. + */ +public class ImagePicker { + + public ImagePicker() { + } + + public Future pickImage(ImageSource source, Double maxWidth, Double maxHeight, + Long imageQuality, Object preferredCameraDevice, Boolean requestFullMetadata) { + return pick(source, Display.GALLERY_IMAGE); + } + + public Future pickVideo(ImageSource source, Object preferredCameraDevice, Object maxDuration) { + return pick(source, Display.GALLERY_VIDEO); + } + + public Future pickMedia(Double maxWidth, Double maxHeight, Long imageQuality, + Boolean requestFullMetadata) { + return pick(ImageSource.gallery, Display.GALLERY_ALL); + } + + public Future> pickMultiImage(Double maxWidth, Double maxHeight, + Long imageQuality, Long limit, Boolean requestFullMetadata) { + return pickMany(Display.GALLERY_IMAGE_MULTI, Display.GALLERY_IMAGE, limit); + } + + public Future> pickMultipleMedia(Double maxWidth, Double maxHeight, + Long imageQuality, Long limit, Boolean requestFullMetadata) { + return pickMany(Display.GALLERY_ALL_MULTI, Display.GALLERY_ALL, limit); + } + + private static Future> asList(Future one) { + final Completer> done = new Completer>(); + one.then(new dart.runtime.Funcs.VoidFunc1() { + @Override + public void call(XFile f) { + DartList out = new DartList(); + if (f != null) { + out.add(f); + } + done.complete(out); + } + }, new dart.runtime.Funcs.VoidFunc1() { + @Override + public void call(Object e) { + // a failed single pick fails the list; without this the await never returned + done.completeError(e); + } + }); + return done.future(); + } + + /** + * The multi-select gallery where the platform offers it (iOS needs the + * ios.enableGalleryMultiselect build hint), else a single pick as a list. + */ + /// `limit` is the plugin's maximum selection. Codename One's gallery cannot be told + /// a maximum, so the picker may let the user choose more; the result is capped to + /// the first `limit` files, so the app never receives more than it allowed. + private static Future> pickMany(int multiType, int singleType, Long limit) { + if (limit != null && limit.longValue() < 2) { + return Future.error(new dart.core.ArgumentError("cannot be lower than 2: limit")); + } + final long max = limit == null ? Long.MAX_VALUE : limit.longValue(); + if (!Display.isInitialized()) { + return Future.value(new DartList()); + } + if (!Display.getInstance().isGalleryTypeSupported(multiType)) { + return asList(pick(ImageSource.gallery, singleType)); + } + final Completer> done = new Completer>(); + Display.getInstance().openGallery(new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + DartList out = new DartList(); + Object src = evt == null ? null : evt.getSource(); + if (src instanceof String[]) { + for (String path : (String[]) src) { + if (out.size() >= max) { + break; + } + out.add(new XFile(path)); + } + } else if (src instanceof String) { + out.add(new XFile((String) src)); + } + done.complete(out); + } + }, multiType); + return done.future(); + } + + private static Future pick(ImageSource source, int galleryType) { + if (!Display.isInitialized()) { + return Future.value(null); + } + final Completer done = new Completer(); + ActionListener onPicked = new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + Object src = evt == null ? null : evt.getSource(); + done.complete(src instanceof String ? new XFile((String) src) : null); + } + }; + if (source == ImageSource.camera) { + if (galleryType == Display.GALLERY_VIDEO) { + Capture.captureVideo(onPicked); + } else { + Capture.capturePhoto(onPicked); + } + } else { + Display.getInstance().openGallery(onPicked, galleryType); + } + return done.future(); + } + + /** Android's recovery of a pick interrupted by the activity being destroyed; nothing here. */ + public Future retrieveLostData() { + return Future.value(null); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/ImageSource.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/ImageSource.java new file mode 100644 index 00000000000..c127f3a5243 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/imagepicker/ImageSource.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.imagepicker; + +/** image_picker's {@code ImageSource}. */ +public enum ImageSource { + camera, gallery +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/json/JsonAnnotation.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/json/JsonAnnotation.java new file mode 100644 index 00000000000..7eebf61098b --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/json/JsonAnnotation.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.json; + +import java.util.Map; + +import dart.core.ArgumentError; +import dart.runtime.DartRuntime; + +/// The helpers json_serializable's generated `*.g.dart` code calls from +/// json_annotation. The annotations themselves only drive code generation, which +/// has already run by the time an app is imported. +public final class JsonAnnotation { + private JsonAnnotation() { + } + + /// `$enumDecode(map, source, unknownValue: ...)`: the enum value whose JSON + /// form is `source`. + /// Typed by its result, not by the map: a generated `const` enum map is often + /// inferred no further than `Map` here, and its key type would + /// then contradict the enum the caller assigns the result to. + @SuppressWarnings("unchecked") + public static K $enumDecode(Map enumValues, Object source, Object unknownValue) { + if (source == null) { + throw new ArgumentError("A value must be provided. Supported values: " + supported(enumValues)); + } + for (Map.Entry e : enumValues.entrySet()) { + if (DartRuntime.eq(e.getValue(), source)) { + return (K) e.getKey(); + } + } + if (unknownValue != null) { + return (K) unknownValue; + } + throw new ArgumentError("`" + DartRuntime.str(source) + "` is not one of the supported values: " + + supported(enumValues)); + } + + private static String supported(Map enumValues) { + StringBuilder sb = new StringBuilder(); + for (Object v : enumValues.values()) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(DartRuntime.str(v)); + } + return sb.toString(); + } + + public static K $enumDecodeNullable(Map enumValues, Object source, Object unknownValue) { + return source == null ? null : JsonAnnotation.$enumDecode(enumValues, source, unknownValue); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/AndroidScheduleMode.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/AndroidScheduleMode.java new file mode 100644 index 00000000000..a28a488731b --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/AndroidScheduleMode.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code AndroidScheduleMode}. */ +public enum AndroidScheduleMode { + alarmClock, exact, exactAllowWhileIdle, inexact, inexactAllowWhileIdle +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/DateTimeComponents.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/DateTimeComponents.java new file mode 100644 index 00000000000..2563e78033f --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/DateTimeComponents.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' DateTimeComponents (repeat matching); accepted, not honoured. */ +public enum DateTimeComponents { + time, dayOfWeekAndTime, dayOfMonthAndTime, dateAndTime +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/FlutterLocalNotificationsPlugin.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/FlutterLocalNotificationsPlugin.java new file mode 100644 index 00000000000..e12688a8350 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/FlutterLocalNotificationsPlugin.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +import java.util.HashMap; +import java.util.Map; + +import com.codename1.notifications.LocalNotification; +import com.codename1.ui.Display; + +import dart.async.Future; +import dart.core.DartList; +import dart.core.DateTime; +import dart.runtime.Funcs; + +/** + * flutter_local_notifications over Codename One's LocalNotification. A tap reaches the + * {@code onDidReceiveNotificationResponse} callback through {@link #notificationTapped}, + * which the imported app's entry point calls from its LocalNotificationCallback. + * Notifications are identified to the platform as {@code "cn1flutter-" + id}. + */ +public class FlutterLocalNotificationsPlugin { + private static final String PREFIX = "cn1flutter-"; + private static final Map PAYLOADS = new HashMap(); + private static Funcs.VoidFunc1 onTap; + private static NotificationResponse launchResponse; + private static int shown; + + public FlutterLocalNotificationsPlugin() { + } + + public Future initialize(NotificationOptions settings, + Funcs.VoidFunc1 onDidReceiveNotificationResponse, + Object onDidReceiveBackgroundNotificationResponse) { + onTap = onDidReceiveNotificationResponse; + return Future.value(Boolean.TRUE); + } + + /** Called by the app entry point when the user opens one of our notifications. */ + public static void notificationTapped(String notificationId) { + if (notificationId == null || !notificationId.startsWith(PREFIX)) { + return; + } + Long id = null; + try { + id = Long.valueOf(Long.parseLong(notificationId.substring(PREFIX.length()))); + } catch (NumberFormatException ignore) { + // not one of ours after all + } + NotificationResponse response = new NotificationResponse(id, payload(notificationId)); + Funcs.VoidFunc1 cb = onTap; + if (cb != null) { + cb.call(response); + } else { + // A cold launch from a tap arrives before the app has run initialize(). As in + // Flutter, that tap is reported by getNotificationAppLaunchDetails, not the + // callback, so it is held for the app to ask. + launchResponse = response; + } + } + + public Future show(long id, String title, String body, NotificationOptions details, String payload) { + schedule(id, title, body, details, payload, System.currentTimeMillis() + 10, + LocalNotification.REPEAT_NONE); + return Future.value(null); + } + + public Future periodicallyShow(long id, String title, String body, RepeatInterval repeat, + NotificationOptions details, Object androidScheduleMode, String payload) { + int r; + long interval; + switch (repeat == null ? RepeatInterval.daily : repeat) { + case everyMinute: + r = LocalNotification.REPEAT_MINUTE; + interval = 60000L; + break; + case hourly: + r = LocalNotification.REPEAT_HOUR; + interval = 3600000L; + break; + case weekly: + r = LocalNotification.REPEAT_WEEK; + interval = 7L * 86400000L; + break; + default: + r = LocalNotification.REPEAT_DAY; + interval = 86400000L; + break; + } + // The plugin's contract: the first one arrives one interval after the call, not now. + schedule(id, title, body, details, payload, System.currentTimeMillis() + interval, r); + return Future.value(null); + } + + /** {@code zonedSchedule}: the time is taken as the instant it names. */ + public Future zonedSchedule(long id, String title, String body, Object scheduledDate, + NotificationOptions details, Object androidScheduleMode, String payload, + Object matchDateTimeComponents, Object uiLocalNotificationDateInterpretation) { + long at = scheduledDate instanceof DateTime + ? ((DateTime) scheduledDate).millisecondsSinceEpoch() : System.currentTimeMillis() + 10; + // matchDateTimeComponents makes the schedule recur: time is every day at that + // time, dayOfWeekAndTime every week. LocalNotification has no monthly or yearly + // interval, so dayOfMonthAndTime and dateAndTime fire once, at the first date. + int repeat = LocalNotification.REPEAT_NONE; + if (matchDateTimeComponents == DateTimeComponents.time) { + repeat = LocalNotification.REPEAT_DAY; + } else if (matchDateTimeComponents == DateTimeComponents.dayOfWeekAndTime) { + repeat = LocalNotification.REPEAT_WEEK; + } + schedule(id, title, body, details, payload, at, repeat); + return Future.value(null); + } + + private static void schedule(long id, String title, String body, NotificationOptions details, + String payload, long at, int repeat) { + String key = PREFIX + id; + putPayload(key, payload); + putMeta(key, title, body, at, repeat); + shown++; + if (!Display.isInitialized()) { + return; + } + LocalNotification n = new LocalNotification(); + n.setId(key); + n.setAlertTitle(title == null ? "" : title); + n.setAlertBody(body == null ? "" : body); + Object sound = details == null ? null : details.find("sound"); + if (sound instanceof String) { + n.setAlertSound((String) sound); + } + Object badge = details == null ? null : details.find("badgeNumber"); + if (badge instanceof Number) { + n.setBadgeNumber(((Number) badge).intValue()); + } + n.setForeground(true); + Display.getInstance().scheduleLocalNotification(n, at, repeat); + } + + public Future cancel(long id, String tag) { + String key = PREFIX + id; + removePayload(key); + if (Display.isInitialized()) { + Display.getInstance().cancelLocalNotification(key); + } + return Future.value(null); + } + + public Future cancelAll() { + for (String key : payloadKeys()) { + if (Display.isInitialized()) { + Display.getInstance().cancelLocalNotification(key); + } + removePayload(key); + } + return Future.value(null); + } + + // Payloads are persisted: a scheduled notification usually fires after the process + // that scheduled it is gone, and the tap that restarts the app still needs the + // payload to route. The map is this process's cache; Preferences outlives it. + private static final String PAYLOAD_PREF = "cn1.flutter.notifications.payload."; + private static final String KEYS_PREF = "cn1.flutter.notifications.keys"; + + private static void putPayload(String key, String payload) { + PAYLOADS.put(key, payload); + if (Display.isInitialized()) { + java.util.Set keys = payloadKeys(); + keys.add(key); + com.codename1.io.Preferences.set(KEYS_PREF, join(keys)); + if (payload == null) { + com.codename1.io.Preferences.delete(PAYLOAD_PREF + key); + } else { + com.codename1.io.Preferences.set(PAYLOAD_PREF + key, payload); + } + } + } + + private static String payload(String key) { + if (PAYLOADS.containsKey(key)) { + return PAYLOADS.get(key); + } + return Display.isInitialized() ? com.codename1.io.Preferences.get(PAYLOAD_PREF + key, (String) null) : null; + } + + private static void removePayload(String key) { + PAYLOADS.remove(key); + removeMeta(key); + if (Display.isInitialized()) { + java.util.Set keys = payloadKeys(); + keys.remove(key); + com.codename1.io.Preferences.set(KEYS_PREF, join(keys)); + com.codename1.io.Preferences.delete(PAYLOAD_PREF + key); + } + } + + private static java.util.Set payloadKeys() { + java.util.Set keys = new java.util.LinkedHashSet(PAYLOADS.keySet()); + if (Display.isInitialized()) { + String stored = com.codename1.io.Preferences.get(KEYS_PREF, ""); + for (String k : com.codename1.util.StringUtil.tokenize(stored, ',')) { + if (k.length() > 0) { + keys.add(k); + } + } + } + return keys; + } + + private static String join(java.util.Set keys) { + StringBuilder sb = new StringBuilder(); + for (String k : keys) { + sb.append(sb.length() == 0 ? "" : ",").append(k); + } + return sb.toString(); + } + + /** How many notifications this process has shown or scheduled. */ + public static int shownCount() { + return shown; + } + + public Future getNotificationAppLaunchDetails() { + NotificationResponse r = launchResponse; + return Future.value(new NotificationAppLaunchDetails(r != null, r)); + } + + /// Every scheduled notification that has not been cancelled and has not yet + /// fired for the last time: a repeating one stays pending, a one-shot one until + /// its time passes. Read from what schedule() persisted, so it survives a restart. + public Future> pendingNotificationRequests() { + DartList out = new DartList(); + long now = System.currentTimeMillis(); + for (String key : payloadKeys()) { + String[] m = meta(key); + if (m == null) { + continue; + } + long at = parseLong(m[2]); + int repeat = (int) parseLong(m[3]); + if (repeat == LocalNotification.REPEAT_NONE && at <= now) { + continue; + } + out.add(new PendingNotificationRequest(parseLong(key.substring(PREFIX.length())), + m[0], m[1], payload(key))); + } + return Future.value(out); + } + + private static long parseLong(String s) { + try { + return s == null ? 0 : Long.parseLong(s); + } catch (NumberFormatException e) { + return 0; + } + } + + // Title, body, time and repeat of each scheduled notification, beside its payload. + private static final Map META = new HashMap(); + private static final String META_PREF = "cn1.flutter.notifications.meta."; + + private static void putMeta(String key, String title, String body, long at, int repeat) { + String[] m = {title, body, String.valueOf(at), String.valueOf(repeat)}; + META.put(key, m); + if (Display.isInitialized()) { + com.codename1.io.Preferences.set(META_PREF + key + ".title", title == null ? "" : title); + com.codename1.io.Preferences.set(META_PREF + key + ".body", body == null ? "" : body); + com.codename1.io.Preferences.set(META_PREF + key + ".at", at); + com.codename1.io.Preferences.set(META_PREF + key + ".repeat", repeat); + } + } + + private static String[] meta(String key) { + String[] m = META.get(key); + if (m != null || !Display.isInitialized()) { + return m; + } + long at = com.codename1.io.Preferences.get(META_PREF + key + ".at", -1L); + if (at < 0) { + return null; + } + return new String[] {com.codename1.io.Preferences.get(META_PREF + key + ".title", ""), + com.codename1.io.Preferences.get(META_PREF + key + ".body", ""), String.valueOf(at), + String.valueOf(com.codename1.io.Preferences.get(META_PREF + key + ".repeat", 0))}; + } + + private static void removeMeta(String key) { + META.remove(key); + if (Display.isInitialized()) { + com.codename1.io.Preferences.delete(META_PREF + key + ".title"); + com.codename1.io.Preferences.delete(META_PREF + key + ".body"); + com.codename1.io.Preferences.delete(META_PREF + key + ".at"); + com.codename1.io.Preferences.delete(META_PREF + key + ".repeat"); + } + } + + /** + * The platform-specific plugin object ({@code AndroidFlutterLocalNotificationsPlugin} + * and so on). There is none here; callers use {@code ?.}, which skips the call. + */ + public T resolvePlatformSpecificImplementation(Class witness) { + return null; + } + + /** + * The platform-specific permission requests. Reached only through the stub's + * {@code Android/IOSFlutterLocalNotificationsPlugin} types, which map to this class; + * {@link #resolvePlatformSpecificImplementation} returns null, so they normally never + * run. Codename One asks for notification permission when it first shows one. + */ + public Future requestNotificationsPermission() { + return Future.value(Boolean.TRUE); + } + + public Future requestPermissions(Boolean alert, Boolean badge, Boolean sound, Boolean critical, + Boolean provisional) { + return Future.value(Boolean.TRUE); + } + + public Future requestExactAlarmsPermission() { + return Future.value(Boolean.TRUE); + } + + public Future createNotificationChannel(Object channel) { + return Future.value(null); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/Importance.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/Importance.java new file mode 100644 index 00000000000..56ead175afd --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/Importance.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code Importance}. */ +public enum Importance { + unspecified, none, min, low, defaultImportance, high, max +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationAppLaunchDetails.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationAppLaunchDetails.java new file mode 100644 index 00000000000..626e2ce6416 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationAppLaunchDetails.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code NotificationAppLaunchDetails}: whether a tap launched the app. */ +public class NotificationAppLaunchDetails { + private final boolean didNotificationLaunchApp; + private final NotificationResponse notificationResponse; + + public NotificationAppLaunchDetails(boolean didNotificationLaunchApp, NotificationResponse notificationResponse) { + this.didNotificationLaunchApp = didNotificationLaunchApp; + this.notificationResponse = notificationResponse; + } + + public boolean didNotificationLaunchApp() { + return didNotificationLaunchApp; + } + + public NotificationResponse notificationResponse() { + return notificationResponse; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationOptions.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationOptions.java new file mode 100644 index 00000000000..c3f41d1541c --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationOptions.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +import java.util.HashMap; +import java.util.Map; + +/** + * Every settings and details class of flutter_local_notifications -- + * {@code InitializationSettings}, {@code AndroidNotificationDetails}, + * {@code DarwinNotificationDetails} and the rest -- maps here. Codename One's local + * notification has one title, body and sound, so the options are held by name and only + * {@code sound} and {@code badgeNumber} are read when a notification is shown; the + * positional values (an Android icon, channel id and name) are kept the same way. + */ +public class NotificationOptions { + private final Map values = new HashMap(); + + public NotificationOptions() { + } + + public NotificationOptions(Object first) { + put("arg0", first); + } + + public NotificationOptions(Object first, Object second) { + put("arg0", first); + put("arg1", second); + } + + public NotificationOptions(Object first, Object second, Object third) { + put("arg0", first); + put("arg1", second); + put("arg2", third); + } + + private void put(String name, Object v) { + values.put(name, v); + } + + Object get(String name) { + return values.get(name); + } + + /** A value held directly or by one of the platform options this one wraps. */ + Object find(String name) { + Object v = values.get(name); + if (v != null) { + return v; + } + for (Object o : values.values()) { + if (o instanceof NotificationOptions) { + Object inner = ((NotificationOptions) o).values.get(name); + if (inner != null) { + return inner; + } + } + } + return null; + } + + public void android(Object v) { + put("android", v); + } + + public void iOS(Object v) { + put("iOS", v); + } + + public void macOS(Object v) { + put("macOS", v); + } + + public void linux(Object v) { + put("linux", v); + } + + public void windows(Object v) { + put("windows", v); + } + + public void importance(Object v) { + put("importance", v); + } + + public void priority(Object v) { + put("priority", v); + } + + public void channelDescription(Object v) { + put("channelDescription", v); + } + + public void icon(Object v) { + put("icon", v); + } + + public void playSound(Object v) { + put("playSound", v); + } + + public void enableVibration(Object v) { + put("enableVibration", v); + } + + public void sound(Object v) { + put("sound", v); + } + + public void ticker(Object v) { + put("ticker", v); + } + + public void styleInformation(Object v) { + put("styleInformation", v); + } + + public void color(Object v) { + put("color", v); + } + + public void largeIcon(Object v) { + put("largeIcon", v); + } + + public void showWhen(Object v) { + put("showWhen", v); + } + + public void autoCancel(Object v) { + put("autoCancel", v); + } + + public void ongoing(Object v) { + put("ongoing", v); + } + + public void category(Object v) { + put("category", v); + } + + public void groupKey(Object v) { + put("groupKey", v); + } + + public void channelShowBadge(Object v) { + put("channelShowBadge", v); + } + + public void visibility(Object v) { + put("visibility", v); + } + + public void fullScreenIntent(Object v) { + put("fullScreenIntent", v); + } + + public void presentAlert(Object v) { + put("presentAlert", v); + } + + public void presentBadge(Object v) { + put("presentBadge", v); + } + + public void presentSound(Object v) { + put("presentSound", v); + } + + public void presentBanner(Object v) { + put("presentBanner", v); + } + + public void presentList(Object v) { + put("presentList", v); + } + + public void badgeNumber(Object v) { + put("badgeNumber", v); + } + + public void subtitle(Object v) { + put("subtitle", v); + } + + public void threadIdentifier(Object v) { + put("threadIdentifier", v); + } + + public void attachments(Object v) { + put("attachments", v); + } + + public void requestAlertPermission(Object v) { + put("requestAlertPermission", v); + } + + public void requestBadgePermission(Object v) { + put("requestBadgePermission", v); + } + + public void requestSoundPermission(Object v) { + put("requestSoundPermission", v); + } + + public void defaultPresentAlert(Object v) { + put("defaultPresentAlert", v); + } + + public void defaultPresentBadge(Object v) { + put("defaultPresentBadge", v); + } + + public void defaultPresentSound(Object v) { + put("defaultPresentSound", v); + } + + public void defaultPresentBanner(Object v) { + put("defaultPresentBanner", v); + } + + public void defaultPresentList(Object v) { + put("defaultPresentList", v); + } + + public void notificationCategories(Object v) { + put("notificationCategories", v); + } + + public void enableLights(Object v) { + put("enableLights", v); + } + + public void ledColor(Object v) { + put("ledColor", v); + } + + public void timeoutAfter(Object v) { + put("timeoutAfter", v); + } + + public void onlyAlertOnce(Object v) { + put("onlyAlertOnce", v); + } + + public void actions(Object v) { + put("actions", v); + } + + public void number(Object v) { + put("number", v); + } + + public void tag(Object v) { + put("tag", v); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationResponse.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationResponse.java new file mode 100644 index 00000000000..1b1589bfebf --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationResponse.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code NotificationResponse}: the tapped notification. */ +public class NotificationResponse { + private final Long id; + private final String payload; + + public NotificationResponse(Long id, String payload) { + this.id = id; + this.payload = payload; + } + + public Long id() { + return id; + } + + public String payload() { + return payload; + } + + public String actionId() { + return null; + } + + public String input() { + return null; + } + + public NotificationResponseType notificationResponseType() { + return NotificationResponseType.selectedNotification; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationResponseType.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationResponseType.java new file mode 100644 index 00000000000..5e49bfe798a --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/NotificationResponseType.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code NotificationResponseType}. */ +public enum NotificationResponseType { + selectedNotification, selectedNotificationAction +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/PendingNotificationRequest.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/PendingNotificationRequest.java new file mode 100644 index 00000000000..bf9bef99914 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/PendingNotificationRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code PendingNotificationRequest}: a scheduled notification not yet delivered. */ +public class PendingNotificationRequest { + private final long id; + private final String title; + private final String body; + private final String payload; + + public PendingNotificationRequest(long id, String title, String body, String payload) { + this.id = id; + this.title = title; + this.body = body; + this.payload = payload; + } + + public long id() { + return id; + } + + public String title() { + return title; + } + + public String body() { + return body; + } + + public String payload() { + return payload; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/Priority.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/Priority.java new file mode 100644 index 00000000000..c687bba02bb --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/Priority.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code Priority}. */ +public enum Priority { + min, low, defaultPriority, high, max +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/RepeatInterval.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/RepeatInterval.java new file mode 100644 index 00000000000..3b385bb1b5f --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/RepeatInterval.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' {@code RepeatInterval}. */ +public enum RepeatInterval { + everyMinute, hourly, daily, weekly +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/UILocalNotificationDateInterpretation.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/UILocalNotificationDateInterpretation.java new file mode 100644 index 00000000000..3ead54b3ce1 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/notifications/UILocalNotificationDateInterpretation.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.notifications; + +/** flutter_local_notifications' UILocalNotificationDateInterpretation; accepted, not honoured. */ +public enum UILocalNotificationDateInterpretation { + absoluteTime, wallClockTime +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/packageinfo/PackageInfo.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/packageinfo/PackageInfo.java new file mode 100644 index 00000000000..47b8278949b --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/packageinfo/PackageInfo.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.packageinfo; + +import com.codename1.ui.Display; + +import dart.async.Future; + +/** + * package_info_plus's {@code PackageInfo}, from the build's own properties: + * {@code AppName}, {@code AppVersion} and {@code package_name}. The build number is + * the version's last dotted part when it has more than three, as Codename One builds + * carry a single version string. + */ +public class PackageInfo { + private static PackageInfo mock; + + private final String appName; + private final String packageName; + private final String version; + private final String buildNumber; + private final String buildSignature; + private final String installerStore; + + public PackageInfo(String appName, String packageName, String version, String buildNumber, + String buildSignature, String installerStore) { + this.appName = appName; + this.packageName = packageName; + this.version = version; + this.buildNumber = buildNumber; + this.buildSignature = buildSignature == null ? "" : buildSignature; + this.installerStore = installerStore; + } + + public static Future fromPlatform() { + if (mock != null) { + return Future.value(mock); + } + String name = prop("AppName", ""); + String pkg = prop("package_name", ""); + String full = prop("AppVersion", ""); + String version = full; + String build = ""; + int dots = 0; + for (int i = 0; i < full.length(); i++) { + if (full.charAt(i) == '.') { + dots++; + } + } + if (dots >= 3) { + int last = full.lastIndexOf('.'); + version = full.substring(0, last); + build = full.substring(last + 1); + } + String imported = importedBuildNumber(); + if (imported != null) { + build = imported; + } + return Future.value(new PackageInfo(name, pkg, version, build, "", null)); + } + + /// Written by the Flutter importer from pubspec's `version: 1.2.3+42`; the 42 goes + /// to the native build-number hints, which no device property reports back. + static final String BUILD_NUMBER_RESOURCE = "cn1-flutter-build-number.txt"; + + private static String importedBuildNumber() { + if (!Display.isInitialized()) { + return null; + } + java.io.InputStream in = Display.getInstance().getResourceAsStream(PackageInfo.class, + "/" + BUILD_NUMBER_RESOURCE); + if (in == null) { + return null; + } + try { + String s = com.codename1.io.Util.readToString(in, "UTF-8").trim(); + return s.length() == 0 ? null : s; + } catch (java.io.IOException e) { + return null; + } finally { + com.codename1.io.Util.cleanup(in); + } + } + + /** Fixes what {@link #fromPlatform} answers, for tests. */ + public static void setMockInitialValues(String appName, String packageName, String version, + String buildNumber, String buildSignature, String installerStore, Object updateTime) { + mock = new PackageInfo(appName, packageName, version, buildNumber, buildSignature, installerStore); + } + + private static String prop(String key, String def) { + return Display.isInitialized() ? Display.getInstance().getProperty(key, def) : def; + } + + public String appName() { + return appName; + } + + public String packageName() { + return packageName; + } + + public String version() { + return version; + } + + public String buildNumber() { + return buildNumber; + } + + public String buildSignature() { + return buildSignature; + } + + public String installerStore() { + return installerStore; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/pathprovider/PathProvider.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/pathprovider/PathProvider.java new file mode 100644 index 00000000000..bac6b0c462b --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/pathprovider/PathProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.pathprovider; + +import dart.async.Future; +import dart.io.Directory; + +/// path_provider's top-level functions, mapped onto Codename One's app storage: +/// the documents and support directories live under the app's private home +/// (`FileSystemStorage.getAppHomePath`), the temporary and cache ones in its +/// caches directory. Directories that only Android exposes answer null, as +/// path_provider does on a platform without them. +public final class PathProvider { + private PathProvider() { + } + + private static Future under(Directory base, String name) { + Directory d = name == null ? base : new Directory(base.path() + "/" + name); + if (!d.existsSync()) { + d.createSync(Boolean.TRUE); + } + return Future.value(d); + } + + public static Future getApplicationDocumentsDirectory() { + return under(Directory.current, null); + } + + public static Future getApplicationSupportDirectory() { + return under(Directory.current, "support"); + } + + public static Future getLibraryDirectory() { + return under(Directory.current, "library"); + } + + // Temp and cache are separate subdirectories: apps clear the temp directory + // recursively, which must neither take the cache with it nor reach past the + // app's own files into the platform temp root. + public static Future getTemporaryDirectory() { + return under(Directory.systemTemp, "tmp"); + } + + public static Future getApplicationCacheDirectory() { + return under(Directory.systemTemp, "cache"); + } + + public static Future getDownloadsDirectory() { + return under(Directory.current, "downloads"); + } + + public static Future getExternalStorageDirectory() { + return Future.value(null); + } + + public static Future getExternalCacheDirectories() { + return Future.value(null); + } + + public static Future getExternalStorageDirectories(Object type) { + return Future.value(null); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareParams.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareParams.java new file mode 100644 index 00000000000..10c4c4404d4 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareParams.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.share; + +import java.util.List; + +import com.codename1.flutter.plugins.crossfile.XFile; + +/** share_plus's {@code ShareParams}: the text, link or files to share. */ +public class ShareParams { + String text; + String subject; + String title; + Object uri; + List files; + + public ShareParams() { + } + + public void text(String v) { + this.text = v; + } + + public void subject(String v) { + this.subject = v; + } + + public void title(String v) { + this.title = v; + } + + public void uri(Object v) { + this.uri = v; + } + + public void files(Object v) { + this.files = v instanceof List ? (List) v : null; + } + + /** Accepted for source compatibility; the platform sheet places itself. */ + public void sharePositionOrigin(Object v) { + } + + String firstFilePath() { + if (files == null) { + return null; + } + for (Object f : files) { + if (f instanceof XFile) { + return ((XFile) f).materializedPath(); + } + } + return null; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/SharePlus.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/SharePlus.java new file mode 100644 index 00000000000..98d8c3e2a17 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/SharePlus.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.share; + +import java.util.List; + +import com.codename1.flutter.plugins.crossfile.XFile; +import com.codename1.ui.Display; + +import dart.async.Future; + +/** + * share_plus, over {@code Display.share}. The platform share sheet does not report + * what the user picked, so a share that opened the sheet answers {@code unavailable} + * -- share_plus's own answer on platforms that cannot tell -- and one that had no + * display to open it on answers the same with an empty raw value. + */ +public class SharePlus { + /** {@code SharePlus.instance}. */ + public static final SharePlus instance = new SharePlus(); + + public Future share(ShareParams p) { + String text = p.text; + if (text == null && p.uri != null) { + text = String.valueOf(p.uri); + } + if (p.subject != null && text != null && p.firstFilePath() == null) { + text = p.subject + "\n" + text; + } + return open(text, p.firstFilePath()); + } + + static Future open(String text, String filePath) { + if (!Display.isInitialized()) { + return Future.value(new ShareResult("", ShareResultStatus.unavailable)); + } + if (filePath != null) { + Display.getInstance().share(text, filePath, mimeOf(filePath)); + } else { + Display.getInstance().share(text == null ? "" : text); + } + return Future.value(new ShareResult("", ShareResultStatus.unavailable)); + } + + static String mimeOf(String path) { + int dot = path.lastIndexOf('.'); + String ext = dot < 0 ? "" : path.substring(dot + 1); + if (ext.equalsIgnoreCase("png")) { + return "image/png"; + } + if (ext.equalsIgnoreCase("jpg") || ext.equalsIgnoreCase("jpeg")) { + return "image/jpeg"; + } + if (ext.equalsIgnoreCase("gif")) { + return "image/gif"; + } + if (ext.equalsIgnoreCase("pdf")) { + return "application/pdf"; + } + if (ext.equalsIgnoreCase("txt")) { + return "text/plain"; + } + return "application/octet-stream"; + } + + // --- the static Share API share_plus deprecated but most apps still call ----- + + /** {@code Share.share(text, {subject, sharePositionOrigin})}. */ + /** The pre-10 static {@code Share.share(text, subject: ...)}. */ + public static Future share(String text, String subject, Object origin) { + return shareText(text, subject, origin); + } + + public static Future shareText(String text, String subject, Object origin) { + return open(subject != null && text != null ? subject + "\n" + text : text, null); + } + + /** {@code Share.shareUri(uri, {sharePositionOrigin})}. */ + public static Future shareUri(Object uri, Object origin) { + return open(String.valueOf(uri), null); + } + + /** {@code Share.shareXFiles(files, {subject, text, sharePositionOrigin})}. */ + public static Future shareXFiles(Object files, String subject, String text, Object origin) { + String path = null; + if (files instanceof List) { + for (Object f : (List) files) { + if (f instanceof XFile) { + path = ((XFile) f).materializedPath(); + break; + } + } + } + return open(text, path); + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareResult.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareResult.java new file mode 100644 index 00000000000..d7041fe0098 --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareResult.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.share; + +/** share_plus's {@code ShareResult}: what the share sheet reports back. */ +public class ShareResult { + private final String raw; + private final ShareResultStatus status; + + public ShareResult(String raw, ShareResultStatus status) { + this.raw = raw; + this.status = status; + } + + public String raw() { + return raw; + } + + public ShareResultStatus status() { + return status; + } +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareResultStatus.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareResultStatus.java new file mode 100644 index 00000000000..e1732418d7c --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/share/ShareResultStatus.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.share; + +/** share_plus's {@code ShareResultStatus}. */ +public enum ShareResultStatus { + success, dismissed, unavailable +} diff --git a/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/sharedpreferences/SharedPreferences.java b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/sharedpreferences/SharedPreferences.java new file mode 100644 index 00000000000..c3a29f1d88f --- /dev/null +++ b/maven/flutter-runtime/src/main/plugins/com/codename1/flutter/plugins/sharedpreferences/SharedPreferences.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.plugins.sharedpreferences; + +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import com.codename1.io.Storage; +import com.codename1.ui.Display; + +import dart.async.Future; +import dart.core.DartList; +import dart.core.DartMap; +import dart.core.DartSet; + +/// shared_preferences' `SharedPreferences`: typed key/value pairs that outlive +/// the app. They are kept as one object in Codename One's [Storage] -- not in +/// `Preferences`, which can neither list its keys nor keep a value's type -- and +/// in memory alone when there is no Display (a headless run or a test). +/// +/// Writes are synchronous underneath; the futures the API returns are completed. +public final class SharedPreferences { + + static final String STORAGE_NAME = "cn1_flutter_shared_preferences"; + + private static SharedPreferences instance; + + private final Hashtable values; + + private SharedPreferences(Hashtable values) { + this.values = values; + } + + public static Future getInstance() { + if (instance == null) { + instance = new SharedPreferences(load()); + } + return Future.value(instance); + } + + /// `SharedPreferences.setMockInitialValues`: what a test starts from. + public static void setMockInitialValues(Map initial) { + Hashtable v = new Hashtable(); + if (initial != null) { + for (Map.Entry e : initial.entrySet()) { + if (e.getKey() != null && e.getValue() != null) { + v.put(e.getKey(), stored(e.getValue())); + } + } + } + instance = new SharedPreferences(v); + // As in pub, a mocked store lives in memory only: a test's clear() or set* + // must not overwrite the app's real persisted preferences. + mocked = true; + } + + private static boolean mocked; + + @SuppressWarnings("unchecked") + private static Hashtable load() { + if (Display.isInitialized()) { + Object o = Storage.getInstance().readObject(STORAGE_NAME); + if (o instanceof Hashtable) { + return (Hashtable) o; + } + } + return new Hashtable(); + } + + private Future save() { + boolean ok = true; + if (!mocked && Display.isInitialized()) { + ok = Storage.getInstance().writeObject(STORAGE_NAME, values); + } + return Future.value(Boolean.valueOf(ok)); + } + + /// Storage serializes Vector, not DartList; a list is stored as a Vector copy. + private static Object stored(Object value) { + if (value instanceof List) { + return new Vector((List) value); + } + if (value instanceof Integer) { + return Long.valueOf(((Integer) value).longValue()); + } + return value; + } + + public DartSet getKeys() { + return DartSet.from(values.keySet()); + } + + public Object get(String key) { + Object v = values.get(key); + return v instanceof Vector ? getStringList(key) : v; + } + + public boolean containsKey(String key) { + return values.containsKey(key); + } + + public String getString(String key) { + Object v = values.get(key); + return v instanceof String ? (String) v : null; + } + + public Long getInt(String key) { + Object v = values.get(key); + return v instanceof Long ? (Long) v : null; + } + + public Double getDouble(String key) { + Object v = values.get(key); + return v instanceof Double ? (Double) v : null; + } + + public Boolean getBool(String key) { + Object v = values.get(key); + return v instanceof Boolean ? (Boolean) v : null; + } + + public DartList getStringList(String key) { + Object v = values.get(key); + if (!(v instanceof Vector)) { + return null; + } + DartList out = new DartList(); + for (Object o : (Vector) v) { + out.add(o instanceof String ? (String) o : String.valueOf(o)); + } + return out; + } + + private Future put(String key, Object value) { + if (value == null) { + return remove(key); + } + values.put(key, stored(value)); + return save(); + } + + public Future setString(String key, String value) { + return put(key, value); + } + + public Future setInt(String key, long value) { + return put(key, Long.valueOf(value)); + } + + public Future setDouble(String key, double value) { + return put(key, Double.valueOf(value)); + } + + public Future setBool(String key, boolean value) { + return put(key, Boolean.valueOf(value)); + } + + public Future setStringList(String key, List value) { + return put(key, value); + } + + public Future remove(String key) { + values.remove(key); + return save(); + } + + public Future clear() { + values.clear(); + return save(); + } + + public Future reload() { + if (!mocked && Display.isInitialized()) { + Hashtable fresh = load(); + values.clear(); + values.putAll(fresh); + } + return Future.value(null); + } + + public Future commit() { + return save(); + } + + /// Everything, as `{key: value}` -- for apps that dump their settings. + public DartMap toMap() { + DartMap m = new DartMap(); + for (String k : values.keySet()) { + m.put(k, get(k)); + } + return m; + } +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_async_stream.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_async_stream.dart index 536c015d2e7..4e8a8f04a17 100644 --- a/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_async_stream.dart +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_async_stream.dart @@ -11,8 +11,9 @@ // - named constructor parameters -> void setter methods of the same name // - instance getters -> no-arg method calls (name()) // - named parameters of methods -> canonical positional order as declared -// - every callback-shaped parameter here stays Object/dynamic, matching -// the convention of every other stub's optional callbacks -- the Java +// - the callbacks a closure's parameter is read from (listen's onData, map, +// where, forEach, ...) are typed, so `stream.listen((xs) => xs.contains(1))` +// knows xs; the rest stay Object/dynamic -- the Java // SIGNATURE that actually receives a transpiled closure lives in // dart.async.Stream/StreamSubscription/StreamController (maven/dart-runtime), // never in this file. Outside a few names the emitter special-cases @@ -31,6 +32,13 @@ // Future.then's onError:. See dart.async.Stream's class comment for the // full detail. +@JavaName('dart.async.TimeoutException') +class TimeoutException implements Exception { + external TimeoutException(String? message, [Duration? duration]); + external String? get message; + external Duration? get duration; +} + @JavaName('dart.async.StreamSubscription') abstract class StreamSubscription { external Future cancel(); @@ -44,10 +52,51 @@ abstract class StreamSubscription { @JavaName('dart.async.Stream') abstract class Stream { - external StreamSubscription listen(Object? onData, - {Object? onError, Object? onDone, bool? cancelOnError}); - external Stream map(Object transform); - external Stream where(Object test); + external StreamSubscription listen(void Function(T event)? onData, + {Object? onError, void Function()? onDone, bool? cancelOnError}); + external Stream map(R Function(T event) transform); + external Stream where(bool Function(T event) test); + // Named constructors, as static factories with a raw return (see broadcast below). + external static Stream fromIterable(Iterable elements); + external static Stream value(Object? value); + external static Stream empty(); + external static Stream error(Object error); + external static Stream fromFuture(Future future); + external static Stream fromFutures(Iterable futures); + external static Stream periodic(Duration period, [Object? computation]); + external Stream expand(Object convert); + external Stream asyncMap(Object convert); + external Stream take(int count); + external Stream skip(int count); + external Stream takeWhile(bool Function(T element) test); + external Stream skipWhile(bool Function(T element) test); + external Stream distinct([Object? equals]); + external Stream handleError(Object onError, {Object? test}); + external Stream transform(Object streamTransformer); + external Stream cast(); + external Stream asBroadcastStream({Object? onListen, Object? onCancel}); + external bool get isBroadcast; + external Future> toList(); + external Future> toSet(); + external Future forEach(void Function(T element) action); + external Future drain([Object? futureValue]); + external Future get length; + external Future get isEmpty; + external Future get first; + external Future get last; + external Future firstWhere(bool Function(T element) test, {Object? orElse}); + external Future any(bool Function(T element) test); + external Future every(Object test); + external Future contains(Object? needle); + external Future join([String separator = ""]); + external Future fold(S initialValue, Object combine); + external Future reduce(Object combine); + external Future elementAt(int index); +} + +@JavaName('dart.async.StreamTransformer') +abstract class StreamTransformer { + external Stream bind(Stream stream); } @JavaName('dart.async.StreamController') diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_convert.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_convert.dart new file mode 100644 index 00000000000..148c553bb82 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_convert.dart @@ -0,0 +1,89 @@ +// Codename One Dart runtime API stubs -- dart:convert. +// +// Backed by dart.convert (maven/dart-runtime). Same conventions as +// dart_async_stream.dart: named parameters of methods are passed in their +// declared order, instance getters are no-arg method calls. +// +// jsonEncode calls toJson() on an object it cannot encode directly; the +// transpiler makes every class that declares a zero-argument toJson() +// implement dart.convert.JsonEncodable so that needs no reflection. +// +// The text and byte codecs share one Java class, dart.convert.ByteCodec, so +// their encode/decode are typed dynamic here: utf8.encode takes a String and +// returns a Uint8List, base64.encode the reverse. + +@JavaName('dart.convert.JsonCodec') +class JsonCodec { + external JsonCodec({Object? reviver, Object? toEncodable}); + external dynamic decode(String source, {Object? reviver}); + external String encode(Object? value, {Object? toEncodable}); + external JsonEncoder get encoder; + external JsonDecoder get decoder; +} + +@JavaName('dart.convert.JsonEncoder') +class JsonEncoder { + external JsonEncoder([Object? toEncodable]); + external static JsonEncoder withIndent(String? indent, [Object? toEncodable]); + external String convert(Object? object); +} + +@JavaName('dart.convert.JsonDecoder') +class JsonDecoder { + external JsonDecoder([Object? reviver]); + external dynamic convert(String input); +} + +@JavaName('dart.convert.JsonUnsupportedObjectError') +class JsonUnsupportedObjectError { + external Object? get unsupportedObject; +} + +@JavaName('dart.convert.ByteCodec') +abstract class Encoding { + external String get name; + external dynamic encode(dynamic input); + external dynamic decode(dynamic encoded, {bool? allowMalformed}); + external Encoding get encoder; + external Encoding get decoder; + external dynamic convert(dynamic input); +} + +@JavaName('dart.convert.ByteCodec') +abstract class Utf8Codec extends Encoding { +} + +@JavaName('dart.convert.ByteCodec') +abstract class Base64Codec extends Encoding { +} + +@JavaName('dart.convert.LineSplitter') +class LineSplitter { + external LineSplitter(); + external static List split(String lines); + external List convert(String data); +} + +@JavaName('dart.convert.Convert.json') +JsonCodec json = JsonCodec(); +@JavaName('dart.convert.Convert.utf8') +Utf8Codec utf8 = Utf8Codec(); +@JavaName('dart.convert.Convert.ascii') +Encoding ascii = Encoding(); +@JavaName('dart.convert.Convert.latin1') +Encoding latin1 = Encoding(); +@JavaName('dart.convert.Convert.base64') +Base64Codec base64 = Base64Codec(); +@JavaName('dart.convert.Convert.base64Url') +Base64Codec base64Url = Base64Codec(); + +@JavaName('dart.convert.Convert.jsonDecode') +external dynamic jsonDecode(String source, {Object? reviver}); +@JavaName('dart.convert.Convert.jsonEncode') +external String jsonEncode(Object? object, {Object? toEncodable}); +@JavaName('dart.convert.Convert.base64Encode') +external String base64Encode(dynamic bytes); +@JavaName('dart.convert.Convert.base64UrlEncode') +external String base64UrlEncode(dynamic bytes); +@JavaName('dart.convert.Convert.base64Decode') +external Uint8List base64Decode(String source); diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_io.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_io.dart new file mode 100644 index 00000000000..2244efceb76 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/dart_io.dart @@ -0,0 +1,298 @@ +// Codename One Dart runtime API stubs -- dart:io. +// +// Backed by dart.io (maven/dart-runtime): +// - File/Directory over FileSystemStorage. Dart's plain paths map to its +// file:// URLs. Before Codename One starts, an in-memory file system stands +// in, which is what the transpiler's own tests run against. +// - Platform from Display.getPlatformName(), so the simulator answers for +// the skin it shows. +// - HttpClient over ConnectionRequest; the body of a response arrives as one +// chunk of its stream. +// - WebSocket over com.codename1.io.WebSocket. +// Same conventions as dart_async_stream.dart: named parameters of methods are +// passed in their declared order, instance getters are no-arg method calls. +// The asynchronous file methods do their I/O before returning a completed +// future; a failure still arrives through the future. + +@JavaName('dart.io.IOException') +class IOException { + external String get message; +} + +@JavaName('dart.io.FileSystemException') +class FileSystemException extends IOException { + external FileSystemException([String message = "", String? path]); + external String? get path; +} + +@JavaName('dart.io.SocketException') +class SocketException extends IOException { + external SocketException(String message); +} + +@JavaName('dart.io.HttpException') +class HttpException extends IOException { + external HttpException(String message, {Uri? uri}); + external Uri? get uri; +} + +@JavaName('dart.io.FileMode') +class FileMode { + external static FileMode get read; + external static FileMode get write; + external static FileMode get append; + external static FileMode get writeOnly; + external static FileMode get writeOnlyAppend; +} + +@JavaName('dart.io.FileSystemEntity') +abstract class FileSystemEntity { + external String get path; + external Uri get uri; + external bool existsSync(); + external Future exists(); + external Directory get parent; + external bool get isAbsolute; + external void deleteSync({bool recursive = false}); + external Future delete({bool recursive = false}); + external static bool isDirectorySync(String path); + external static Future isDirectory(String path); + external static bool isFileSync(String path); + external static Future isFile(String path); +} + +@JavaName('dart.io.File') +class File extends FileSystemEntity { + external File(String path); + external static File fromUri(Uri uri); + external File get absolute; + external Uint8List readAsBytesSync(); + external Future readAsBytes(); + external String readAsStringSync({Object? encoding}); + external Future readAsString({Object? encoding}); + external List readAsLinesSync({Object? encoding}); + external Future> readAsLines({Object? encoding}); + external Stream> openRead([int? start, int? end]); + external void writeAsBytesSync(List bytes, {FileMode mode = FileMode.write, bool flush = false}); + external Future writeAsBytes(List bytes, {FileMode mode = FileMode.write, bool flush = false}); + external void writeAsStringSync(String contents, + {FileMode mode = FileMode.write, Object? encoding, bool flush = false}); + external Future writeAsString(String contents, + {FileMode mode = FileMode.write, Object? encoding, bool flush = false}); + external void createSync({bool recursive = false, bool exclusive = false}); + external Future create({bool recursive = false, bool exclusive = false}); + external int lengthSync(); + external Future length(); + external DateTime lastModifiedSync(); + external Future lastModified(); + external File copySync(String newPath); + external Future copy(String newPath); + external File renameSync(String newPath); + external Future rename(String newPath); +} + +@JavaName('dart.io.Directory') +class Directory extends FileSystemEntity { + external Directory(String path); + external static Directory get systemTemp; + external static Directory get current; + external static set current(Directory dir); + external Directory get absolute; + external void createSync({bool recursive = false}); + external Future create({bool recursive = false}); + external List listSync({bool recursive = false, bool followLinks = true}); + external Stream list({bool recursive = false, bool followLinks = true}); + external Directory renameSync(String newPath); + external Future rename(String newPath); + external Directory createTempSync([String? prefix]); + external Future createTemp([String? prefix]); +} + +@JavaName('dart.io.Platform') +abstract class Platform { + external static String get operatingSystem; + external static String get operatingSystemVersion; + external static bool get isAndroid; + external static bool get isIOS; + external static bool get isMacOS; + external static bool get isWindows; + external static bool get isLinux; + external static bool get isFuchsia; + external static String get localeName; + external static int get numberOfProcessors; + external static String get pathSeparator; + external static String get localHostname; + external static String get version; + external static Map get environment; + external static String get resolvedExecutable; + external static String get script; +} + +@JavaName('dart.io.HttpStatus') +abstract class HttpStatus { + external static int get continue_; + external static int get ok; + external static int get created; + external static int get accepted; + external static int get noContent; + external static int get movedPermanently; + external static int get found; + external static int get seeOther; + external static int get notModified; + external static int get temporaryRedirect; + external static int get permanentRedirect; + external static int get badRequest; + external static int get unauthorized; + external static int get paymentRequired; + external static int get forbidden; + external static int get notFound; + external static int get methodNotAllowed; + external static int get notAcceptable; + external static int get requestTimeout; + external static int get conflict; + external static int get gone; + external static int get unprocessableEntity; + external static int get tooManyRequests; + external static int get internalServerError; + external static int get notImplemented; + external static int get badGateway; + external static int get serviceUnavailable; + external static int get gatewayTimeout; +} + +@JavaName('dart.io.ContentType') +class ContentType { + external ContentType(String primaryType, String subType, {String? charset, Map parameters = const {}}); + external static ContentType parse(String value); + external static ContentType get text; + external static ContentType get html; + external static ContentType get json; + external static ContentType get binary; + external String get primaryType; + external String get subType; + external String get mimeType; + external String? get charset; + external String get value; +} + +@JavaName('dart.io.HttpHeaders') +abstract class HttpHeaders { + external static String get acceptHeader; + external static String get acceptEncodingHeader; + external static String get acceptLanguageHeader; + external static String get authorizationHeader; + external static String get cacheControlHeader; + external static String get contentEncodingHeader; + external static String get contentLengthHeader; + external static String get contentTypeHeader; + external static String get cookieHeader; + external static String get dateHeader; + external static String get etagHeader; + external static String get hostHeader; + external static String get ifModifiedSinceHeader; + external static String get ifNoneMatchHeader; + external static String get lastModifiedHeader; + external static String get locationHeader; + external static String get setCookieHeader; + external static String get userAgentHeader; + external List? operator [](String name); + external String? value(String name); + external void add(String name, Object value, {bool preserveHeaderCase = false}); + external void set(String name, Object value, {bool preserveHeaderCase = false}); + external void remove(String name, Object value); + external void removeAll(String name); + external void clear(); + external void forEach(Object action); + external ContentType? get contentType; + external set contentType(ContentType? type); + external int get contentLength; + external set contentLength(int length); + external bool get chunkedTransferEncoding; + external set chunkedTransferEncoding(bool v); +} + +@JavaName('dart.io.HttpClient') +class HttpClient { + external HttpClient({Object? context}); + external Duration? get connectionTimeout; + external set connectionTimeout(Duration? d); + external Duration get idleTimeout; + external set idleTimeout(Duration d); + external String? get userAgent; + external set userAgent(String? ua); + external bool get autoUncompress; + external set autoUncompress(bool v); + external set badCertificateCallback(Object? callback); + external set maxConnectionsPerHost(int? n); + external set findProxy(Object? f); + external Future openUrl(String method, Uri url); + external Future getUrl(Uri url); + external Future postUrl(Uri url); + external Future putUrl(Uri url); + external Future patchUrl(Uri url); + external Future deleteUrl(Uri url); + external Future headUrl(Uri url); + external Future open(String method, String host, int port, String path); + external Future get(String host, int port, String path); + external Future post(String host, int port, String path); + external void close({bool force = false}); +} + +@JavaName('dart.io.HttpClientRequest') +abstract class HttpClientRequest { + external String get method; + external Uri get uri; + external HttpHeaders get headers; + external bool get followRedirects; + external set followRedirects(bool v); + external int get maxRedirects; + external set maxRedirects(int v); + external int get contentLength; + external set contentLength(int v); + external bool get persistentConnection; + external set persistentConnection(bool v); + external Object get encoding; + external set encoding(Object e); + external void add(List data); + external void write(Object? object); + external void writeln([Object? object]); + external Future addStream(Stream> stream); + external Future flush(); + external void abort([Object? exception, Object? stackTrace]); + external Future get done; + external Future close(); +} + +@JavaName('dart.io.HttpClientResponse') +abstract class HttpClientResponse extends Stream> { + external int get statusCode; + external String get reasonPhrase; + external HttpHeaders get headers; + external int get contentLength; + external bool get isRedirect; + external bool get persistentConnection; + external List get redirects; + external List get cookies; + external String get compressionState; +} + +@JavaName('dart.io.WebSocket') +abstract class WebSocket extends Stream { + external static int get connecting; + external static int get open; + external static int get closing; + external static int get closed; + external static Future connect(String url, + {Iterable? protocols, Map? headers, Object? compression}); + external void add(Object? data); + external void addUtf8Text(List bytes); + external Future addStream(Stream stream); + external Future close([int? code, String? reason]); + external Future get done; + external int get readyState; + external int? get closeCode; + external String? get closeReason; + external String? get protocol; + external String get extensions; + external set pingInterval(Duration? interval); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/flutter_material.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/flutter_material.dart index e1a8101b108..e03b40208fd 100644 --- a/maven/flutter-runtime/src/main/resources/META-INF/dart/flutter_material.dart +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/flutter_material.dart @@ -37,6 +37,8 @@ abstract class BuildContext { external T dependOnInheritedWidgetOfExactType(); external T watch(); external T read(); + // `witness` is filled in with the class of , from the selector's parameter type. + external R select(R Function(T value) selector, [Type? witness]); // The render object of this element — Flutter's `BuildContext.findRenderObject`. external RenderObject? findRenderObject(); // The nearest ancestor State of the given type (recovered via the trailing @@ -210,6 +212,2093 @@ class IconData {} @JavaName('com.codename1.flutter.Icons') abstract class Icons { + external static IconData get abc; + external static IconData get ac_unit; + external static IconData get access_alarms; + external static IconData get access_time_filled; + external static IconData get accessibility; + external static IconData get accessibility_new; + external static IconData get accessible; + external static IconData get accessible_forward; + external static IconData get account_balance; + external static IconData get account_balance_wallet; + external static IconData get account_box; + external static IconData get account_tree; + external static IconData get ad_units; + external static IconData get adb; + external static IconData get add_a_photo; + external static IconData get add_alarm; + external static IconData get add_alert; + external static IconData get add_box; + external static IconData get add_business; + external static IconData get add_call; + external static IconData get add_card; + external static IconData get add_chart; + external static IconData get add_home; + external static IconData get add_home_work; + external static IconData get add_ic_call; + external static IconData get add_link; + external static IconData get add_location; + external static IconData get add_location_alt; + external static IconData get add_moderator; + external static IconData get add_photo_alternate; + external static IconData get add_reaction; + external static IconData get add_road; + external static IconData get add_task; + external static IconData get add_to_drive; + external static IconData get add_to_home_screen; + external static IconData get add_to_photos; + external static IconData get add_to_queue; + external static IconData get addchart; + external static IconData get adf_scanner; + external static IconData get adjust; + external static IconData get admin_panel_settings; + external static IconData get adobe; + external static IconData get ads_click; + external static IconData get agriculture; + external static IconData get air; + external static IconData get airline_seat_flat; + external static IconData get airline_seat_flat_angled; + external static IconData get airline_seat_individual_suite; + external static IconData get airline_seat_legroom_extra; + external static IconData get airline_seat_legroom_normal; + external static IconData get airline_seat_legroom_reduced; + external static IconData get airline_seat_recline_extra; + external static IconData get airline_seat_recline_normal; + external static IconData get airline_stops; + external static IconData get airlines; + external static IconData get airplane_ticket; + external static IconData get airplanemode_inactive; + external static IconData get airplanemode_off; + external static IconData get airplanemode_on; + external static IconData get airplay; + external static IconData get airport_shuttle; + external static IconData get alarm; + external static IconData get alarm_add; + external static IconData get alarm_off; + external static IconData get album; + external static IconData get align_horizontal_center; + external static IconData get align_horizontal_left; + external static IconData get align_horizontal_right; + external static IconData get align_vertical_bottom; + external static IconData get align_vertical_center; + external static IconData get align_vertical_top; + external static IconData get all_inbox; + external static IconData get all_inclusive; + external static IconData get all_out; + external static IconData get alt_route; + external static IconData get alternate_email; + external static IconData get amp_stories; + external static IconData get analytics; + external static IconData get anchor; + external static IconData get android; + external static IconData get animation; + external static IconData get announcement; + external static IconData get aod; + external static IconData get apartment; + external static IconData get api; + external static IconData get app_blocking; + external static IconData get app_registration; + external static IconData get app_settings_alt; + external static IconData get app_shortcut; + external static IconData get apple; + external static IconData get approval; + external static IconData get apps; + external static IconData get apps_outage; + external static IconData get architecture; + external static IconData get archive; + external static IconData get area_chart; + external static IconData get arrow_back_ios_new; + external static IconData get arrow_circle_down; + external static IconData get arrow_circle_left; + external static IconData get arrow_circle_right; + external static IconData get arrow_circle_up; + external static IconData get arrow_downward; + external static IconData get arrow_drop_down_circle; + external static IconData get arrow_outward; + external static IconData get arrow_right; + external static IconData get arrow_right_alt; + external static IconData get arrow_upward; + external static IconData get art_track; + external static IconData get article; + external static IconData get aspect_ratio; + external static IconData get assessment; + external static IconData get assignment; + external static IconData get assignment_add; + external static IconData get assignment_ind; + external static IconData get assignment_late; + external static IconData get assignment_return; + external static IconData get assignment_returned; + external static IconData get assignment_turned_in; + external static IconData get assist_walker; + external static IconData get assistant; + external static IconData get assistant_direction; + external static IconData get assistant_navigation; + external static IconData get assistant_photo; + external static IconData get assured_workload; + external static IconData get atm; + external static IconData get attach_email; + external static IconData get attach_file; + external static IconData get attachment; + external static IconData get attractions; + external static IconData get attribution; + external static IconData get audio_file; + external static IconData get audiotrack; + external static IconData get auto_awesome; + external static IconData get auto_awesome_mosaic; + external static IconData get auto_awesome_motion; + external static IconData get auto_delete; + external static IconData get auto_fix_high; + external static IconData get auto_fix_normal; + external static IconData get auto_fix_off; + external static IconData get auto_graph; + external static IconData get auto_mode; + external static IconData get auto_stories; + external static IconData get autofps_select; + external static IconData get autorenew; + external static IconData get av_timer; + external static IconData get baby_changing_station; + external static IconData get back_hand; + external static IconData get backpack; + external static IconData get backspace; + external static IconData get backup; + external static IconData get backup_table; + external static IconData get badge; + external static IconData get bakery_dining; + external static IconData get balance; + external static IconData get balcony; + external static IconData get ballot; + external static IconData get bar_chart; + external static IconData get barcode_reader; + external static IconData get batch_prediction; + external static IconData get bathroom; + external static IconData get bathtub; + external static IconData get battery_0_bar; + external static IconData get battery_1_bar; + external static IconData get battery_2_bar; + external static IconData get battery_3_bar; + external static IconData get battery_4_bar; + external static IconData get battery_5_bar; + external static IconData get battery_6_bar; + external static IconData get battery_alert; + external static IconData get battery_charging_full; + external static IconData get battery_full; + external static IconData get battery_saver; + external static IconData get battery_std; + external static IconData get battery_unknown; + external static IconData get beach_access; + external static IconData get bed; + external static IconData get bedroom_baby; + external static IconData get bedroom_child; + external static IconData get bedroom_parent; + external static IconData get bedtime; + external static IconData get bedtime_off; + external static IconData get beenhere; + external static IconData get bento; + external static IconData get bike_scooter; + external static IconData get biotech; + external static IconData get blender; + external static IconData get blind; + external static IconData get blinds; + external static IconData get blinds_closed; + external static IconData get block; + external static IconData get block_flipped; + external static IconData get bloodtype; + external static IconData get bluetooth; + external static IconData get bluetooth_audio; + external static IconData get bluetooth_connected; + external static IconData get bluetooth_disabled; + external static IconData get bluetooth_drive; + external static IconData get bluetooth_searching; + external static IconData get blur_circular; + external static IconData get blur_linear; + external static IconData get blur_off; + external static IconData get blur_on; + external static IconData get bolt; + external static IconData get book_online; + external static IconData get bookmark; + external static IconData get bookmark_add; + external static IconData get bookmark_added; + external static IconData get bookmark_outline; + external static IconData get bookmark_remove; + external static IconData get bookmarks; + external static IconData get border_all; + external static IconData get border_bottom; + external static IconData get border_clear; + external static IconData get border_color; + external static IconData get border_horizontal; + external static IconData get border_inner; + external static IconData get border_left; + external static IconData get border_outer; + external static IconData get border_right; + external static IconData get border_style; + external static IconData get border_top; + external static IconData get border_vertical; + external static IconData get boy; + external static IconData get branding_watermark; + external static IconData get breakfast_dining; + external static IconData get brightness_1; + external static IconData get brightness_2; + external static IconData get brightness_3; + external static IconData get brightness_4; + external static IconData get brightness_6; + external static IconData get brightness_7; + external static IconData get brightness_auto; + external static IconData get brightness_high; + external static IconData get brightness_low; + external static IconData get brightness_medium; + external static IconData get broadcast_on_home; + external static IconData get broadcast_on_personal; + external static IconData get broken_image; + external static IconData get browse_gallery; + external static IconData get browser_not_supported; + external static IconData get browser_updated; + external static IconData get brunch_dining; + external static IconData get brush; + external static IconData get bubble_chart; + external static IconData get bug_report; + external static IconData get build; + external static IconData get build_circle; + external static IconData get bungalow; + external static IconData get burst_mode; + external static IconData get bus_alert; + external static IconData get business; + external static IconData get business_center; + external static IconData get cabin; + external static IconData get cable; + external static IconData get cached; + external static IconData get cake; + external static IconData get calculate; + external static IconData get calendar_month; + external static IconData get calendar_view_day; + external static IconData get calendar_view_month; + external static IconData get calendar_view_week; + external static IconData get call; + external static IconData get call_end; + external static IconData get call_made; + external static IconData get call_merge; + external static IconData get call_missed; + external static IconData get call_missed_outgoing; + external static IconData get call_received; + external static IconData get call_split; + external static IconData get call_to_action; + external static IconData get camera; + external static IconData get camera_alt; + external static IconData get camera_front; + external static IconData get camera_indoor; + external static IconData get camera_outdoor; + external static IconData get camera_rear; + external static IconData get camera_roll; + external static IconData get cameraswitch; + external static IconData get campaign; + external static IconData get cancel; + external static IconData get cancel_presentation; + external static IconData get cancel_schedule_send; + external static IconData get candlestick_chart; + external static IconData get car_crash; + external static IconData get car_rental; + external static IconData get car_repair; + external static IconData get card_giftcard; + external static IconData get card_membership; + external static IconData get card_travel; + external static IconData get carpenter; + external static IconData get cases; + external static IconData get casino; + external static IconData get cast; + external static IconData get cast_connected; + external static IconData get cast_for_education; + external static IconData get castle; + external static IconData get catching_pokemon; + external static IconData get category; + external static IconData get celebration; + external static IconData get cell_tower; + external static IconData get cell_wifi; + external static IconData get center_focus_strong; + external static IconData get center_focus_weak; + external static IconData get chair; + external static IconData get chair_alt; + external static IconData get chalet; + external static IconData get change_circle; + external static IconData get change_history; + external static IconData get charging_station; + external static IconData get chat; + external static IconData get chat_bubble; + external static IconData get chat_bubble_outline; + external static IconData get check_box; + external static IconData get check_box_outline_blank; + external static IconData get checklist; + external static IconData get checklist_rtl; + external static IconData get checkroom; + external static IconData get child_care; + external static IconData get child_friendly; + external static IconData get chrome_reader_mode; + external static IconData get church; + external static IconData get circle; + external static IconData get circle_notifications; + external static IconData get class_; + external static IconData get clean_hands; + external static IconData get cleaning_services; + external static IconData get clear; + external static IconData get clear_all; + external static IconData get close_fullscreen; + external static IconData get closed_caption; + external static IconData get closed_caption_disabled; + external static IconData get closed_caption_off; + external static IconData get cloud; + external static IconData get cloud_circle; + external static IconData get cloud_done; + external static IconData get cloud_download; + external static IconData get cloud_off; + external static IconData get cloud_queue; + external static IconData get cloud_sync; + external static IconData get cloud_upload; + external static IconData get cloudy_snowing; + external static IconData get co2; + external static IconData get co_present; + external static IconData get code_off; + external static IconData get coffee; + external static IconData get coffee_maker; + external static IconData get collections; + external static IconData get collections_bookmark; + external static IconData get color_lens; + external static IconData get colorize; + external static IconData get comment_bank; + external static IconData get comments_disabled; + external static IconData get commit; + external static IconData get commute; + external static IconData get compare; + external static IconData get compare_arrows; + external static IconData get compass_calibration; + external static IconData get compost; + external static IconData get compress; + external static IconData get computer; + external static IconData get confirmation_num; + external static IconData get confirmation_number; + external static IconData get connect_without_contact; + external static IconData get connected_tv; + external static IconData get connecting_airports; + external static IconData get construction; + external static IconData get contact_emergency; + external static IconData get contact_mail; + external static IconData get contact_page; + external static IconData get contact_phone; + external static IconData get contact_support; + external static IconData get contactless; + external static IconData get contacts; + external static IconData get content_copy; + external static IconData get content_cut; + external static IconData get content_paste; + external static IconData get content_paste_go; + external static IconData get content_paste_off; + external static IconData get content_paste_search; + external static IconData get contrast; + external static IconData get control_camera; + external static IconData get control_point; + external static IconData get control_point_duplicate; + external static IconData get conveyor_belt; + external static IconData get cookie; + external static IconData get copy_all; + external static IconData get copyright; + external static IconData get coronavirus; + external static IconData get corporate_fare; + external static IconData get cottage; + external static IconData get countertops; + external static IconData get create_new_folder; + external static IconData get credit_card_off; + external static IconData get credit_score; + external static IconData get crib; + external static IconData get crisis_alert; + external static IconData get crop; + external static IconData get crop_16_9; + external static IconData get crop_3_2; + external static IconData get crop_5_4; + external static IconData get crop_7_5; + external static IconData get crop_din; + external static IconData get crop_free; + external static IconData get crop_landscape; + external static IconData get crop_original; + external static IconData get crop_portrait; + external static IconData get crop_rotate; + external static IconData get crop_square; + external static IconData get cruelty_free; + external static IconData get css; + external static IconData get currency_bitcoin; + external static IconData get currency_exchange; + external static IconData get currency_franc; + external static IconData get currency_lira; + external static IconData get currency_pound; + external static IconData get currency_ruble; + external static IconData get currency_rupee; + external static IconData get currency_yen; + external static IconData get currency_yuan; + external static IconData get curtains; + external static IconData get curtains_closed; + external static IconData get cyclone; + external static IconData get dangerous; + external static IconData get dark_mode; + external static IconData get dashboard; + external static IconData get dashboard_customize; + external static IconData get data_array; + external static IconData get data_exploration; + external static IconData get data_object; + external static IconData get data_saver_off; + external static IconData get data_saver_on; + external static IconData get data_thresholding; + external static IconData get data_usage; + external static IconData get dataset; + external static IconData get dataset_linked; + external static IconData get deblur; + external static IconData get deck; + external static IconData get dehaze; + external static IconData get delete_forever; + external static IconData get delete_outline; + external static IconData get delete_sweep; + external static IconData get delivery_dining; + external static IconData get density_large; + external static IconData get density_medium; + external static IconData get density_small; + external static IconData get departure_board; + external static IconData get description; + external static IconData get deselect; + external static IconData get design_services; + external static IconData get desk; + external static IconData get desktop_access_disabled; + external static IconData get desktop_mac; + external static IconData get desktop_windows; + external static IconData get details; + external static IconData get developer_board; + external static IconData get developer_board_off; + external static IconData get developer_mode; + external static IconData get device_hub; + external static IconData get device_thermostat; + external static IconData get device_unknown; + external static IconData get devices; + external static IconData get devices_fold; + external static IconData get devices_other; + external static IconData get dew_point; + external static IconData get dialer_sip; + external static IconData get dialpad; + external static IconData get diamond; + external static IconData get difference; + external static IconData get dining; + external static IconData get dinner_dining; + external static IconData get directions; + external static IconData get directions_boat; + external static IconData get directions_boat_filled; + external static IconData get directions_bus; + external static IconData get directions_bus_filled; + external static IconData get directions_car; + external static IconData get directions_car_filled; + external static IconData get directions_ferry; + external static IconData get directions_off; + external static IconData get directions_railway; + external static IconData get directions_railway_filled; + external static IconData get directions_run; + external static IconData get directions_subway; + external static IconData get directions_subway_filled; + external static IconData get directions_train; + external static IconData get directions_transit; + external static IconData get directions_transit_filled; + external static IconData get directions_walk; + external static IconData get dirty_lens; + external static IconData get disabled_by_default; + external static IconData get disabled_visible; + external static IconData get disc_full; + external static IconData get discord; + external static IconData get discount; + external static IconData get display_settings; + external static IconData get diversity_1; + external static IconData get diversity_2; + external static IconData get diversity_3; + external static IconData get dnd_forwardslash; + external static IconData get dns; + external static IconData get do_disturb; + external static IconData get do_disturb_alt; + external static IconData get do_disturb_off; + external static IconData get do_disturb_on; + external static IconData get do_not_disturb; + external static IconData get do_not_disturb_alt; + external static IconData get do_not_disturb_off; + external static IconData get do_not_disturb_on; + external static IconData get do_not_disturb_on_total_silence; + external static IconData get do_not_step; + external static IconData get do_not_touch; + external static IconData get dock; + external static IconData get document_scanner; + external static IconData get domain; + external static IconData get domain_add; + external static IconData get domain_disabled; + external static IconData get domain_verification; + external static IconData get done; + external static IconData get done_all; + external static IconData get done_outline; + external static IconData get donut_large; + external static IconData get donut_small; + external static IconData get door_back; + external static IconData get door_front; + external static IconData get door_sliding; + external static IconData get doorbell; + external static IconData get double_arrow; + external static IconData get downhill_skiing; + external static IconData get download; + external static IconData get download_done; + external static IconData get download_for_offline; + external static IconData get downloading; + external static IconData get drafts; + external static IconData get drag_handle; + external static IconData get drag_indicator; + external static IconData get draw; + external static IconData get drive_eta; + external static IconData get drive_file_move; + external static IconData get drive_file_move_outline; + external static IconData get drive_file_move_rtl; + external static IconData get drive_file_rename_outline; + external static IconData get drive_folder_upload; + external static IconData get dry; + external static IconData get dry_cleaning; + external static IconData get duo; + external static IconData get dvr; + external static IconData get dynamic_feed; + external static IconData get dynamic_form; + external static IconData get e_mobiledata; + external static IconData get earbuds; + external static IconData get earbuds_battery; + external static IconData get east; + external static IconData get eco; + external static IconData get edgesensor_high; + external static IconData get edgesensor_low; + external static IconData get edit_attributes; + external static IconData get edit_calendar; + external static IconData get edit_document; + external static IconData get edit_location; + external static IconData get edit_location_alt; + external static IconData get edit_note; + external static IconData get edit_notifications; + external static IconData get edit_off; + external static IconData get edit_road; + external static IconData get edit_square; + external static IconData get egg; + external static IconData get egg_alt; + external static IconData get eject; + external static IconData get elderly; + external static IconData get elderly_woman; + external static IconData get electric_bike; + external static IconData get electric_bolt; + external static IconData get electric_car; + external static IconData get electric_meter; + external static IconData get electric_moped; + external static IconData get electric_rickshaw; + external static IconData get electric_scooter; + external static IconData get electrical_services; + external static IconData get elevator; + external static IconData get emergency; + external static IconData get emergency_recording; + external static IconData get emergency_share; + external static IconData get emoji_emotions; + external static IconData get emoji_events; + external static IconData get emoji_flags; + external static IconData get emoji_food_beverage; + external static IconData get emoji_nature; + external static IconData get emoji_objects; + external static IconData get emoji_people; + external static IconData get emoji_symbols; + external static IconData get emoji_transportation; + external static IconData get energy_savings_leaf; + external static IconData get engineering; + external static IconData get enhance_photo_translate; + external static IconData get enhanced_encryption; + external static IconData get equalizer; + external static IconData get error; + external static IconData get error_outline; + external static IconData get escalator; + external static IconData get escalator_warning; + external static IconData get euro; + external static IconData get euro_symbol; + external static IconData get ev_station; + external static IconData get event; + external static IconData get event_available; + external static IconData get event_busy; + external static IconData get event_note; + external static IconData get event_repeat; + external static IconData get event_seat; + external static IconData get exit_to_app; + external static IconData get expand; + external static IconData get expand_circle_down; + external static IconData get expand_less; + external static IconData get expand_more; + external static IconData get explicit; + external static IconData get explore; + external static IconData get explore_off; + external static IconData get exposure; + external static IconData get exposure_minus_1; + external static IconData get exposure_minus_2; + external static IconData get exposure_neg_1; + external static IconData get exposure_neg_2; + external static IconData get exposure_plus_1; + external static IconData get exposure_plus_2; + external static IconData get exposure_zero; + external static IconData get extension; + external static IconData get extension_off; + external static IconData get face; + external static IconData get face_2; + external static IconData get face_3; + external static IconData get face_4; + external static IconData get face_5; + external static IconData get face_6; + external static IconData get face_retouching_natural; + external static IconData get face_retouching_off; + external static IconData get facebook; + external static IconData get fact_check; + external static IconData get factory; + external static IconData get family_restroom; + external static IconData get fast_forward; + external static IconData get fast_rewind; + external static IconData get fastfood; + external static IconData get favorite_outline; + external static IconData get fax; + external static IconData get featured_play_list; + external static IconData get featured_video; + external static IconData get feed; + external static IconData get female; + external static IconData get fence; + external static IconData get festival; + external static IconData get fiber_dvr; + external static IconData get fiber_manual_record; + external static IconData get fiber_new; + external static IconData get fiber_pin; + external static IconData get fiber_smart_record; + external static IconData get file_copy; + external static IconData get file_download; + external static IconData get file_download_done; + external static IconData get file_download_off; + external static IconData get file_open; + external static IconData get file_present; + external static IconData get file_upload; + external static IconData get file_upload_off; + external static IconData get filter; + external static IconData get filter_1; + external static IconData get filter_2; + external static IconData get filter_3; + external static IconData get filter_4; + external static IconData get filter_5; + external static IconData get filter_6; + external static IconData get filter_7; + external static IconData get filter_8; + external static IconData get filter_9; + external static IconData get filter_9_plus; + external static IconData get filter_alt; + external static IconData get filter_alt_off; + external static IconData get filter_b_and_w; + external static IconData get filter_center_focus; + external static IconData get filter_drama; + external static IconData get filter_frames; + external static IconData get filter_hdr; + external static IconData get filter_list; + external static IconData get filter_list_alt; + external static IconData get filter_list_off; + external static IconData get filter_none; + external static IconData get filter_tilt_shift; + external static IconData get filter_vintage; + external static IconData get find_in_page; + external static IconData get find_replace; + external static IconData get fingerprint; + external static IconData get fire_extinguisher; + external static IconData get fire_hydrant; + external static IconData get fire_hydrant_alt; + external static IconData get fire_truck; + external static IconData get fireplace; + external static IconData get first_page; + external static IconData get fit_screen; + external static IconData get fitbit; + external static IconData get fitness_center; + external static IconData get flag; + external static IconData get flag_circle; + external static IconData get flaky; + external static IconData get flare; + external static IconData get flash_auto; + external static IconData get flash_off; + external static IconData get flash_on; + external static IconData get flashlight_off; + external static IconData get flashlight_on; + external static IconData get flatware; + external static IconData get flight; + external static IconData get flight_class; + external static IconData get flight_land; + external static IconData get flight_takeoff; + external static IconData get flip; + external static IconData get flip_camera_android; + external static IconData get flip_camera_ios; + external static IconData get flip_to_back; + external static IconData get flip_to_front; + external static IconData get flood; + external static IconData get flourescent; + external static IconData get fluorescent; + external static IconData get flutter_dash; + external static IconData get fmd_bad; + external static IconData get fmd_good; + external static IconData get foggy; + external static IconData get folder; + external static IconData get folder_copy; + external static IconData get folder_delete; + external static IconData get folder_off; + external static IconData get folder_open; + external static IconData get folder_shared; + external static IconData get folder_special; + external static IconData get folder_zip; + external static IconData get follow_the_signs; + external static IconData get font_download; + external static IconData get font_download_off; + external static IconData get food_bank; + external static IconData get forest; + external static IconData get fork_left; + external static IconData get fork_right; + external static IconData get forklift; + external static IconData get format_align_center; + external static IconData get format_align_justify; + external static IconData get format_align_left; + external static IconData get format_align_right; + external static IconData get format_clear; + external static IconData get format_color_fill; + external static IconData get format_color_reset; + external static IconData get format_color_text; + external static IconData get format_indent_decrease; + external static IconData get format_indent_increase; + external static IconData get format_line_spacing; + external static IconData get format_list_bulleted; + external static IconData get format_list_bulleted_add; + external static IconData get format_list_numbered; + external static IconData get format_list_numbered_rtl; + external static IconData get format_overline; + external static IconData get format_paint; + external static IconData get format_quote; + external static IconData get format_shapes; + external static IconData get format_size; + external static IconData get format_strikethrough; + external static IconData get format_textdirection_l_to_r; + external static IconData get format_textdirection_r_to_l; + external static IconData get format_underlined; + external static IconData get fort; + external static IconData get forum; + external static IconData get forward; + external static IconData get forward_10; + external static IconData get forward_30; + external static IconData get forward_5; + external static IconData get forward_to_inbox; + external static IconData get foundation; + external static IconData get free_breakfast; + external static IconData get free_cancellation; + external static IconData get front_hand; + external static IconData get front_loader; + external static IconData get fullscreen_exit; + external static IconData get functions; + external static IconData get g_mobiledata; + external static IconData get g_translate; + external static IconData get gamepad; + external static IconData get games; + external static IconData get garage; + external static IconData get gas_meter; + external static IconData get gavel; + external static IconData get generating_tokens; + external static IconData get gesture; + external static IconData get get_app; + external static IconData get gif; + external static IconData get gif_box; + external static IconData get girl; + external static IconData get gite; + external static IconData get golf_course; + external static IconData get gpp_bad; + external static IconData get gpp_good; + external static IconData get gpp_maybe; + external static IconData get gps_fixed; + external static IconData get gps_not_fixed; + external static IconData get gps_off; + external static IconData get grade; + external static IconData get gradient; + external static IconData get grading; + external static IconData get grain; + external static IconData get graphic_eq; + external static IconData get grass; + external static IconData get grid_3x3; + external static IconData get grid_4x4; + external static IconData get grid_goldenratio; + external static IconData get grid_off; + external static IconData get grid_on; + external static IconData get grid_view; + external static IconData get group; + external static IconData get group_add; + external static IconData get group_off; + external static IconData get group_remove; + external static IconData get group_work; + external static IconData get groups; + external static IconData get groups_2; + external static IconData get groups_3; + external static IconData get h_mobiledata; + external static IconData get h_plus_mobiledata; + external static IconData get hail; + external static IconData get handshake; + external static IconData get handyman; + external static IconData get hardware; + external static IconData get hd; + external static IconData get hdr_auto; + external static IconData get hdr_auto_select; + external static IconData get hdr_enhanced_select; + external static IconData get hdr_off; + external static IconData get hdr_off_select; + external static IconData get hdr_on; + external static IconData get hdr_on_select; + external static IconData get hdr_plus; + external static IconData get hdr_strong; + external static IconData get hdr_weak; + external static IconData get headphones; + external static IconData get headphones_battery; + external static IconData get headset; + external static IconData get headset_mic; + external static IconData get headset_off; + external static IconData get healing; + external static IconData get health_and_safety; + external static IconData get hearing; + external static IconData get hearing_disabled; + external static IconData get heart_broken; + external static IconData get heat_pump; + external static IconData get height; + external static IconData get help_center; + external static IconData get help_outline; + external static IconData get hevc; + external static IconData get hexagon; + external static IconData get hide_image; + external static IconData get hide_source; + external static IconData get high_quality; + external static IconData get highlight; + external static IconData get highlight_alt; + external static IconData get highlight_off; + external static IconData get highlight_remove; + external static IconData get hiking; + external static IconData get history; + external static IconData get history_edu; + external static IconData get history_toggle_off; + external static IconData get hive; + external static IconData get hls; + external static IconData get hls_off; + external static IconData get holiday_village; + external static IconData get home_filled; + external static IconData get home_max; + external static IconData get home_mini; + external static IconData get home_repair_service; + external static IconData get home_work; + external static IconData get horizontal_distribute; + external static IconData get horizontal_rule; + external static IconData get horizontal_split; + external static IconData get hot_tub; + external static IconData get hotel_class; + external static IconData get hourglass_bottom; + external static IconData get hourglass_disabled; + external static IconData get hourglass_empty; + external static IconData get hourglass_full; + external static IconData get hourglass_top; + external static IconData get house; + external static IconData get house_siding; + external static IconData get houseboat; + external static IconData get how_to_reg; + external static IconData get how_to_vote; + external static IconData get html; + external static IconData get http; + external static IconData get https; + external static IconData get hub; + external static IconData get hvac; + external static IconData get ice_skating; + external static IconData get icecream; + external static IconData get image; + external static IconData get image_aspect_ratio; + external static IconData get image_not_supported; + external static IconData get image_search; + external static IconData get imagesearch_roller; + external static IconData get import_contacts; + external static IconData get import_export; + external static IconData get important_devices; + external static IconData get inbox; + external static IconData get incomplete_circle; + external static IconData get indeterminate_check_box; + external static IconData get input; + external static IconData get insert_chart; + external static IconData get insert_chart_outlined; + external static IconData get insert_comment; + external static IconData get insert_drive_file; + external static IconData get insert_emoticon; + external static IconData get insert_invitation; + external static IconData get insert_link; + external static IconData get insert_page_break; + external static IconData get insert_photo; + external static IconData get insights; + external static IconData get install_desktop; + external static IconData get install_mobile; + external static IconData get integration_instructions; + external static IconData get interests; + external static IconData get interpreter_mode; + external static IconData get inventory; + external static IconData get inventory_2; + external static IconData get invert_colors; + external static IconData get invert_colors_off; + external static IconData get invert_colors_on; + external static IconData get ios_share; + external static IconData get iron; + external static IconData get iso; + external static IconData get javascript; + external static IconData get join_full; + external static IconData get join_inner; + external static IconData get join_left; + external static IconData get join_right; + external static IconData get kayaking; + external static IconData get kebab_dining; + external static IconData get key; + external static IconData get key_off; + external static IconData get keyboard; + external static IconData get keyboard_alt; + external static IconData get keyboard_arrow_left; + external static IconData get keyboard_arrow_right; + external static IconData get keyboard_backspace; + external static IconData get keyboard_capslock; + external static IconData get keyboard_command; + external static IconData get keyboard_command_key; + external static IconData get keyboard_control; + external static IconData get keyboard_control_key; + external static IconData get keyboard_double_arrow_down; + external static IconData get keyboard_double_arrow_left; + external static IconData get keyboard_double_arrow_right; + external static IconData get keyboard_double_arrow_up; + external static IconData get keyboard_hide; + external static IconData get keyboard_option; + external static IconData get keyboard_option_key; + external static IconData get keyboard_return; + external static IconData get keyboard_tab; + external static IconData get keyboard_voice; + external static IconData get king_bed; + external static IconData get kitchen; + external static IconData get kitesurfing; + external static IconData get label; + external static IconData get label_important; + external static IconData get label_important_outline; + external static IconData get label_off; + external static IconData get label_outline; + external static IconData get lan; + external static IconData get landscape; + external static IconData get landslide; + external static IconData get language; + external static IconData get laptop; + external static IconData get laptop_chromebook; + external static IconData get laptop_mac; + external static IconData get laptop_windows; + external static IconData get last_page; + external static IconData get launch; + external static IconData get layers; + external static IconData get layers_clear; + external static IconData get leaderboard; + external static IconData get leak_add; + external static IconData get leak_remove; + external static IconData get leave_bags_at_home; + external static IconData get legend_toggle; + external static IconData get lens; + external static IconData get lens_blur; + external static IconData get library_add; + external static IconData get library_add_check; + external static IconData get library_music; + external static IconData get light; + external static IconData get light_mode; + external static IconData get lightbulb; + external static IconData get lightbulb_circle; + external static IconData get lightbulb_outline; + external static IconData get line_axis; + external static IconData get line_style; + external static IconData get line_weight; + external static IconData get linear_scale; + external static IconData get link_off; + external static IconData get linked_camera; + external static IconData get liquor; + external static IconData get list; + external static IconData get list_alt; + external static IconData get live_help; + external static IconData get live_tv; + external static IconData get living; + external static IconData get local_activity; + external static IconData get local_airport; + external static IconData get local_atm; + external static IconData get local_attraction; + external static IconData get local_bar; + external static IconData get local_cafe; + external static IconData get local_car_wash; + external static IconData get local_convenience_store; + external static IconData get local_dining; + external static IconData get local_drink; + external static IconData get local_fire_department; + external static IconData get local_florist; + external static IconData get local_gas_station; + external static IconData get local_grocery_store; + external static IconData get local_hospital; + external static IconData get local_hotel; + external static IconData get local_laundry_service; + external static IconData get local_library; + external static IconData get local_mall; + external static IconData get local_movies; + external static IconData get local_offer; + external static IconData get local_parking; + external static IconData get local_pharmacy; + external static IconData get local_phone; + external static IconData get local_pizza; + external static IconData get local_play; + external static IconData get local_police; + external static IconData get local_post_office; + external static IconData get local_print_shop; + external static IconData get local_printshop; + external static IconData get local_restaurant; + external static IconData get local_see; + external static IconData get local_shipping; + external static IconData get local_taxi; + external static IconData get location_city; + external static IconData get location_disabled; + external static IconData get location_history; + external static IconData get location_off; + external static IconData get location_on; + external static IconData get location_pin; + external static IconData get location_searching; + external static IconData get lock_clock; + external static IconData get lock_open; + external static IconData get lock_outline; + external static IconData get lock_person; + external static IconData get lock_reset; + external static IconData get login; + external static IconData get logo_dev; + external static IconData get logout; + external static IconData get looks; + external static IconData get looks_3; + external static IconData get looks_4; + external static IconData get looks_5; + external static IconData get looks_6; + external static IconData get looks_one; + external static IconData get looks_two; + external static IconData get loop; + external static IconData get loupe; + external static IconData get low_priority; + external static IconData get loyalty; + external static IconData get lte_mobiledata; + external static IconData get lte_plus_mobiledata; + external static IconData get luggage; + external static IconData get lunch_dining; + external static IconData get lyrics; + external static IconData get macro_off; + external static IconData get mail; + external static IconData get mail_lock; + external static IconData get mail_outline; + external static IconData get male; + external static IconData get man; + external static IconData get man_2; + external static IconData get man_3; + external static IconData get man_4; + external static IconData get manage_accounts; + external static IconData get manage_history; + external static IconData get manage_search; + external static IconData get map; + external static IconData get maps_home_work; + external static IconData get maps_ugc; + external static IconData get margin; + external static IconData get mark_as_unread; + external static IconData get mark_chat_read; + external static IconData get mark_chat_unread; + external static IconData get mark_email_read; + external static IconData get mark_email_unread; + external static IconData get mark_unread_chat_alt; + external static IconData get markunread; + external static IconData get markunread_mailbox; + external static IconData get masks; + external static IconData get maximize; + external static IconData get media_bluetooth_off; + external static IconData get media_bluetooth_on; + external static IconData get mediation; + external static IconData get medical_information; + external static IconData get medical_services; + external static IconData get medication; + external static IconData get medication_liquid; + external static IconData get meeting_room; + external static IconData get memory; + external static IconData get menu_book; + external static IconData get menu_open; + external static IconData get merge; + external static IconData get merge_type; + external static IconData get message; + external static IconData get messenger; + external static IconData get messenger_outline; + external static IconData get mic_external_off; + external static IconData get mic_external_on; + external static IconData get mic_none; + external static IconData get mic_off; + external static IconData get microwave; + external static IconData get military_tech; + external static IconData get minimize; + external static IconData get minor_crash; + external static IconData get miscellaneous_services; + external static IconData get missed_video_call; + external static IconData get mms; + external static IconData get mobile_friendly; + external static IconData get mobile_off; + external static IconData get mobile_screen_share; + external static IconData get mobiledata_off; + external static IconData get mode; + external static IconData get mode_comment; + external static IconData get mode_edit; + external static IconData get mode_edit_outline; + external static IconData get mode_fan_off; + external static IconData get mode_night; + external static IconData get mode_of_travel; + external static IconData get mode_standby; + external static IconData get model_training; + external static IconData get monetization_on; + external static IconData get money; + external static IconData get money_off_csred; + external static IconData get monitor; + external static IconData get monitor_heart; + external static IconData get monitor_weight; + external static IconData get monochrome_photos; + external static IconData get mood; + external static IconData get mood_bad; + external static IconData get moped; + external static IconData get more; + external static IconData get more_time; + external static IconData get mosque; + external static IconData get motion_photos_auto; + external static IconData get motion_photos_off; + external static IconData get motion_photos_on; + external static IconData get motion_photos_pause; + external static IconData get motion_photos_paused; + external static IconData get motorcycle; + external static IconData get mouse; + external static IconData get move_down; + external static IconData get move_to_inbox; + external static IconData get move_up; + external static IconData get movie; + external static IconData get movie_creation; + external static IconData get movie_edit; + external static IconData get movie_filter; + external static IconData get moving; + external static IconData get mp; + external static IconData get multiline_chart; + external static IconData get multiple_stop; + external static IconData get multitrack_audio; + external static IconData get museum; + external static IconData get music_note; + external static IconData get music_off; + external static IconData get music_video; + external static IconData get my_library_add; + external static IconData get my_library_books; + external static IconData get my_library_music; + external static IconData get my_location; + external static IconData get nat; + external static IconData get nature; + external static IconData get nature_people; + external static IconData get navigate_before; + external static IconData get navigate_next; + external static IconData get navigation; + external static IconData get near_me; + external static IconData get near_me_disabled; + external static IconData get nearby_error; + external static IconData get nearby_off; + external static IconData get nest_cam_wired_stand; + external static IconData get network_cell; + external static IconData get network_check; + external static IconData get network_locked; + external static IconData get network_ping; + external static IconData get network_wifi; + external static IconData get network_wifi_1_bar; + external static IconData get network_wifi_2_bar; + external static IconData get network_wifi_3_bar; + external static IconData get new_label; + external static IconData get new_releases; + external static IconData get newspaper; + external static IconData get next_plan; + external static IconData get next_week; + external static IconData get nfc; + external static IconData get night_shelter; + external static IconData get nightlife; + external static IconData get nightlight; + external static IconData get nightlight_round; + external static IconData get nights_stay; + external static IconData get no_accounts; + external static IconData get no_adult_content; + external static IconData get no_backpack; + external static IconData get no_cell; + external static IconData get no_crash; + external static IconData get no_drinks; + external static IconData get no_encryption; + external static IconData get no_encryption_gmailerrorred; + external static IconData get no_flash; + external static IconData get no_food; + external static IconData get no_luggage; + external static IconData get no_meals; + external static IconData get no_meals_ouline; + external static IconData get no_meeting_room; + external static IconData get no_photography; + external static IconData get no_sim; + external static IconData get no_stroller; + external static IconData get no_transfer; + external static IconData get noise_aware; + external static IconData get noise_control_off; + external static IconData get nordic_walking; + external static IconData get north; + external static IconData get north_east; + external static IconData get north_west; + external static IconData get not_accessible; + external static IconData get not_listed_location; + external static IconData get not_started; + external static IconData get note; + external static IconData get note_add; + external static IconData get note_alt; + external static IconData get notes; + external static IconData get notification_add; + external static IconData get notification_important; + external static IconData get notifications_active; + external static IconData get notifications_none; + external static IconData get notifications_off; + external static IconData get notifications_on; + external static IconData get notifications_paused; + external static IconData get now_wallpaper; + external static IconData get now_widgets; + external static IconData get numbers; + external static IconData get offline_bolt; + external static IconData get offline_pin; + external static IconData get offline_share; + external static IconData get oil_barrel; + external static IconData get on_device_training; + external static IconData get ondemand_video; + external static IconData get online_prediction; + external static IconData get opacity; + external static IconData get open_in_browser; + external static IconData get open_in_full; + external static IconData get open_in_new; + external static IconData get open_in_new_off; + external static IconData get open_with; + external static IconData get other_houses; + external static IconData get outbond; + external static IconData get outbound; + external static IconData get outbox; + external static IconData get outdoor_grill; + external static IconData get outgoing_mail; + external static IconData get outlet; + external static IconData get outlined_flag; + external static IconData get output; + external static IconData get padding; + external static IconData get pages; + external static IconData get pageview; + external static IconData get paid; + external static IconData get palette; + external static IconData get pallet; + external static IconData get pan_tool; + external static IconData get pan_tool_alt; + external static IconData get panorama; + external static IconData get panorama_fish_eye; + external static IconData get panorama_fisheye; + external static IconData get panorama_horizontal; + external static IconData get panorama_horizontal_select; + external static IconData get panorama_photosphere; + external static IconData get panorama_photosphere_select; + external static IconData get panorama_vertical; + external static IconData get panorama_vertical_select; + external static IconData get panorama_wide_angle; + external static IconData get panorama_wide_angle_select; + external static IconData get paragliding; + external static IconData get park; + external static IconData get party_mode; + external static IconData get password; + external static IconData get pattern; + external static IconData get pause; + external static IconData get pause_circle; + external static IconData get pause_circle_filled; + external static IconData get pause_circle_outline; + external static IconData get pause_presentation; + external static IconData get payment; + external static IconData get payments; + external static IconData get paypal; + external static IconData get pedal_bike; + external static IconData get pending; + external static IconData get pending_actions; + external static IconData get pentagon; + external static IconData get people; + external static IconData get people_alt; + external static IconData get people_outline; + external static IconData get percent; + external static IconData get perm_camera_mic; + external static IconData get perm_contact_cal; + external static IconData get perm_contact_calendar; + external static IconData get perm_data_setting; + external static IconData get perm_device_info; + external static IconData get perm_device_information; + external static IconData get perm_identity; + external static IconData get perm_media; + external static IconData get perm_phone_msg; + external static IconData get perm_scan_wifi; + external static IconData get person_2; + external static IconData get person_3; + external static IconData get person_4; + external static IconData get person_add_alt; + external static IconData get person_add_alt_1; + external static IconData get person_add_disabled; + external static IconData get person_off; + external static IconData get person_outline; + external static IconData get person_pin; + external static IconData get person_pin_circle; + external static IconData get person_remove; + external static IconData get person_remove_alt_1; + external static IconData get person_search; + external static IconData get personal_injury; + external static IconData get personal_video; + external static IconData get pest_control; + external static IconData get pest_control_rodent; + external static IconData get pets; + external static IconData get phishing; + external static IconData get phone_android; + external static IconData get phone_bluetooth_speaker; + external static IconData get phone_callback; + external static IconData get phone_disabled; + external static IconData get phone_enabled; + external static IconData get phone_forwarded; + external static IconData get phone_in_talk; + external static IconData get phone_iphone; + external static IconData get phone_locked; + external static IconData get phone_missed; + external static IconData get phone_paused; + external static IconData get phonelink; + external static IconData get phonelink_erase; + external static IconData get phonelink_lock; + external static IconData get phonelink_off; + external static IconData get phonelink_ring; + external static IconData get phonelink_setup; + external static IconData get photo_album; + external static IconData get photo_camera; + external static IconData get photo_camera_back; + external static IconData get photo_camera_front; + external static IconData get photo_filter; + external static IconData get photo_size_select_actual; + external static IconData get photo_size_select_large; + external static IconData get photo_size_select_small; + external static IconData get php; + external static IconData get piano; + external static IconData get piano_off; + external static IconData get picture_as_pdf; + external static IconData get picture_in_picture; + external static IconData get picture_in_picture_alt; + external static IconData get pie_chart_outline; + external static IconData get pie_chart_outlined; + external static IconData get pin; + external static IconData get pin_drop; + external static IconData get pin_end; + external static IconData get pin_invoke; + external static IconData get pinch; + external static IconData get pivot_table_chart; + external static IconData get pix; + external static IconData get plagiarism; + external static IconData get play_arrow; + external static IconData get play_circle; + external static IconData get play_circle_fill; + external static IconData get play_circle_filled; + external static IconData get play_circle_outline; + external static IconData get play_disabled; + external static IconData get play_for_work; + external static IconData get play_lesson; + external static IconData get playlist_add; + external static IconData get playlist_add_check; + external static IconData get playlist_add_check_circle; + external static IconData get playlist_add_circle; + external static IconData get playlist_play; + external static IconData get playlist_remove; + external static IconData get plumbing; + external static IconData get plus_one; + external static IconData get podcasts; + external static IconData get point_of_sale; + external static IconData get policy; + external static IconData get poll; + external static IconData get polyline; + external static IconData get polymer; + external static IconData get pool; + external static IconData get portable_wifi_off; + external static IconData get portrait; + external static IconData get post_add; + external static IconData get power; + external static IconData get power_input; + external static IconData get power_off; + external static IconData get power_settings_new; + external static IconData get precision_manufacturing; + external static IconData get pregnant_woman; + external static IconData get present_to_all; + external static IconData get preview; + external static IconData get price_change; + external static IconData get price_check; + external static IconData get print; + external static IconData get print_disabled; + external static IconData get priority_high; + external static IconData get privacy_tip; + external static IconData get private_connectivity; + external static IconData get production_quantity_limits; + external static IconData get propane; + external static IconData get propane_tank; + external static IconData get psychology; + external static IconData get psychology_alt; + external static IconData get public_; + external static IconData get public_off; + external static IconData get publish; + external static IconData get published_with_changes; + external static IconData get punch_clock; + external static IconData get push_pin; + external static IconData get qr_code; + external static IconData get qr_code_2; + external static IconData get qr_code_scanner; + external static IconData get query_builder; + external static IconData get query_stats; + external static IconData get question_answer; + external static IconData get question_mark; + external static IconData get queue; + external static IconData get queue_music; + external static IconData get queue_play_next; + external static IconData get quick_contacts_dialer; + external static IconData get quick_contacts_mail; + external static IconData get quickreply; + external static IconData get quiz; + external static IconData get quora; + external static IconData get r_mobiledata; + external static IconData get radar; + external static IconData get radio; + external static IconData get radio_button_checked; + external static IconData get radio_button_off; + external static IconData get radio_button_on; + external static IconData get radio_button_unchecked; + external static IconData get railway_alert; + external static IconData get ramen_dining; + external static IconData get ramp_left; + external static IconData get ramp_right; + external static IconData get rate_review; + external static IconData get raw_off; + external static IconData get raw_on; + external static IconData get read_more; + external static IconData get real_estate_agent; + external static IconData get rebase_edit; + external static IconData get receipt; + external static IconData get receipt_long; + external static IconData get recent_actors; + external static IconData get recommend; + external static IconData get record_voice_over; + external static IconData get rectangle; + external static IconData get recycling; + external static IconData get reddit; + external static IconData get redeem; + external static IconData get redo; + external static IconData get reduce_capacity; + external static IconData get remember_me; + external static IconData get remove_circle; + external static IconData get remove_done; + external static IconData get remove_from_queue; + external static IconData get remove_moderator; + external static IconData get remove_red_eye; + external static IconData get remove_road; + external static IconData get remove_shopping_cart; + external static IconData get reorder; + external static IconData get repartition; + external static IconData get repeat; + external static IconData get repeat_on; + external static IconData get repeat_one; + external static IconData get repeat_one_on; + external static IconData get replay_10; + external static IconData get replay_30; + external static IconData get replay_5; + external static IconData get replay_circle_filled; + external static IconData get reply; + external static IconData get report; + external static IconData get report_gmailerrorred; + external static IconData get report_off; + external static IconData get report_problem; + external static IconData get request_page; + external static IconData get request_quote; + external static IconData get reset_tv; + external static IconData get restart_alt; + external static IconData get restaurant; + external static IconData get restore; + external static IconData get restore_from_trash; + external static IconData get restore_page; + external static IconData get reviews; + external static IconData get rice_bowl; + external static IconData get ring_volume; + external static IconData get rocket; + external static IconData get rocket_launch; + external static IconData get roller_shades; + external static IconData get roller_shades_closed; + external static IconData get roller_skating; + external static IconData get roofing; + external static IconData get room; + external static IconData get room_preferences; + external static IconData get room_service; + external static IconData get rotate_90_degrees_ccw; + external static IconData get rotate_90_degrees_cw; + external static IconData get rotate_left; + external static IconData get rotate_right; + external static IconData get roundabout_left; + external static IconData get roundabout_right; + external static IconData get rounded_corner; + external static IconData get route; + external static IconData get router; + external static IconData get rowing; + external static IconData get rss_feed; + external static IconData get rsvp; + external static IconData get rtt; + external static IconData get rule; + external static IconData get rule_folder; + external static IconData get run_circle; + external static IconData get running_with_errors; + external static IconData get rv_hookup; + external static IconData get safety_check; + external static IconData get safety_divider; + external static IconData get sailing; + external static IconData get sanitizer; + external static IconData get satellite; + external static IconData get satellite_alt; + external static IconData get save; + external static IconData get save_alt; + external static IconData get save_as; + external static IconData get saved_search; + external static IconData get savings; + external static IconData get scale; + external static IconData get scanner; + external static IconData get scatter_plot; + external static IconData get schedule; + external static IconData get schedule_send; + external static IconData get schema; + external static IconData get school; + external static IconData get science; + external static IconData get score; + external static IconData get scoreboard; + external static IconData get screen_lock_landscape; + external static IconData get screen_lock_portrait; + external static IconData get screen_lock_rotation; + external static IconData get screen_rotation; + external static IconData get screen_rotation_alt; + external static IconData get screen_search_desktop; + external static IconData get screen_share; + external static IconData get screenshot; + external static IconData get screenshot_monitor; + external static IconData get scuba_diving; + external static IconData get sd; + external static IconData get sd_card; + external static IconData get sd_card_alert; + external static IconData get sd_storage; + external static IconData get search_off; + external static IconData get security_update; + external static IconData get security_update_good; + external static IconData get security_update_warning; + external static IconData get segment; + external static IconData get select_all; + external static IconData get self_improvement; + external static IconData get sell; + external static IconData get send; + external static IconData get send_and_archive; + external static IconData get send_time_extension; + external static IconData get send_to_mobile; + external static IconData get sensor_door; + external static IconData get sensor_occupied; + external static IconData get sensor_window; + external static IconData get sensors; + external static IconData get sensors_off; + external static IconData get sentiment_dissatisfied; + external static IconData get sentiment_neutral; + external static IconData get sentiment_satisfied; + external static IconData get sentiment_satisfied_alt; + external static IconData get sentiment_very_dissatisfied; + external static IconData get sentiment_very_satisfied; + external static IconData get set_meal; + external static IconData get settings_accessibility; + external static IconData get settings_applications; + external static IconData get settings_backup_restore; + external static IconData get settings_bluetooth; + external static IconData get settings_brightness; + external static IconData get settings_cell; + external static IconData get settings_display; + external static IconData get settings_ethernet; + external static IconData get settings_input_antenna; + external static IconData get settings_input_component; + external static IconData get settings_input_composite; + external static IconData get settings_input_hdmi; + external static IconData get settings_input_svideo; + external static IconData get settings_overscan; + external static IconData get settings_phone; + external static IconData get settings_power; + external static IconData get settings_remote; + external static IconData get settings_suggest; + external static IconData get settings_system_daydream; + external static IconData get settings_voice; + external static IconData get severe_cold; + external static IconData get shape_line; + external static IconData get share_arrival_time; + external static IconData get share_location; + external static IconData get shelves; + external static IconData get shield; + external static IconData get shield_moon; + external static IconData get shop; + external static IconData get shop_2; + external static IconData get shop_two; + external static IconData get shopify; + external static IconData get shopping_bag; + external static IconData get shopping_basket; + external static IconData get shopping_cart_checkout; + external static IconData get short_text; + external static IconData get shortcut; + external static IconData get show_chart; + external static IconData get shower; + external static IconData get shuffle; + external static IconData get shuffle_on; + external static IconData get shutter_speed; + external static IconData get sick; + external static IconData get sign_language; + external static IconData get signal_cellular_0_bar; + external static IconData get signal_cellular_4_bar; + external static IconData get signal_cellular_alt; + external static IconData get signal_cellular_alt_1_bar; + external static IconData get signal_cellular_alt_2_bar; + external static IconData get signal_cellular_connected_no_internet_0_bar; + external static IconData get signal_cellular_connected_no_internet_4_bar; + external static IconData get signal_cellular_no_sim; + external static IconData get signal_cellular_nodata; + external static IconData get signal_cellular_null; + external static IconData get signal_cellular_off; + external static IconData get signal_wifi_0_bar; + external static IconData get signal_wifi_4_bar; + external static IconData get signal_wifi_4_bar_lock; + external static IconData get signal_wifi_bad; + external static IconData get signal_wifi_connected_no_internet_4; + external static IconData get signal_wifi_off; + external static IconData get signal_wifi_statusbar_4_bar; + external static IconData get signal_wifi_statusbar_connected_no_internet_4; + external static IconData get signal_wifi_statusbar_null; + external static IconData get signpost; + external static IconData get sim_card; + external static IconData get sim_card_alert; + external static IconData get sim_card_download; + external static IconData get single_bed; + external static IconData get sip; + external static IconData get skateboarding; + external static IconData get skip_next; + external static IconData get skip_previous; + external static IconData get sledding; + external static IconData get slideshow; + external static IconData get slow_motion_video; + external static IconData get smart_button; + external static IconData get smart_display; + external static IconData get smart_screen; + external static IconData get smart_toy; + external static IconData get smartphone; + external static IconData get smoke_free; + external static IconData get smoking_rooms; + external static IconData get sms; + external static IconData get sms_failed; + external static IconData get snapchat; + external static IconData get snippet_folder; + external static IconData get snooze; + external static IconData get snowboarding; + external static IconData get snowing; + external static IconData get snowmobile; + external static IconData get snowshoeing; + external static IconData get soap; + external static IconData get social_distance; + external static IconData get solar_power; + external static IconData get sort_by_alpha; + external static IconData get sos; + external static IconData get soup_kitchen; + external static IconData get source; + external static IconData get south; + external static IconData get south_america; + external static IconData get south_east; + external static IconData get south_west; + external static IconData get spa; + external static IconData get space_bar; + external static IconData get space_dashboard; + external static IconData get spatial_audio; + external static IconData get spatial_audio_off; + external static IconData get spatial_tracking; + external static IconData get speaker; + external static IconData get speaker_group; + external static IconData get speaker_notes; + external static IconData get speaker_notes_off; + external static IconData get speaker_phone; + external static IconData get speed; + external static IconData get spellcheck; + external static IconData get splitscreen; + external static IconData get spoke; + external static IconData get sports; + external static IconData get sports_bar; + external static IconData get sports_baseball; + external static IconData get sports_basketball; + external static IconData get sports_cricket; + external static IconData get sports_esports; + external static IconData get sports_football; + external static IconData get sports_golf; + external static IconData get sports_gymnastics; + external static IconData get sports_handball; + external static IconData get sports_hockey; + external static IconData get sports_kabaddi; + external static IconData get sports_martial_arts; + external static IconData get sports_mma; + external static IconData get sports_motorsports; + external static IconData get sports_rugby; + external static IconData get sports_score; + external static IconData get sports_soccer; + external static IconData get sports_tennis; + external static IconData get sports_volleyball; + external static IconData get square; + external static IconData get square_foot; + external static IconData get ssid_chart; + external static IconData get stacked_bar_chart; + external static IconData get stacked_line_chart; + external static IconData get stadium; + external static IconData get stairs; + external static IconData get star_border_purple500; + external static IconData get star_half; + external static IconData get star_outline; + external static IconData get star_purple500; + external static IconData get star_rate; + external static IconData get stars; + external static IconData get start; + external static IconData get stay_current_landscape; + external static IconData get stay_current_portrait; + external static IconData get stay_primary_landscape; + external static IconData get stay_primary_portrait; + external static IconData get sticky_note_2; + external static IconData get stop; + external static IconData get stop_circle; + external static IconData get stop_screen_share; + external static IconData get storage; + external static IconData get store; + external static IconData get store_mall_directory; + external static IconData get storefront; + external static IconData get storm; + external static IconData get straight; + external static IconData get straighten; + external static IconData get stream; + external static IconData get streetview; + external static IconData get strikethrough_s; + external static IconData get stroller; + external static IconData get style; + external static IconData get subdirectory_arrow_left; + external static IconData get subdirectory_arrow_right; + external static IconData get subject; + external static IconData get subscript; + external static IconData get subscriptions; + external static IconData get subtitles; + external static IconData get subtitles_off; + external static IconData get subway; + external static IconData get summarize; + external static IconData get sunny; + external static IconData get sunny_snowing; + external static IconData get superscript; + external static IconData get supervised_user_circle; + external static IconData get supervisor_account; + external static IconData get support; + external static IconData get support_agent; + external static IconData get surfing; + external static IconData get surround_sound; + external static IconData get swap_calls; + external static IconData get swap_horiz; + external static IconData get swap_horizontal_circle; + external static IconData get swap_vert; + external static IconData get swap_vert_circle; + external static IconData get swap_vertical_circle; + external static IconData get swipe; + external static IconData get swipe_down; + external static IconData get swipe_down_alt; + external static IconData get swipe_left; + external static IconData get swipe_left_alt; + external static IconData get swipe_right; + external static IconData get swipe_right_alt; + external static IconData get swipe_up; + external static IconData get swipe_up_alt; + external static IconData get swipe_vertical; + external static IconData get switch_access_shortcut; + external static IconData get switch_access_shortcut_add; + external static IconData get switch_account; + external static IconData get switch_camera; + external static IconData get switch_left; + external static IconData get switch_right; + external static IconData get switch_video; + external static IconData get synagogue; + external static IconData get sync; + external static IconData get sync_alt; + external static IconData get sync_disabled; + external static IconData get sync_lock; + external static IconData get sync_problem; + external static IconData get system_security_update; + external static IconData get system_security_update_good; + external static IconData get system_security_update_warning; + external static IconData get system_update; + external static IconData get system_update_alt; + external static IconData get system_update_tv; + external static IconData get tab; + external static IconData get tab_unselected; + external static IconData get table_bar; + external static IconData get table_restaurant; + external static IconData get table_rows; + external static IconData get table_view; + external static IconData get tablet; + external static IconData get tablet_android; + external static IconData get tablet_mac; + external static IconData get tag; + external static IconData get tag_faces; + external static IconData get takeout_dining; + external static IconData get tap_and_play; + external static IconData get tapas; + external static IconData get task; + external static IconData get task_alt; + external static IconData get taxi_alert; + external static IconData get telegram; + external static IconData get temple_buddhist; + external static IconData get temple_hindu; + external static IconData get terminal; + external static IconData get terrain; + external static IconData get text_decrease; + external static IconData get text_fields; + external static IconData get text_format; + external static IconData get text_increase; + external static IconData get text_rotate_up; + external static IconData get text_rotate_vertical; + external static IconData get text_rotation_angledown; + external static IconData get text_rotation_angleup; + external static IconData get text_rotation_down; + external static IconData get text_rotation_none; + external static IconData get text_snippet; + external static IconData get textsms; + external static IconData get texture; + external static IconData get theater_comedy; + external static IconData get theaters; + external static IconData get thermostat; + external static IconData get thermostat_auto; + external static IconData get thumb_down; + external static IconData get thumb_down_alt; + external static IconData get thumb_down_off_alt; + external static IconData get thumb_up; + external static IconData get thumb_up_alt; + external static IconData get thumb_up_off_alt; + external static IconData get thumbs_up_down; + external static IconData get thunderstorm; + external static IconData get tiktok; + external static IconData get time_to_leave; + external static IconData get timelapse; + external static IconData get timeline; + external static IconData get timer; + external static IconData get timer_10; + external static IconData get timer_10_select; + external static IconData get timer_3; + external static IconData get timer_3_select; + external static IconData get timer_off; + external static IconData get tips_and_updates; + external static IconData get tire_repair; + external static IconData get title; + external static IconData get toc; + external static IconData get today; + external static IconData get toggle_off; + external static IconData get toggle_on; + external static IconData get token; + external static IconData get toll; + external static IconData get tonality; + external static IconData get topic; + external static IconData get tornado; + external static IconData get touch_app; + external static IconData get tour; + external static IconData get toys; + external static IconData get track_changes; + external static IconData get traffic; + external static IconData get train; + external static IconData get tram; + external static IconData get transcribe; + external static IconData get transfer_within_a_station; + external static IconData get transform; + external static IconData get transgender; + external static IconData get transit_enterexit; + external static IconData get translate; + external static IconData get travel_explore; + external static IconData get trending_down; + external static IconData get trending_flat; + external static IconData get trending_neutral; + external static IconData get trending_up; + external static IconData get trip_origin; + external static IconData get trolley; + external static IconData get troubleshoot; + external static IconData get try_; + external static IconData get tsunami; + external static IconData get tty; + external static IconData get tungsten; + external static IconData get turn_left; + external static IconData get turn_right; + external static IconData get turn_sharp_left; + external static IconData get turn_sharp_right; + external static IconData get turn_slight_left; + external static IconData get turn_slight_right; + external static IconData get turned_in; + external static IconData get turned_in_not; + external static IconData get tv; + external static IconData get tv_off; + external static IconData get two_wheeler; + external static IconData get type_specimen; + external static IconData get u_turn_left; + external static IconData get u_turn_right; + external static IconData get umbrella; + external static IconData get unarchive; + external static IconData get undo; + external static IconData get unfold_less; + external static IconData get unfold_less_double; + external static IconData get unfold_more; + external static IconData get unfold_more_double; + external static IconData get unpublished; + external static IconData get unsubscribe; + external static IconData get upcoming; + external static IconData get update; + external static IconData get update_disabled; + external static IconData get upgrade; + external static IconData get upload; + external static IconData get upload_file; + external static IconData get usb; + external static IconData get usb_off; + external static IconData get vaccines; + external static IconData get vape_free; + external static IconData get vaping_rooms; + external static IconData get verified; + external static IconData get verified_user; + external static IconData get vertical_align_bottom; + external static IconData get vertical_align_center; + external static IconData get vertical_align_top; + external static IconData get vertical_distribute; + external static IconData get vertical_shades; + external static IconData get vertical_shades_closed; + external static IconData get vibration; + external static IconData get video_call; + external static IconData get video_camera_back; + external static IconData get video_camera_front; + external static IconData get video_chat; + external static IconData get video_collection; + external static IconData get video_file; + external static IconData get video_label; + external static IconData get video_library; + external static IconData get video_settings; + external static IconData get video_stable; + external static IconData get videocam; + external static IconData get videocam_off; + external static IconData get videogame_asset; + external static IconData get videogame_asset_off; + external static IconData get view_agenda; + external static IconData get view_array; + external static IconData get view_carousel; + external static IconData get view_column; + external static IconData get view_comfortable; + external static IconData get view_comfy; + external static IconData get view_comfy_alt; + external static IconData get view_compact; + external static IconData get view_compact_alt; + external static IconData get view_cozy; + external static IconData get view_day; + external static IconData get view_headline; + external static IconData get view_in_ar; + external static IconData get view_kanban; + external static IconData get view_list; + external static IconData get view_module; + external static IconData get view_quilt; + external static IconData get view_sidebar; + external static IconData get view_stream; + external static IconData get view_timeline; + external static IconData get view_week; + external static IconData get vignette; + external static IconData get villa; + external static IconData get voice_chat; + external static IconData get voice_over_off; + external static IconData get voicemail; + external static IconData get volcano; + external static IconData get volume_down; + external static IconData get volume_down_alt; + external static IconData get volume_mute; + external static IconData get volume_off; + external static IconData get volume_up; + external static IconData get volunteer_activism; + external static IconData get vpn_key; + external static IconData get vpn_key_off; + external static IconData get vpn_lock; + external static IconData get vrpano; + external static IconData get wallet; + external static IconData get wallet_giftcard; + external static IconData get wallet_membership; + external static IconData get wallet_travel; + external static IconData get wallpaper; + external static IconData get warehouse; + external static IconData get warning; + external static IconData get warning_amber; + external static IconData get wash; + external static IconData get watch; + external static IconData get watch_later; + external static IconData get watch_off; + external static IconData get water; + external static IconData get water_damage; + external static IconData get water_drop; + external static IconData get waterfall_chart; + external static IconData get waves; + external static IconData get waving_hand; + external static IconData get wb_auto; + external static IconData get wb_cloudy; + external static IconData get wb_incandescent; + external static IconData get wb_iridescent; + external static IconData get wb_shade; + external static IconData get wb_sunny; + external static IconData get wb_twighlight; + external static IconData get wb_twilight; + external static IconData get wc; + external static IconData get web; + external static IconData get web_asset_off; + external static IconData get web_stories; + external static IconData get webhook; + external static IconData get wechat; + external static IconData get weekend; + external static IconData get west; + external static IconData get whatshot; + external static IconData get wheelchair_pickup; + external static IconData get where_to_vote; + external static IconData get widgets; + external static IconData get width_full; + external static IconData get width_normal; + external static IconData get width_wide; + external static IconData get wifi; + external static IconData get wifi_1_bar; + external static IconData get wifi_2_bar; + external static IconData get wifi_calling; + external static IconData get wifi_calling_3; + external static IconData get wifi_channel; + external static IconData get wifi_find; + external static IconData get wifi_lock; + external static IconData get wifi_off; + external static IconData get wifi_password; + external static IconData get wifi_protected_setup; + external static IconData get wifi_tethering; + external static IconData get wifi_tethering_error; + external static IconData get wifi_tethering_error_rounded; + external static IconData get wifi_tethering_off; + external static IconData get wind_power; + external static IconData get window; + external static IconData get wine_bar; + external static IconData get woman; + external static IconData get woman_2; + external static IconData get woo_commerce; + external static IconData get wordpress; + external static IconData get work; + external static IconData get work_history; + external static IconData get work_off; + external static IconData get work_outline; + external static IconData get workspace_premium; + external static IconData get workspaces; + external static IconData get workspaces_filled; + external static IconData get workspaces_outline; + external static IconData get wrap_text; + external static IconData get wrong_location; + external static IconData get wysiwyg; + external static IconData get yard; + external static IconData get youtube_searched_for; + external static IconData get zoom_in; + external static IconData get zoom_in_map; + external static IconData get zoom_out; + external static IconData get zoom_out_map; external static IconData get access_alarm; external static IconData get access_time; external static IconData get account_circle; @@ -349,6 +2438,10 @@ class Expanded extends Widget { @JavaName('com.codename1.flutter.material.MaterialApp') class MaterialApp extends Widget { external MaterialApp({Key? key, String? title, ThemeData? theme, ThemeData? darkTheme, ThemeMode? themeMode, Widget? home, Route Function(RouteSettings)? onGenerateRoute}); + // go_router's MaterialApp.router(routerConfig: GoRouter(...)). + external static MaterialApp router({Key? key, Object? routerConfig, String? title, ThemeData? theme, + ThemeData? darkTheme, ThemeMode? themeMode, bool? debugShowCheckedModeBanner, + dynamic localizationsDelegates, dynamic supportedLocales, Locale? locale}); } @JavaName('com.codename1.flutter.material.Scaffold') @@ -359,7 +2452,11 @@ class Scaffold extends Widget { @JavaName('com.codename1.flutter.material.AppBar') class AppBar extends Widget { - external AppBar({Key? key, Widget? title, Color? backgroundColor, bool? centerTitle}); + external AppBar({Key? key, Widget? title, Color? backgroundColor, bool? centerTitle, + List? actions, Widget? leading, bool? automaticallyImplyLeading, Widget? bottom, + double? elevation, SystemUiOverlayStyle? systemOverlayStyle, double? titleSpacing, + double? toolbarHeight, IconThemeData? iconTheme, Color? foregroundColor, + Widget? flexibleSpace, bool? primary, TextStyle? titleTextStyle}); // The height this app bar prefers to occupy — Flutter's // `AppBar.preferredSize` (an app bar implements PreferredSizeWidget). external Size get preferredSize; @@ -609,6 +2706,13 @@ class TextButton extends Widget { external static TextButton icon({Key? key, VoidCallback? onPressed, ButtonStyle? style, Widget? icon, Widget? label}); } +@JavaName('com.codename1.flutter.material.FilledButton') +class FilledButton extends ElevatedButton { + external FilledButton({Key? key, VoidCallback? onPressed, ButtonStyle? style, Widget? child}); + external static FilledButton tonal({Key? key, VoidCallback? onPressed, ButtonStyle? style, Widget? child}); + external static FilledButton icon({Key? key, VoidCallback? onPressed, ButtonStyle? style, Widget? icon, Widget? label}); +} + @JavaName('com.codename1.flutter.material.OutlinedButton') class OutlinedButton extends Widget { external OutlinedButton({Key? key, VoidCallback? onPressed, ButtonStyle? style, Widget? child}); @@ -713,7 +2817,7 @@ class Navigator extends Widget { } @JavaName('com.codename1.flutter.material.Dialogs.showDialog') -external Future showDialog({BuildContext context, WidgetBuilder builder}); +external Future showDialog({BuildContext context, WidgetBuilder builder}); @JavaName('com.codename1.flutter.material.AlertDialog') class AlertDialog extends Widget { diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_dartCore.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_dartCore.dart index 1289dccc4ee..3da04d96a41 100644 --- a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_dartCore.dart +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_dartCore.dart @@ -58,6 +58,11 @@ class DateTime { external static DateTime utc(int year, [int month = 1, int day = 1, int hour = 0, int minute = 0, int second = 0, int millisecond = 0, int microsecond = 0]); external static DateTime fromMillisecondsSinceEpoch(int millisecondsSinceEpoch, {bool isUtc}); external static DateTime fromMicrosecondsSinceEpoch(int microsecondsSinceEpoch, {bool isUtc}); + external static DateTime parse(String formattedString); + external static DateTime? tryParse(String formattedString); + external String toIso8601String(); + external bool get isUtc; + external int get microsecond; external int get year; external int get month; external int get day; @@ -174,6 +179,12 @@ class Uint8List { external Uint8List(int length); external static Uint8List fromList(List elements); external int get length; + external int operator [](int index); + external void operator []=(int index, int value); + external List toList(); + external Uint8List sublist(int start, [int? end]); + external bool get isEmpty; + external bool get isNotEmpty; } @JavaName('dart.typed_data.ByteData') diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p2_widgetsMore.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p2_widgetsMore.dart index 76125d42182..4ecdfb04496 100644 --- a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p2_widgetsMore.dart +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p2_widgetsMore.dart @@ -184,7 +184,7 @@ enum DismissDirection { vertical, horizontal, endToStart, startToEnd, up, down, @JavaName('com.codename1.flutter.widgets.Dismissible') class Dismissible extends Widget { - external Dismissible({required Key key, required Widget child, Widget? background, Widget? secondaryBackground, Object? confirmDismiss, VoidCallback? onResize, Object? onUpdate, Object? onDismissed, Object? direction, Object? resizeDuration, Object? dismissThresholds, Object? movementDuration, double? crossAxisEndOffset, Object? dragStartBehavior, Object? behavior}); + external Dismissible({required Key key, required Widget child, Widget? background, Widget? secondaryBackground, Future Function(DismissDirection direction)? confirmDismiss, VoidCallback? onResize, Object? onUpdate, void Function(DismissDirection direction)? onDismissed, Object? direction, Object? resizeDuration, Object? dismissThresholds, Object? movementDuration, double? crossAxisEndOffset, Object? dragStartBehavior, Object? behavior}); } // ====================================================================== diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p3_identifiersEnums.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p3_identifiersEnums.dart index 4956cc02c5b..f38b2fc60ac 100644 --- a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p3_identifiersEnums.dart +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p3_identifiersEnums.dart @@ -95,3 +95,7 @@ class Localizations { external static T of(BuildContext context, Object type); external static Locale localeOf(BuildContext context); } + +// Flutter's `debugPrint`, the logging call apps use instead of print. +@JavaName('com.codename1.flutter.foundation.FoundationLib.debugPrint') +external void debugPrint(String? message, {int? wrapWidth}); diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p7.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p7.dart index 56e05ec3d6f..47777dd7f6f 100644 --- a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p7.dart +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_p7.dart @@ -467,7 +467,15 @@ class NotificationListener extends Widget { // Intercepts the system back gesture — Flutter's `WillPopScope`. @JavaName('com.codename1.flutter.widgets.WillPopScope') class WillPopScope extends Widget { - external WillPopScope({Key? key, Object onWillPop, Widget child}); + external WillPopScope({Key? key, Future Function()? onWillPop, Widget child}); +} + +// canPop: false vetoes a back press or maybePop on its route; onPopInvoked hears each try. +@JavaName('com.codename1.flutter.widgets.PopScope') +class PopScope extends Widget { + external PopScope({Key? key, required Widget child, bool canPop, + void Function(bool didPop)? onPopInvoked, + void Function(bool didPop, T? result)? onPopInvokedWithResult}); } // ====================================================================== @@ -600,6 +608,13 @@ class FutureBuilder extends Widget { external FutureBuilder({Key? key, Object? future, T? initialData, Object builder}); } +// Builds itself from the latest event of a Stream -- Flutter's `StreamBuilder`. +@JavaName('com.codename1.flutter.widgets.StreamBuilder') +class StreamBuilder extends Widget { + external StreamBuilder({Key? key, Stream? stream, T? initialData, + required Widget Function(BuildContext context, AsyncSnapshot snapshot) builder}); +} + // The connection state of an async computation feeding an AsyncSnapshot — // Flutter's `ConnectionState`. @JavaName('com.codename1.flutter.widgets.ConnectionState') @@ -814,11 +829,26 @@ abstract class AutofillHints { // The glue binding the widget layer to the engine — Flutter's `WidgetsBinding`. // new_gallery reaches the ambient brightness through // `WidgetsBinding.instance.platformDispatcher.platformBrightness`; the dispatcher -// is left `dynamic` so that chain resolves without pulling in the engine types. +// is typed (PlatformDispatcher below): left `dynamic`, the chain went through +// Dyn.get, and the runtime's dispatcher is a plain Java object with no dynamic +// members -- the native gallery threw NoSuchMethodError before its first frame. +// `WidgetsFlutterBinding.ensureInitialized()` -- main()'s usual first line; the +// binding is Codename One's Display, initialized before main() runs. +@JavaName('com.codename1.flutter.widgets.WidgetsBinding') +class WidgetsFlutterBinding extends WidgetsBinding { + external static WidgetsBinding ensureInitialized(); +} + +@JavaName('com.codename1.flutter.widgets.WidgetsBinding.PlatformDispatcher') +class PlatformDispatcher { + external Brightness get platformBrightness; + external double get devicePixelRatio; +} + @JavaName('com.codename1.flutter.widgets.WidgetsBinding') class WidgetsBinding { external static WidgetsBinding get instance; - external dynamic get platformDispatcher; + external PlatformDispatcher get platformDispatcher; external Object get window; external void addPostFrameCallback(Object callback); external void addObserver(Object observer); diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_stateMgmt.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_stateMgmt.dart index 9b8a16522b9..6ab069d47af 100644 --- a/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_stateMgmt.dart +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/gallery_stateMgmt.dart @@ -50,7 +50,7 @@ class SingleChildWidget extends Widget { @JavaName('com.codename1.flutter.provider.Provider') class Provider extends SingleChildWidget { - external Provider({Key? key, dynamic create, Object? value, bool? lazy, Widget? child}); + external Provider({Key? key, dynamic create, Object? value, bool? lazy, dynamic dispose, Widget? child}); // named constructors are declared as static factory methods for the emitter external static Provider value({Key? key, Object value, Widget? child}); external static T of(BuildContext context, {bool listen}); diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_app_links.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_app_links.dart new file mode 100644 index 00000000000..06d457e5fe5 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_app_links.dart @@ -0,0 +1,13 @@ +// package:app_links, over the AppArg property the platform sets when a link +// opens the app. The generated entry point forwards each start to it. + +@JavaName('com.codename1.flutter.plugins.applinks.AppLinks') +class AppLinks { + external AppLinks(); + external Future getInitialLink(); + external Future getInitialLinkString(); + external Future getLatestLink(); + external Future getLatestLinkString(); + external Stream get stringLinkStream; + external Stream get uriLinkStream; +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_bloc.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_bloc.dart new file mode 100644 index 00000000000..21d8553d58e --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_bloc.dart @@ -0,0 +1,126 @@ +// package:bloc and package:flutter_bloc, over com.codename1.flutter.plugins.bloc. +// +// Not transpiled from pub: flutter_bloc reads blocs by type (`context.read()`, +// `BlocBuilder`), and the type arguments that carries are erased in Java. +// Here they arrive as class tokens instead -- `providedType` on the widgets and +// the `witness` parameter of `on` -- and a bloc is a ChangeNotifier, so the +// provider lookup and rebuild machinery the runtime already has does the rest. + +@JavaName('com.codename1.flutter.plugins.bloc.Change') +class Change { + external Change({S currentState, S nextState}); + external S get currentState; + external S get nextState; +} + +@JavaName('com.codename1.flutter.plugins.bloc.Transition') +class Transition extends Change { + external Transition({S currentState, E event, S nextState}); + external E get event; +} + +@JavaName('com.codename1.flutter.plugins.bloc.BlocBase') +abstract class BlocBase extends ChangeNotifier { + external BlocBase(S initialState); + external S get state; + external Stream get stream; + external bool get isClosed; + external void emit(S state); + external void onChange(Change change); + external void addError(Object error, [Object? stackTrace]); + external void onError(Object error, Object? stackTrace); + external Future close(); +} + +@JavaName('com.codename1.flutter.plugins.bloc.Cubit') +abstract class Cubit extends BlocBase { + external Cubit(S initialState); +} + +@JavaName('com.codename1.flutter.plugins.bloc.Emitter') +class Emitter { + external void call(S state); + external bool get isDone; + external Future forEach(Stream stream, {required S Function(T data) onData, Object? onError}); + external Future onEach(Stream stream, {required void Function(T data) onData, Object? onError}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.Bloc') +abstract class Bloc extends BlocBase { + external Bloc(S initialState); + external static BlocObserver get observer; + external static set observer(BlocObserver value); + external void add(E event); + external void onEvent(E event); + external void onTransition(Transition transition); + // `witness` is filled in by the transpiler with the class of the call's . + // Typed, so a handler's parameters are (event, Emitter) rather than dynamic + // -- `emit.forEach(...)` then binds statically -- and a block body that returns + // nothing still fits the value-returning Java handler type. + external void on(dynamic Function(T event, Emitter emit) handler, [Type? witness]); +} + +@JavaName('com.codename1.flutter.plugins.bloc.BlocObserver') +class BlocObserver { + external BlocObserver(); + external void onCreate(BlocBase bloc); + external void onEvent(Bloc bloc, Object? event); + external void onChange(BlocBase bloc, Change change); + external void onTransition(Bloc bloc, Transition transition); + external void onError(BlocBase bloc, Object error, Object? stackTrace); + external void onClose(BlocBase bloc); +} + +@JavaName('com.codename1.flutter.plugins.bloc.BlocProvider') +class BlocProvider extends ChangeNotifierProvider { + external BlocProvider({Key? key, dynamic create, bool? lazy, Widget? child}); + external static BlocProvider value({Key? key, Object value, Widget? child}); + external static T of(BuildContext context, {bool listen}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.RepositoryProvider') +class RepositoryProvider extends Provider { + external RepositoryProvider({Key? key, dynamic create, bool? lazy, dynamic dispose, Widget? child}); + external static RepositoryProvider value({Key? key, Object value, Widget? child}); + external static T of(BuildContext context, {bool listen}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.MultiBlocProvider') +class MultiBlocProvider extends Widget { + external MultiBlocProvider({Key? key, List providers, Widget child}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.MultiBlocProvider') +class MultiRepositoryProvider extends Widget { + external MultiRepositoryProvider({Key? key, List providers, Widget child}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.BlocBuilder') +class BlocBuilder extends StatefulWidget { + external BlocBuilder({Key? key, B? bloc, Widget Function(BuildContext context, S state) builder, + bool Function(S previous, S current)? buildWhen}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.BlocSelector') +class BlocSelector extends StatefulWidget { + external BlocSelector({Key? key, B? bloc, T Function(S state) selector, + Widget Function(BuildContext context, T state) builder}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.BlocListener') +class BlocListener extends StatefulWidget { + external BlocListener({Key? key, B? bloc, void Function(BuildContext context, S state) listener, + bool Function(S previous, S current)? listenWhen, Widget? child}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.MultiBlocListener') +class MultiBlocListener extends StatelessWidget { + external MultiBlocListener({Key? key, List listeners, Widget child}); +} + +@JavaName('com.codename1.flutter.plugins.bloc.BlocConsumer') +class BlocConsumer extends StatefulWidget { + external BlocConsumer({Key? key, B? bloc, Widget Function(BuildContext context, S state) builder, + void Function(BuildContext context, S state) listener, + bool Function(S previous, S current)? buildWhen, bool Function(S previous, S current)? listenWhen}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_cached_network_image.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_cached_network_image.dart new file mode 100644 index 00000000000..a045338f613 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_cached_network_image.dart @@ -0,0 +1,16 @@ +// package:cached_network_image, over Image.network, which already caches what it +// downloads. + +@JavaName('com.codename1.flutter.plugins.cachednetworkimage.CachedNetworkImage') +class CachedNetworkImage extends Widget { + external CachedNetworkImage({Key? key, required String imageUrl, double? width, double? height, + BoxFit? fit, Widget Function(BuildContext context, String url)? placeholder, + Widget Function(BuildContext context, String url, Object error)? errorWidget, + Map? httpHeaders, int? memCacheWidth, int? memCacheHeight, + Duration? fadeInDuration, String? cacheKey}); +} + +@JavaName('com.codename1.flutter.NetworkImage') +class CachedNetworkImageProvider extends ImageProvider { + external CachedNetworkImageProvider(String url, {double? scale, Object? headers}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_connectivity_plus.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_connectivity_plus.dart new file mode 100644 index 00000000000..e3f7f492a78 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_connectivity_plus.dart @@ -0,0 +1,12 @@ +// package:connectivity_plus, over NetworkManager's network type and its change +// listener. Headless (tests) it answers [ConnectivityResult.none]. + +@JavaName('com.codename1.flutter.plugins.connectivity.ConnectivityResult') +enum ConnectivityResult { bluetooth, wifi, ethernet, mobile, none, vpn, other } + +@JavaName('com.codename1.flutter.plugins.connectivity.Connectivity') +class Connectivity { + external Connectivity(); + external Future> checkConnectivity(); + external Stream> get onConnectivityChanged; +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_cross_file.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_cross_file.dart new file mode 100644 index 00000000000..e4f800e88ce --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_cross_file.dart @@ -0,0 +1,16 @@ +// package:cross_file, the XFile that image_picker and share_plus hand around: +// a path, or bytes held in memory, over FileSystemStorage. + +@JavaName('com.codename1.flutter.plugins.crossfile.XFile') +class XFile { + external XFile(String path, {String? mimeType, String? name}); + external static XFile fromData(Uint8List bytes, {String? mimeType, String? name, int? length, + DateTime? lastModified, String? path}); + external String get path; + external String get name; + external String? get mimeType; + external Future readAsBytes(); + external Future readAsString(); + external Future length(); + external Future saveTo(String path); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_device_info_plus.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_device_info_plus.dart new file mode 100644 index 00000000000..28db8775fe3 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_device_info_plus.dart @@ -0,0 +1,42 @@ +// package:device_info_plus, from Display's platform and device properties. Fields +// the platform does not expose to Codename One read as "unknown". + +@JavaName('com.codename1.flutter.plugins.deviceinfo.BaseDeviceInfo') +class BaseDeviceInfo { + external Map get data; +} + +@JavaName('com.codename1.flutter.plugins.deviceinfo.AndroidBuildVersion') +class AndroidBuildVersion { + external String get release; + external int get sdkInt; +} + +@JavaName('com.codename1.flutter.plugins.deviceinfo.AndroidDeviceInfo') +class AndroidDeviceInfo extends BaseDeviceInfo { + external String get model; + external String get brand; + external String get manufacturer; + external String get device; + external String get id; + external bool get isPhysicalDevice; + external AndroidBuildVersion get version; +} + +@JavaName('com.codename1.flutter.plugins.deviceinfo.IosDeviceInfo') +class IosDeviceInfo extends BaseDeviceInfo { + external String get name; + external String get model; + external String get systemName; + external String get systemVersion; + external String? get identifierForVendor; + external bool get isPhysicalDevice; +} + +@JavaName('com.codename1.flutter.plugins.deviceinfo.DeviceInfoPlugin') +class DeviceInfoPlugin { + external DeviceInfoPlugin(); + external Future get androidInfo; + external Future get iosInfo; + external Future get deviceInfo; +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_dio.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_dio.dart new file mode 100644 index 00000000000..d10a1febed6 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_dio.dart @@ -0,0 +1,229 @@ +// package:dio, over com.codename1.flutter.plugins.dio and Codename One's +// ConnectionRequest: options, interceptors, JSON decoding, FormData uploads, +// cancellation. Not transpiled from pub: dio's IO adapter is dart:io sockets and +// isolates all the way down, and its dependency tree is most of pub's core. + +@JavaName('com.codename1.flutter.plugins.dio.ResponseType') +enum ResponseType { json, stream, plain, bytes } + +@JavaName('com.codename1.flutter.plugins.dio.DioExceptionType') +enum DioExceptionType { + connectionTimeout, sendTimeout, receiveTimeout, badCertificate, badResponse, cancel, connectionError, unknown +} + +@JavaName('com.codename1.flutter.plugins.dio.OptionsBase') +abstract class _DioOptionsBase { + external String? get method; + external set method(String? v); + external Map get headers; + external set headers(Map? v); + external Map get extra; + external set extra(Map? v); + external String? get contentType; + external set contentType(String? v); + external ResponseType? get responseType; + external set responseType(ResponseType? v); + external Duration? get sendTimeout; + external set sendTimeout(Duration? v); + external Duration? get receiveTimeout; + external set receiveTimeout(Duration? v); + external bool? get followRedirects; + external set followRedirects(bool? v); +} + +@JavaName('com.codename1.flutter.plugins.dio.BaseOptions') +class BaseOptions extends _DioOptionsBase { + external BaseOptions({String? method, String baseUrl, Map? queryParameters, + Duration? connectTimeout, Duration? receiveTimeout, Duration? sendTimeout, + Map? extra, Map? headers, ResponseType? responseType, + String? contentType, bool Function(int? status)? validateStatus, bool? followRedirects}); + external String get baseUrl; + external set baseUrl(String v); + external Duration? get connectTimeout; + external set connectTimeout(Duration? v); + external Map get queryParameters; + external set queryParameters(Map? v); +} + +@JavaName('com.codename1.flutter.plugins.dio.Options') +class Options extends _DioOptionsBase { + external Options({String? method, Duration? sendTimeout, Duration? receiveTimeout, + Map? extra, Map? headers, ResponseType? responseType, + String? contentType, bool Function(int? status)? validateStatus, bool? followRedirects}); +} + +@JavaName('com.codename1.flutter.plugins.dio.RequestOptions') +class RequestOptions extends _DioOptionsBase { + external RequestOptions({String path, String? method, String? baseUrl, Object? data, + Map? queryParameters, Map? headers, Map? extra, + String? contentType, ResponseType? responseType}); + external String get path; + external set path(String v); + external String get baseUrl; + external set baseUrl(String v); + external Map get queryParameters; + external set queryParameters(Map? v); + external dynamic get data; + external set data(dynamic v); + external Duration? get connectTimeout; + external set connectTimeout(Duration? v); + external CancelToken? get cancelToken; + external Uri get uri; +} + +@JavaName('com.codename1.flutter.plugins.dio.Headers') +class Headers { + external static String get contentTypeHeader; + external static String get jsonContentType; + external static String get formUrlEncodedContentType; + external static String get textPlainContentType; + external Headers(); + external Map> get map; + external List? operator [](String name); + external String? value(String name); + external void add(String name, String value); + external void set(String name, dynamic value); + external void removeAll(String name); + external void forEach(void Function(String name, List values) f); +} + +@JavaName('com.codename1.flutter.plugins.dio.ResponseBody') +class ResponseBody { + external Stream get stream; + external int? get statusCode; + external int get contentLength; +} + +@JavaName('com.codename1.flutter.plugins.dio.Response') +class Response { + external Response({required RequestOptions requestOptions, T? data, int? statusCode, + String? statusMessage, Headers? headers}); + external T? get data; + external set data(T? v); + external int? get statusCode; + external set statusCode(int? v); + external String? get statusMessage; + external Headers get headers; + external RequestOptions get requestOptions; + external Map get extra; + external Uri get realUri; +} + +@JavaName('com.codename1.flutter.plugins.dio.DioException') +class DioException implements Exception { + external DioException({required RequestOptions requestOptions, Response? response, + DioExceptionType type, Object? error, String? message, StackTrace? stackTrace}); + external RequestOptions get requestOptions; + external Response? get response; + external DioExceptionType get type; + external Object? get error; + external String? get message; +} + +// The pre-5.0 name, still common in app code; the same class, so `on DioError` +// catches what dio throws. +@JavaName('com.codename1.flutter.plugins.dio.DioException') +class DioError extends DioException { + external DioError({required RequestOptions requestOptions, Response? response, + DioExceptionType type, Object? error, String? message}); +} + +@JavaName('com.codename1.flutter.plugins.dio.CancelToken') +class CancelToken { + external CancelToken(); + external bool get isCancelled; + external void cancel([Object? reason]); +} + +@JavaName('com.codename1.flutter.plugins.dio.RequestInterceptorHandler') +class RequestInterceptorHandler { + external void next(RequestOptions requestOptions); + external void resolve(Response response, [bool callFollowingResponseInterceptor = false]); + external void reject(DioException error, [bool callFollowingErrorInterceptor = false]); +} + +@JavaName('com.codename1.flutter.plugins.dio.ResponseInterceptorHandler') +class ResponseInterceptorHandler { + external void next(Response response); + external void resolve(Response response); + external void reject(DioException error, [bool callFollowingErrorInterceptor = false]); +} + +@JavaName('com.codename1.flutter.plugins.dio.ErrorInterceptorHandler') +class ErrorInterceptorHandler { + external void next(DioException error); + external void resolve(Response response); + external void reject(DioException error); +} + +@JavaName('com.codename1.flutter.plugins.dio.Interceptor') +class Interceptor { + external Interceptor(); + external void onRequest(RequestOptions options, RequestInterceptorHandler handler); + external void onResponse(Response response, ResponseInterceptorHandler handler); + external void onError(DioException err, ErrorInterceptorHandler handler); +} + +@JavaName('com.codename1.flutter.plugins.dio.InterceptorsWrapper') +class InterceptorsWrapper extends Interceptor { + external InterceptorsWrapper({ + void Function(RequestOptions options, RequestInterceptorHandler handler)? onRequest, + void Function(Response response, ResponseInterceptorHandler handler)? onResponse, + void Function(DioException error, ErrorInterceptorHandler handler)? onError}); +} + +@JavaName('com.codename1.flutter.plugins.dio.LogInterceptor') +class LogInterceptor extends Interceptor { + external LogInterceptor({bool request, bool requestHeader, bool requestBody, bool responseHeader, + bool responseBody, bool error, void Function(Object object)? logPrint}); +} + +@JavaName('com.codename1.flutter.plugins.dio.Interceptors') +class Interceptors { + external void add(Interceptor interceptor); + external void addAll(Iterable interceptors); + external void clear(); + external bool remove(Object? interceptor); + external int get length; +} + +@JavaName('com.codename1.flutter.plugins.dio.MultipartFile') +class MultipartFile { + external static Future fromFile(String filePath, {String? filename, Object? contentType, Object? headers}); + external static MultipartFile fromFileSync(String filePath, {String? filename, Object? contentType, Object? headers}); + external static MultipartFile fromBytes(List value, {String? filename, Object? contentType, Object? headers}); + external static MultipartFile fromString(String value, {String? filename, Object? contentType, Object? headers}); + external String? get filename; + external int get length; +} + +@JavaName('com.codename1.flutter.plugins.dio.FormData') +class FormData { + external FormData(); + external static FormData fromMap(Map map, [Object? collectionFormat, bool camelCaseContentDisposition = false]); + external int get length; +} + +@JavaName('com.codename1.flutter.plugins.dio.Dio') +class Dio { + external Dio([BaseOptions? options]); + external BaseOptions get options; + external set options(BaseOptions v); + external Interceptors get interceptors; + external void close({bool force}); + external Future> get(String path, {Object? data, Map? queryParameters, + Options? options, CancelToken? cancelToken, Object? onReceiveProgress}); + external Future> post(String path, {Object? data, Map? queryParameters, + Options? options, CancelToken? cancelToken, Object? onSendProgress, Object? onReceiveProgress}); + external Future> put(String path, {Object? data, Map? queryParameters, + Options? options, CancelToken? cancelToken, Object? onSendProgress, Object? onReceiveProgress}); + external Future> patch(String path, {Object? data, Map? queryParameters, + Options? options, CancelToken? cancelToken, Object? onSendProgress, Object? onReceiveProgress}); + external Future> delete(String path, {Object? data, Map? queryParameters, + Options? options, CancelToken? cancelToken}); + external Future> head(String path, {Object? data, Map? queryParameters, + Options? options, CancelToken? cancelToken}); + external Future> request(String path, {Object? data, Map? queryParameters, + CancelToken? cancelToken, Options? options, Object? onSendProgress, Object? onReceiveProgress}); + external Future> fetch(RequestOptions requestOptions); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_equatable.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_equatable.dart new file mode 100644 index 00000000000..cd93f8abc23 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_equatable.dart @@ -0,0 +1,9 @@ +// package:equatable, over com.codename1.flutter.plugins.equatable: value equality +// from `props`, with collections compared by content. + +@JavaName('com.codename1.flutter.plugins.equatable.Equatable') +abstract class Equatable { + external const Equatable(); + List get props; + external bool? get stringify; +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_firebase.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_firebase.dart new file mode 100644 index 00000000000..83fc59303c7 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_firebase.dart @@ -0,0 +1,146 @@ +// package:firebase_core, firebase_auth, firebase_analytics and firebase_messaging. +// Auth talks to the Identity Toolkit REST API with the apiKey from +// Firebase.initializeApp (email/password and anonymous accounts). Analytics logs, +// and forwards screen views to AnalyticsService. Messaging reads the device push key +// and delivers foreground pushes through onMessage. + +@JavaName('com.codename1.flutter.plugins.firebase.FirebaseOptions') +class FirebaseOptions { + external FirebaseOptions({required String apiKey, required String appId, required String messagingSenderId, + required String projectId, String? authDomain, String? databaseURL, String? storageBucket, + String? measurementId, String? trackingId, String? deepLinkURLScheme, String? androidClientId, + String? iosClientId, String? iosBundleId, String? appGroupId}); + external String get apiKey; + external String get appId; + external String get messagingSenderId; + external String get projectId; + external String? get authDomain; + external String? get storageBucket; +} + +@JavaName('com.codename1.flutter.plugins.firebase.FirebaseApp') +class FirebaseApp { + external String get name; + external FirebaseOptions get options; + external bool get isAutomaticDataCollectionEnabled; +} + +@JavaName('com.codename1.flutter.plugins.firebase.Firebase') +class Firebase { + external static List get apps; + external static Future initializeApp({String? name, FirebaseOptions? options}); + external static FirebaseApp app([String name]); +} + +@JavaName('com.codename1.flutter.plugins.firebase.FirebaseException') +class FirebaseException implements Exception { + external String get plugin; + external String get code; + external String? get message; +} + +@JavaName('com.codename1.flutter.plugins.firebase.FirebaseAuthException') +class FirebaseAuthException extends FirebaseException { +} + +@JavaName('com.codename1.flutter.plugins.firebase.User') +class User { + external String get uid; + external String? get email; + external String? get displayName; + external String? get photoURL; + external String? get phoneNumber; + external bool get isAnonymous; + external bool get emailVerified; + external String? get refreshToken; + external Future getIdToken([bool forceRefresh]); + external Future updateDisplayName(String? displayName); + external Future updatePhotoURL(String? photoURL); + external Future reload(); + external Future delete(); + external Future sendEmailVerification([Object? actionCodeSettings]); +} + +@JavaName('com.codename1.flutter.plugins.firebase.UserCredential') +class UserCredential { + external User? get user; + external Object? get additionalUserInfo; + external Object? get credential; +} + +@JavaName('com.codename1.flutter.plugins.firebase.FirebaseAuth') +class FirebaseAuth { + external static FirebaseAuth get instance; + external static FirebaseAuth instanceFor({required FirebaseApp app, Object? persistence}); + external User? get currentUser; + external Stream authStateChanges(); + external Stream idTokenChanges(); + external Stream userChanges(); + external Future signInWithEmailAndPassword({required String email, required String password}); + external Future createUserWithEmailAndPassword({required String email, + required String password}); + external Future signInAnonymously(); + external Future signInWithCredential(Object credential); + external Future signInWithProvider(Object provider); + external Future signOut(); + external Future sendPasswordResetEmail({required String email, Object? actionCodeSettings}); + external Future setLanguageCode(String? languageCode); +} + +@JavaName('com.codename1.flutter.plugins.firebase.FirebaseAnalytics') +class FirebaseAnalytics { + external static FirebaseAnalytics get instance; + external Future logEvent({required String name, Map? parameters, Object? callOptions}); + external Future logScreenView({String? screenClass, String? screenName, Map? parameters, + Object? callOptions}); + external Future logLogin({String? loginMethod, Map? parameters, Object? callOptions}); + external Future logSignUp({required String signUpMethod, Map? parameters, + Object? callOptions}); + external Future logAppOpen({Map? parameters, Object? callOptions}); + external Future setUserId({String? id, Object? callOptions}); + external Future setUserProperty({required String name, required String? value, Object? callOptions}); + external Future setAnalyticsCollectionEnabled(bool enabled); + external Future setCurrentScreen({required String? screenName, String screenClassOverride, + Object? callOptions}); +} + +@JavaName('com.codename1.flutter.plugins.firebase.AuthorizationStatus') +enum AuthorizationStatus { authorized, denied, notDetermined, provisional } + +@JavaName('com.codename1.flutter.plugins.firebase.NotificationSettings') +class NotificationSettings { + external AuthorizationStatus get authorizationStatus; +} + +@JavaName('com.codename1.flutter.plugins.firebase.RemoteNotification') +class RemoteNotification { + external String? get title; + external String? get body; +} + +@JavaName('com.codename1.flutter.plugins.firebase.RemoteMessage') +class RemoteMessage { + external String? get messageId; + external Map get data; + external RemoteNotification? get notification; +} + +@JavaName('com.codename1.flutter.plugins.firebase.FirebaseMessaging') +class FirebaseMessaging { + external static FirebaseMessaging get instance; + external static Stream get onMessage; + external static Stream get onMessageOpenedApp; + external static void onBackgroundMessage(Future Function(RemoteMessage message) handler); + external Future getToken({String? vapidKey}); + external Future getAPNSToken(); + external Stream get onTokenRefresh; + external Future deleteToken(); + external Future requestPermission({bool alert, bool announcement, bool badge, + bool carPlay, bool criticalAlert, bool provisional, bool sound}); + external Future getNotificationSettings(); + external Future getInitialMessage(); + external Future subscribeToTopic(String topic); + external Future unsubscribeFromTopic(String topic); + external Future setForegroundNotificationPresentationOptions({bool alert, bool badge, bool sound}); + external Future setAutoInitEnabled(bool enabled); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_flutter_local_notifications.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_flutter_local_notifications.dart new file mode 100644 index 00000000000..3b013dc7cfa --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_flutter_local_notifications.dart @@ -0,0 +1,137 @@ +// package:flutter_local_notifications, over Codename One's LocalNotification. +// Every settings and details class maps to one options holder; what the platform +// cannot express (channels, styles, LEDs) is accepted and dropped. A tap reaches +// onDidReceiveNotificationResponse through the generated entry point. + +@JavaName('com.codename1.flutter.plugins.notifications.Importance') +enum Importance { unspecified, none, min, low, defaultImportance, high, max } + +@JavaName('com.codename1.flutter.plugins.notifications.Priority') +enum Priority { min, low, defaultPriority, high, max } + +@JavaName('com.codename1.flutter.plugins.notifications.RepeatInterval') +enum RepeatInterval { everyMinute, hourly, daily, weekly } + +@JavaName('com.codename1.flutter.plugins.notifications.AndroidScheduleMode') +enum AndroidScheduleMode { alarmClock, exact, exactAllowWhileIdle, inexact, inexactAllowWhileIdle } + +@JavaName('com.codename1.flutter.plugins.notifications.DateTimeComponents') +enum DateTimeComponents { time, dayOfWeekAndTime, dayOfMonthAndTime, dateAndTime } + +@JavaName('com.codename1.flutter.plugins.notifications.UILocalNotificationDateInterpretation') +enum UILocalNotificationDateInterpretation { absoluteTime, wallClockTime } + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationResponseType') +enum NotificationResponseType { selectedNotification, selectedNotificationAction } + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationAppLaunchDetails') +class NotificationAppLaunchDetails { + external NotificationAppLaunchDetails(bool didNotificationLaunchApp, NotificationResponse? notificationResponse); + external bool get didNotificationLaunchApp; + external NotificationResponse? get notificationResponse; +} + +@JavaName('com.codename1.flutter.plugins.notifications.PendingNotificationRequest') +class PendingNotificationRequest { + external PendingNotificationRequest(int id, String? title, String? body, String? payload); + external int get id; + external String? get title; + external String? get body; + external String? get payload; +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationResponse') +class NotificationResponse { + external int? get id; + external String? get payload; + external String? get actionId; + external String? get input; + external NotificationResponseType get notificationResponseType; +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class InitializationSettings { + external InitializationSettings({Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class AndroidInitializationSettings { + external AndroidInitializationSettings(String defaultIcon, {Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class DarwinInitializationSettings { + external DarwinInitializationSettings({Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class LinuxInitializationSettings { + external LinuxInitializationSettings(String defaultActionName, {Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class NotificationDetails { + external NotificationDetails({Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class AndroidNotificationDetails { + external AndroidNotificationDetails(String channelId, String channelName, {Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class DarwinNotificationDetails { + external DarwinNotificationDetails({Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class LinuxNotificationDetails { + external LinuxNotificationDetails({Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.NotificationOptions') +class AndroidNotificationChannel { + external AndroidNotificationChannel(String id, String name, {Object? android, Object? iOS, Object? macOS, Object? linux, Object? windows, Object? importance, Object? priority, Object? channelDescription, Object? icon, Object? playSound, Object? enableVibration, Object? sound, Object? ticker, Object? styleInformation, Object? color, Object? largeIcon, Object? showWhen, Object? autoCancel, Object? ongoing, Object? category, Object? groupKey, Object? channelShowBadge, Object? visibility, Object? fullScreenIntent, Object? presentAlert, Object? presentBadge, Object? presentSound, Object? presentBanner, Object? presentList, Object? badgeNumber, Object? subtitle, Object? threadIdentifier, Object? attachments, Object? requestAlertPermission, Object? requestBadgePermission, Object? requestSoundPermission, Object? defaultPresentAlert, Object? defaultPresentBadge, Object? defaultPresentSound, Object? defaultPresentBanner, Object? defaultPresentList, Object? notificationCategories, Object? enableLights, Object? ledColor, Object? timeoutAfter, Object? onlyAlertOnce, Object? actions, Object? number, Object? tag}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.FlutterLocalNotificationsPlugin') +class FlutterLocalNotificationsPlugin { + external FlutterLocalNotificationsPlugin(); + external Future initialize(InitializationSettings initializationSettings, + {void Function(NotificationResponse details)? onDidReceiveNotificationResponse, + Object? onDidReceiveBackgroundNotificationResponse}); + external Future show(int id, String? title, String? body, NotificationDetails? notificationDetails, + {String? payload}); + external Future periodicallyShow(int id, String? title, String? body, RepeatInterval repeatInterval, + NotificationDetails notificationDetails, {AndroidScheduleMode androidScheduleMode, String? payload}); + external Future zonedSchedule(int id, String? title, String? body, Object scheduledDate, + NotificationDetails notificationDetails, {AndroidScheduleMode androidScheduleMode, String? payload, + DateTimeComponents? matchDateTimeComponents, + UILocalNotificationDateInterpretation? uiLocalNotificationDateInterpretation}); + external Future cancel(int id, {String? tag}); + external Future cancelAll(); + external Future getNotificationAppLaunchDetails(); + external Future> pendingNotificationRequests(); + external T? resolvePlatformSpecificImplementation([Type? witness]); +} + +// What resolvePlatformSpecificImplementation would return; it returns null here, +// so the usual `?.requestNotificationsPermission()` is skipped. +@JavaName('com.codename1.flutter.plugins.notifications.FlutterLocalNotificationsPlugin') +class AndroidFlutterLocalNotificationsPlugin { + external Future requestNotificationsPermission(); + external Future requestExactAlarmsPermission(); + external Future createNotificationChannel(AndroidNotificationChannel channel); +} + +@JavaName('com.codename1.flutter.plugins.notifications.FlutterLocalNotificationsPlugin') +class IOSFlutterLocalNotificationsPlugin { + external Future requestPermissions({bool alert, bool badge, bool sound, bool critical, + bool provisional}); +} + +@JavaName('com.codename1.flutter.plugins.notifications.FlutterLocalNotificationsPlugin') +class MacOSFlutterLocalNotificationsPlugin { + external Future requestPermissions({bool alert, bool badge, bool sound, bool critical, + bool provisional}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_geolocator.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_geolocator.dart new file mode 100644 index 00000000000..7c3c7fc3f1b --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_geolocator.dart @@ -0,0 +1,66 @@ +// package:geolocator, over LocationManager. Permission is the platform's own +// prompt, raised on first use; distanceBetween/bearingBetween are computed here. + +@JavaName('com.codename1.flutter.plugins.geolocator.LocationPermission') +enum LocationPermission { denied, deniedForever, whileInUse, always, unableToDetermine } + +@JavaName('com.codename1.flutter.plugins.geolocator.LocationAccuracy') +enum LocationAccuracy { lowest, low, medium, high, best, bestForNavigation, reduced } + +@JavaName('com.codename1.flutter.plugins.geolocator.LocationSettings') +class LocationSettings { + external LocationSettings({LocationAccuracy accuracy, int distanceFilter, Duration? timeLimit}); +} + +@JavaName('com.codename1.flutter.plugins.geolocator.LocationSettings') +class AndroidSettings extends LocationSettings { + external AndroidSettings({LocationAccuracy accuracy, int distanceFilter, Duration? timeLimit}); +} + +@JavaName('com.codename1.flutter.plugins.geolocator.LocationSettings') +class AppleSettings extends LocationSettings { + external AppleSettings({LocationAccuracy accuracy, int distanceFilter, Duration? timeLimit}); +} + +@JavaName('com.codename1.flutter.plugins.geolocator.Position') +class Position { + external double get latitude; + external double get longitude; + external DateTime get timestamp; + external double get accuracy; + external double get altitude; + external double get altitudeAccuracy; + external double get heading; + external double get headingAccuracy; + external double get speed; + external double get speedAccuracy; + external bool get isMocked; + external Map toJson(); +} + +@JavaName('com.codename1.flutter.plugins.geolocator.LocationServiceDisabledException') +class LocationServiceDisabledException implements Exception { + external LocationServiceDisabledException(); +} + +@JavaName('com.codename1.flutter.plugins.geolocator.PermissionDeniedException') +class PermissionDeniedException implements Exception { + external PermissionDeniedException(String? message); +} + +@JavaName('com.codename1.flutter.plugins.geolocator.Geolocator') +class Geolocator { + external static Future isLocationServiceEnabled(); + external static Future checkPermission(); + external static Future requestPermission(); + external static Future getCurrentPosition({LocationAccuracy desiredAccuracy, + bool forceAndroidLocationManager, Duration? timeLimit, LocationSettings? locationSettings}); + external static Future getLastKnownPosition({bool forceAndroidLocationManager}); + external static Stream getPositionStream({LocationSettings? locationSettings}); + external static Future openAppSettings(); + external static Future openLocationSettings(); + external static double distanceBetween(double startLatitude, double startLongitude, + double endLatitude, double endLongitude); + external static double bearingBetween(double startLatitude, double startLongitude, + double endLatitude, double endLongitude); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_go_router.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_go_router.dart new file mode 100644 index 00000000000..4c4cf6f85cd --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_go_router.dart @@ -0,0 +1,100 @@ +// package:go_router, over com.codename1.flutter.plugins.gorouter. +// +// Not transpiled from pub: go_router is built on the Router/Navigator 2.0 page API, +// which the runtime does not model. The stub matches locations against the route +// tree itself and pushes the matched page through the existing Navigator, and +// MaterialApp.router takes the router in place of a route table. + +@JavaName('com.codename1.flutter.plugins.gorouter.RouteBase') +abstract class RouteBase { + external List get routes; +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoRouterState') +class GoRouterState { + external Uri get uri; + external String get matchedLocation; + external String? get fullPath; + external String? get name; + external String? get path; + external Map get pathParameters; + external Map get queryParameters; + external Object? get extra; + external Exception? get error; + external static GoRouterState of(BuildContext context); +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoRoute') +class GoRoute extends RouteBase { + external GoRoute({required String path, String? name, + Widget Function(BuildContext context, GoRouterState state)? builder, + Page Function(BuildContext context, GoRouterState state)? pageBuilder, + Object? Function(BuildContext context, GoRouterState state)? redirect, + List routes, dynamic parentNavigatorKey}); +} + +@JavaName('com.codename1.flutter.plugins.gorouter.ShellRoute') +class ShellRoute extends RouteBase { + external ShellRoute({Widget Function(BuildContext context, GoRouterState state, Widget child)? builder, + List routes, dynamic navigatorKey}); +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoPage') +class Page { + external Key? get key; + external String? get name; + external Object? get arguments; +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoPage') +class MaterialPage extends Page { + external MaterialPage({Key? key, required Widget child, String? name, Object? arguments, + bool maintainState, bool fullscreenDialog}); + external Widget get child; +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoPage') +class NoTransitionPage extends Page { + external NoTransitionPage({Key? key, required Widget child, String? name, Object? arguments}); + external Widget get child; +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoPage') +class CustomTransitionPage extends Page { + external CustomTransitionPage({Key? key, required Widget child, String? name, Object? arguments, + dynamic transitionsBuilder, Duration? transitionDuration, bool maintainState, bool fullscreenDialog}); + external Widget get child; +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoRouter') +class GoRouter { + external GoRouter({required List routes, String? initialLocation, + Object? Function(BuildContext context, GoRouterState state)? redirect, + Widget Function(BuildContext context, GoRouterState state)? errorBuilder, + int redirectLimit, bool debugLogDiagnostics, dynamic navigatorKey, Listenable? refreshListenable}); + external static GoRouter of(BuildContext context); + external static GoRouter? maybeOf(BuildContext context); + external GoRouterState? get state; + external void go(String location, {Object? extra}); + external Future push(String location, {Object? extra}); + external void pushReplacement(String location, {Object? extra}); + external void replace(String location, {Object? extra}); + external void pop([T? result]); + external bool canPop(); + external void goNamed(String name, {Map pathParameters, Map queryParameters, Object? extra}); + external Future pushNamed(String name, {Map pathParameters, Map queryParameters, Object? extra}); + external String namedLocation(String name, {Map pathParameters, Map queryParameters}); +} + +@JavaName('com.codename1.flutter.plugins.gorouter.GoRouterHelper') +extension GoRouterHelper on BuildContext { + external void go(String location, {Object? extra}); + external Future push(String location, {Object? extra}); + external void pushReplacement(String location, {Object? extra}); + external void replace(String location, {Object? extra}); + external void pop([T? result]); + external bool canPop(); + external void goNamed(String name, {Map pathParameters, Map queryParameters, Object? extra}); + external Future pushNamed(String name, {Map pathParameters, Map queryParameters, Object? extra}); + external String namedLocation(String name, {Map pathParameters, Map queryParameters}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_google_maps_flutter.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_google_maps_flutter.dart new file mode 100644 index 00000000000..5bc80c7409b --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_google_maps_flutter.dart @@ -0,0 +1,100 @@ +// package:google_maps_flutter, hosting Codename One's MapView: native maps where +// the platform has them, vector tiles elsewhere. Markers, the camera, taps and +// camera moves work; polylines, polygons, circles and styles are accepted and not +// drawn, and markers keep the default pin. + +@JavaName('com.codename1.flutter.plugins.googlemaps.LatLng') +class LatLng { + external LatLng(double latitude, double longitude); + external double get latitude; + external double get longitude; +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.CameraPosition') +class CameraPosition { + external CameraPosition({required LatLng target, double zoom, double bearing, double tilt}); + external LatLng get target; + external double get zoom; + external double get bearing; + external double get tilt; +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.CameraUpdate') +class CameraUpdate { + external static CameraUpdate newLatLng(LatLng latLng); + external static CameraUpdate newLatLngZoom(LatLng latLng, double zoom); + external static CameraUpdate newCameraPosition(CameraPosition cameraPosition); + external static CameraUpdate zoomIn(); + external static CameraUpdate zoomOut(); + external static CameraUpdate zoomTo(double zoom); + external static CameraUpdate zoomBy(double amount, [Offset? focus]); +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.MarkerId') +class MarkerId { + external MarkerId(String value); + external String get value; +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.InfoWindow') +class InfoWindow { + external InfoWindow({String? title, String? snippet, void Function()? onTap}); + external String? get title; + external String? get snippet; +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.BitmapDescriptor') +class BitmapDescriptor { + external static BitmapDescriptor get defaultMarker; + external static BitmapDescriptor defaultMarkerWithHue(double hue); + external static double get hueRed; + external static double get hueOrange; + external static double get hueYellow; + external static double get hueGreen; + external static double get hueCyan; + external static double get hueAzure; + external static double get hueBlue; + external static double get hueViolet; + external static double get hueMagenta; + external static double get hueRose; +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.Marker') +class Marker { + external Marker({required MarkerId markerId, LatLng position, InfoWindow infoWindow, bool draggable, + bool visible, void Function()? onTap, BitmapDescriptor icon, double alpha, double rotation, + double zIndex, void Function(LatLng value)? onDragEnd}); + external MarkerId get markerId; + external LatLng get position; + external InfoWindow get infoWindow; + external bool get draggable; + external bool get visible; +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.MapType') +enum MapType { none, normal, satellite, terrain, hybrid } + +@JavaName('com.codename1.flutter.plugins.googlemaps.GoogleMapController') +class GoogleMapController { + external Future animateCamera(CameraUpdate cameraUpdate, {Duration? duration}); + external Future moveCamera(CameraUpdate cameraUpdate); + external Future getZoomLevel(); + external Future showMarkerInfoWindow(MarkerId markerId); + external Future hideMarkerInfoWindow(MarkerId markerId); + external Future setMapStyle(String? mapStyle); + external void dispose(); +} + +@JavaName('com.codename1.flutter.plugins.googlemaps.GoogleMap') +class GoogleMap extends Widget { + external GoogleMap({Key? key, required CameraPosition initialCameraPosition, Set markers, + MapType mapType, void Function(GoogleMapController controller)? onMapCreated, + void Function(LatLng argument)? onTap, void Function(LatLng argument)? onLongPress, + void Function(CameraPosition position)? onCameraMove, void Function()? onCameraIdle, + Object? polylines, Object? polygons, Object? circles, bool myLocationEnabled, + bool myLocationButtonEnabled, bool zoomControlsEnabled, bool zoomGesturesEnabled, + bool scrollGesturesEnabled, bool rotateGesturesEnabled, bool tiltGesturesEnabled, + bool compassEnabled, bool mapToolbarEnabled, bool trafficEnabled, bool buildingsEnabled, + bool liteModeEnabled, EdgeInsets padding, Object? minMaxZoomPreference, + Object? gestureRecognizers, String? style}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_image_picker.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_image_picker.dart new file mode 100644 index 00000000000..5e1e28a2606 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_image_picker.dart @@ -0,0 +1,23 @@ +// package:image_picker, over Capture (camera) and Display.openGallery. Resizing +// and quality arguments are accepted and not applied. + +@JavaName('com.codename1.flutter.plugins.imagepicker.ImageSource') +enum ImageSource { camera, gallery } + +@JavaName('com.codename1.flutter.plugins.imagepicker.CameraDevice') +enum CameraDevice { rear, front } + +@JavaName('com.codename1.flutter.plugins.imagepicker.ImagePicker') +class ImagePicker { + external ImagePicker(); + external Future pickImage({required ImageSource source, double? maxWidth, double? maxHeight, + int? imageQuality, CameraDevice preferredCameraDevice, bool requestFullMetadata}); + external Future pickVideo({required ImageSource source, CameraDevice preferredCameraDevice, + Duration? maxDuration}); + external Future pickMedia({double? maxWidth, double? maxHeight, int? imageQuality, + bool requestFullMetadata}); + external Future> pickMultiImage({double? maxWidth, double? maxHeight, int? imageQuality, + int? limit, bool requestFullMetadata}); + external Future> pickMultipleMedia({double? maxWidth, double? maxHeight, int? imageQuality, + int? limit, bool requestFullMetadata}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_json_annotation.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_json_annotation.dart new file mode 100644 index 00000000000..92024d7d5af --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_json_annotation.dart @@ -0,0 +1,27 @@ +// package:json_annotation: the annotations drive json_serializable, whose output +// (`*.g.dart`) an app commits, so only what that generated code calls at run time +// is backed here. + +@JavaName('com.codename1.flutter.plugins.json.JsonAnnotation.$enumDecode') +external K $enumDecode(Map enumValues, Object? source, {K? unknownValue}); + +@JavaName('com.codename1.flutter.plugins.json.JsonAnnotation.$enumDecodeNullable') +external K? $enumDecodeNullable(Map enumValues, Object? source, {K? unknownValue}); + +class JsonSerializable { + external const JsonSerializable({bool? explicitToJson, bool? includeIfNull, Object? fieldRename, + bool? createFactory, bool? createToJson, bool? checked, bool? anyMap, bool? disallowUnrecognizedKeys}); +} + +class JsonKey { + external const JsonKey({String? name, Object? defaultValue, bool? includeFromJson, bool? includeToJson, + bool? includeIfNull, bool? required, Object? fromJson, Object? toJson, Object? unknownEnumValue}); +} + +class JsonValue { + external const JsonValue(Object? value); +} + +class JsonEnum { + external const JsonEnum({bool? alwaysCreate, Object? fieldRename, String? valueField}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_package_info_plus.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_package_info_plus.dart new file mode 100644 index 00000000000..9fd6fc325f5 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_package_info_plus.dart @@ -0,0 +1,16 @@ +// package:package_info_plus, from the app's build properties (AppName, +// package_name, AppVersion). + +@JavaName('com.codename1.flutter.plugins.packageinfo.PackageInfo') +class PackageInfo { + external static Future fromPlatform(); + external static void setMockInitialValues({required String appName, required String packageName, + required String version, required String buildNumber, required String buildSignature, + String? installerStore, DateTime? updateTime}); + external String get appName; + external String get packageName; + external String get version; + external String get buildNumber; + external String get buildSignature; + external String? get installerStore; +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_path_provider.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_path_provider.dart new file mode 100644 index 00000000000..d511201cbf2 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_path_provider.dart @@ -0,0 +1,23 @@ +// package:path_provider, over com.codename1.flutter.plugins.pathprovider: the +// app's private home and caches directories from FileSystemStorage. + +@JavaName('com.codename1.flutter.plugins.pathprovider.PathProvider.getApplicationDocumentsDirectory') +external Future getApplicationDocumentsDirectory(); + +@JavaName('com.codename1.flutter.plugins.pathprovider.PathProvider.getApplicationSupportDirectory') +external Future getApplicationSupportDirectory(); + +@JavaName('com.codename1.flutter.plugins.pathprovider.PathProvider.getLibraryDirectory') +external Future getLibraryDirectory(); + +@JavaName('com.codename1.flutter.plugins.pathprovider.PathProvider.getTemporaryDirectory') +external Future getTemporaryDirectory(); + +@JavaName('com.codename1.flutter.plugins.pathprovider.PathProvider.getApplicationCacheDirectory') +external Future getApplicationCacheDirectory(); + +@JavaName('com.codename1.flutter.plugins.pathprovider.PathProvider.getDownloadsDirectory') +external Future getDownloadsDirectory(); + +@JavaName('com.codename1.flutter.plugins.pathprovider.PathProvider.getExternalStorageDirectory') +external Future getExternalStorageDirectory(); diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_share_plus.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_share_plus.dart new file mode 100644 index 00000000000..08957da5e29 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_share_plus.dart @@ -0,0 +1,33 @@ +// package:share_plus, over Display.share. The platform sheet reports no outcome, +// so every share completes with ShareResultStatus.unavailable, as share_plus does +// on platforms that cannot tell. + +@JavaName('com.codename1.flutter.plugins.share.ShareResultStatus') +enum ShareResultStatus { success, dismissed, unavailable } + +@JavaName('com.codename1.flutter.plugins.share.ShareResult') +class ShareResult { + external String get raw; + external ShareResultStatus get status; +} + +@JavaName('com.codename1.flutter.plugins.share.ShareParams') +class ShareParams { + external ShareParams({String? text, String? subject, String? title, Uri? uri, List? files, + Rect? sharePositionOrigin}); +} + +@JavaName('com.codename1.flutter.plugins.share.SharePlus') +class SharePlus { + external static SharePlus get instance; + external Future share(ShareParams params); +} + +// The pre-10 static API, still what most apps call. +@JavaName('com.codename1.flutter.plugins.share.SharePlus') +class Share { + external static Future share(String text, {String? subject, Rect? sharePositionOrigin}); + external static Future shareUri(Uri uri, {Rect? sharePositionOrigin}); + external static Future shareXFiles(List files, {String? subject, String? text, + Rect? sharePositionOrigin}); +} diff --git a/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_shared_preferences.dart b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_shared_preferences.dart new file mode 100644 index 00000000000..96958519731 --- /dev/null +++ b/maven/flutter-runtime/src/main/resources/META-INF/dart/plugin_shared_preferences.dart @@ -0,0 +1,25 @@ +// package:shared_preferences, over com.codename1.flutter.plugins.sharedpreferences: +// typed values kept in Codename One Storage (in memory when headless). + +@JavaName('com.codename1.flutter.plugins.sharedpreferences.SharedPreferences') +class SharedPreferences { + external static Future getInstance(); + external static void setMockInitialValues(Map values); + external Set getKeys(); + external Object? get(String key); + external bool containsKey(String key); + external String? getString(String key); + external int? getInt(String key); + external double? getDouble(String key); + external bool? getBool(String key); + external List? getStringList(String key); + external Future setString(String key, String value); + external Future setInt(String key, int value); + external Future setDouble(String key, double value); + external Future setBool(String key, bool value); + external Future setStringList(String key, List value); + external Future remove(String key); + external Future clear(); + external Future reload(); + external Future commit(); +} diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/ButtonConsumptionTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/ButtonConsumptionTest.java index 4f7d78a2a2a..cc388bb928c 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/ButtonConsumptionTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/ButtonConsumptionTest.java @@ -68,10 +68,10 @@ void textChildBecomesTheLabel() { @Test void iconChildBecomesTheMaterialGlyph() { TextButton b = new TextButton(); - b.child(new Icon(Icons.add)); + b.child(new Icon(Icons.add())); ButtonRenderElement el = mount(b); assertNull(el.consumedLabel()); - assertEquals(Icons.add.codePoint(), el.consumedIconChar()); + assertEquals(Icons.add().codePoint(), el.consumedIconChar()); } @Test @@ -85,11 +85,11 @@ void outlinedButtonConsumesLikeTheOthers() { @Test void iconButtonConsumesItsIconParameter() { IconButton b = new IconButton(); - b.icon(new Icon(Icons.settings)); + b.icon(new Icon(Icons.settings())); b.iconSize(32.0); ButtonRenderElement el = mount(b); assertNull(el.consumedLabel()); - assertEquals(Icons.settings.codePoint(), el.consumedIconChar()); + assertEquals(Icons.settings().codePoint(), el.consumedIconChar()); } /** diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/NoPushReferenceTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/NoPushReferenceTest.java new file mode 100644 index 00000000000..1829982c633 --- /dev/null +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/NoPushReferenceTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/// The Android builder turns push on, and then requires google-services.json, for any +/// app whose classes name com/codename1/push. The transpiler copies a plugin pack into +/// an app that names it, and firebase_auth alone pulls in the whole firebase pack, so +/// no pack and no runtime class may: the generated entry point installs push hooks only +/// for an app that depends on firebase_messaging (FirebaseMessaging.tokenSource / +/// topicSink). +class NoPushReferenceTest { + @Test + void noRuntimeClassNamesThePushPackage() throws IOException { + File root = new File("target/classes/com/codename1/flutter"); + assertTrue(root.isDirectory(), "run after compile: " + root.getAbsolutePath()); + // The packs compile with the tests (src/main/plugins is a test source root). + File packs = new File("target/test-classes/com/codename1/flutter/plugins"); + assertTrue(packs.isDirectory(), "run after test-compile: " + packs.getAbsolutePath()); + List offenders = new ArrayList(); + scan(root, offenders); + scan(packs, offenders); + assertTrue(offenders.isEmpty(), "classes naming com/codename1/push: " + offenders); + } + + /// A pack compiled into the runtime jar would ship in every app again, whatever it + /// imports: the packs reach an app only as sources the transpiler copies. + @Test + void noPackIsCompiledIntoTheRuntime() { + File compiled = new File("target/classes/com/codename1/flutter/plugins"); + assertTrue(!compiled.exists(), "plugin packs compiled into the runtime: " + compiled); + File shipped = new File("target/classes/META-INF/dart/java/com/codename1/flutter/plugins"); + assertTrue(shipped.isDirectory(), "plugin pack sources not packaged: " + shipped); + } + + private static void scan(File dir, List offenders) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File f : files) { + if (f.isDirectory()) { + scan(f, offenders); + } else if (f.getName().endsWith(".class")) { + String bytes = new String(Files.readAllBytes(f.toPath()), StandardCharsets.ISO_8859_1); + if (bytes.contains("com/codename1/push")) { + offenders.add(f.getPath()); + } + } + } + } +} diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/ButtonContentUnwrapTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/ButtonContentUnwrapTest.java index 464821ea242..fc01d50780a 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/ButtonContentUnwrapTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/ButtonContentUnwrapTest.java @@ -54,7 +54,7 @@ class ButtonContentUnwrapTest { static class ComposedIcon extends StatelessWidget { @Override public Widget build(BuildContext context) { - return new Icon(Icons.arrow_back); + return new Icon(Icons.arrow_back()); } } @@ -86,8 +86,8 @@ private ButtonRenderElement mount(Widget icon) { @Test @DisplayName("a plain Icon still yields its glyph") void aPlainIconIsFound() { - assertEquals(Icons.arrow_back.codePoint(), - mount(new Icon(Icons.arrow_back)).consumedIconChar()); + assertEquals(Icons.arrow_back().codePoint(), + mount(new Icon(Icons.arrow_back())).consumedIconChar()); } @Test @@ -95,7 +95,7 @@ void aPlainIconIsFound() { void aComposedIconIsResolved() { ButtonRenderElement e = mount(new ComposedIcon()); - assertEquals(Icons.arrow_back.codePoint(), e.consumedIconChar(), + assertEquals(Icons.arrow_back().codePoint(), e.consumedIconChar(), "BackButtonIcon-shaped content must resolve to its glyph"); assertEquals(null, e.consumedLabel(), "and must NOT fall back to a toString() label"); @@ -104,9 +104,9 @@ void aComposedIconIsResolved() { @Test @DisplayName("a wrapped icon is found through the wrapper") void aWrappedIconIsResolved() { - ButtonRenderElement e = mount(new Wrapper(new Icon(Icons.search))); + ButtonRenderElement e = mount(new Wrapper(new Icon(Icons.search()))); - assertEquals(Icons.search.codePoint(), e.consumedIconChar()); + assertEquals(Icons.search().codePoint(), e.consumedIconChar()); assertEquals(null, e.consumedLabel()); } @@ -115,7 +115,7 @@ void aWrappedIconIsResolved() { void nestingIsWalked() { ButtonRenderElement e = mount(new Wrapper(new ComposedIcon())); - assertEquals(Icons.arrow_back.codePoint(), e.consumedIconChar()); + assertEquals(Icons.arrow_back().codePoint(), e.consumedIconChar()); } @Test @@ -133,7 +133,7 @@ public com.codename1.flutter.State createState() { return new com.codename1.flutter.State() { @Override public Widget build(BuildContext context) { - return new Icon(Icons.tune); + return new Icon(Icons.tune()); } }; } @@ -144,7 +144,7 @@ public Widget build(BuildContext context) { void aStatefulIconIsResolved() { ButtonRenderElement e = mount(new StatefulIcon()); - assertEquals(Icons.tune.codePoint(), e.consumedIconChar(), + assertEquals(Icons.tune().codePoint(), e.consumedIconChar(), "FeatureDiscovery-shaped content must resolve to its glyph"); assertEquals(null, e.consumedLabel()); } diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/DisabledButtonIconForegroundTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/DisabledButtonIconForegroundTest.java index fb2fbcf64cc..e91e0ba8853 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/DisabledButtonIconForegroundTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/DisabledButtonIconForegroundTest.java @@ -79,7 +79,7 @@ void uninstall() { private static Button mount(dart.runtime.Funcs.VoidFunc0 onPressed) { TextButton b = TextButton.icon(null, onPressed, null, - new Icon(Icons.add), new Text("Button")); + new Icon(Icons.add()), new Text("Button")); Element root = FlutterUI.mount(b, new RenderHost(), new BuildOwner()); Component c = ((ButtonRenderElement) root).component(); assertNotNull(c, "RasterDisplay gives createComponent a real Button"); diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/FabExtendedPaddingTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/FabExtendedPaddingTest.java index aa89fa3e07c..c3579dadf4c 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/FabExtendedPaddingTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/FabExtendedPaddingTest.java @@ -76,7 +76,7 @@ private static FabRenderElement mountFab(com.codename1.flutter.Widget icon) { @Test void withAnIconTheStartInsetIsSixteenAndTheEndIsTwenty() { - FabRenderElement el = mountFab(new Icon(Icons.arrow_back)); + FabRenderElement el = mountFab(new Icon(Icons.arrow_back())); Component c = el.component(); assertNotNull(c, "RasterDisplay gives createComponent a real Button"); Style s = c.getUnselectedStyle(); @@ -100,7 +100,7 @@ void withoutAnIconBothInsetsAreTwenty() { @Test void theIconAndLabelAreEightLogicalPixelsApart() { - FabRenderElement el = mountFab(new Icon(Icons.arrow_back)); + FabRenderElement el = mountFab(new Icon(Icons.arrow_back())); Component c = el.component(); assertTrue(c instanceof Button); assertEquals((int) Math.round(Dp.px(8)), ((Button) c).getGap(), diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/PopupMenuTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/PopupMenuTest.java index 5d07f28471f..f6ee766891c 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/PopupMenuTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/PopupMenuTest.java @@ -87,16 +87,16 @@ void theDefaultTriggerIsTheOverflowIcon() { assertNotNull(trigger, "a menu button with no icon must still show something"); assertTrue(trigger instanceof Icon, "Flutter falls back to an Icon"); - assertEquals(Icons.more_vert.codePoint(), ((Icon) trigger).getIcon().codePoint()); + assertEquals(Icons.more_vert().codePoint(), ((Icon) trigger).getIcon().codePoint()); } @Test @DisplayName("an explicit icon wins over the default") void anExplicitIconIsUsed() { PopupMenuButton b = new PopupMenuButton(); - b.icon(new Icon(Icons.search)); + b.icon(new Icon(Icons.search())); - assertEquals(Icons.search.codePoint(), + assertEquals(Icons.search().codePoint(), ((Icon) b.effectiveTrigger()).getIcon().codePoint()); } @@ -107,7 +107,7 @@ void theTriggerIsRendered() { PopupMenuButtonRenderElement e = (PopupMenuButtonRenderElement) FlutterUI.mount(b, new RenderHost(), new BuildOwner()); - assertEquals(Icons.more_vert.codePoint(), e.consumedIconChar(), + assertEquals(Icons.more_vert().codePoint(), e.consumedIconChar(), "the overflow glyph must be what the button draws"); assertNotNull(e.pressHandler(), "and pressing it must do something"); } diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/TextFieldDecorationUpdateTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/TextFieldDecorationUpdateTest.java index 98123ab27fc..f91cc30eab7 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/TextFieldDecorationUpdateTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/material/TextFieldDecorationUpdateTest.java @@ -51,7 +51,7 @@ * a previous finite limit must lift the native cap back up, not keep it. *
  • The decoration's prefix/suffix icon glyph: {@code decorated()} only builds the * row ONCE, on mount, so a password field toggling between - * {@code Icons.visibility}/{@code visibility_off} kept drawing the glyph it was + * {@code Icons.visibility()}/{@code visibility_off} kept drawing the glyph it was * built with forever.
  • * */ @@ -116,7 +116,7 @@ private static TextField passwordField(boolean obscured) { tf.obscureText(true); InputDecoration d = new InputDecoration(); IconButton toggle = new IconButton(); - toggle.icon(new Icon(obscured ? Icons.visibility_off : Icons.visibility)); + toggle.icon(new Icon(obscured ? Icons.visibility_off() : Icons.visibility())); d.suffixIcon(toggle); tf.decoration(d); return tf; diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/navigation/PopScopeTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/navigation/PopScopeTest.java new file mode 100644 index 00000000000..32f9a90d16a --- /dev/null +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/navigation/PopScopeTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.flutter.navigation; + +import java.util.ArrayList; +import java.util.List; + +import com.codename1.flutter.material.Dialogs; +import com.codename1.flutter.testsupport.ProbeBox; +import com.codename1.flutter.widgets.PopScope; +import com.codename1.flutter.widgets.WillPopScope; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** PopScope and WillPopScope veto maybePop; pop itself is never vetoed. */ +class PopScopeTest { + + @BeforeEach + void resetStacks() { + Navigator.reset(); + Dialogs.reset(); + } + + /** Pushes a route and mounts {@code page} as its tree, which headless pushes never do. */ + private static void pushMounted(com.codename1.flutter.Widget page) { + MaterialPageRoute r = new MaterialPageRoute(); + r.builder((context) -> new ProbeBox(1, 1)); + Navigator.push(null, r); + Navigator.attachTopRootForTest(com.codename1.flutter.FlutterUI.mount(page, + new com.codename1.flutter.rendering.RenderHost(), new com.codename1.flutter.BuildOwner())); + } + + private static PopScope scope(boolean canPop, final List heard) { + PopScope scope = new PopScope(); + scope.canPop(canPop); + scope.onPopInvokedWithResult((didPop, result) -> heard.add(didPop + ":" + result)); + scope.child(new ProbeBox(10, 10)); + return scope; + } + + @Test + void canPopFalseVetoesMaybePopAndReportsTheAttempt() { + List heard = new ArrayList(); + pushMounted(scope(false, heard)); + assertEquals(Boolean.TRUE, Navigator.maybePop(null, "x").getNow(), "a veto is a handled request"); + assertEquals(1, Navigator.stackSize()); + assertEquals("[false:x]", heard.toString()); + + Navigator.pop(null); + assertEquals(0, Navigator.stackSize(), "pop is not a request and is never vetoed"); + } + + @Test + void canPopTrueLetsMaybePopCloseAndReportsDidPop() { + List heard = new ArrayList(); + pushMounted(scope(true, heard)); + assertEquals(Boolean.TRUE, Navigator.maybePop(null, "done").getNow()); + assertEquals(0, Navigator.stackSize()); + assertEquals("[true:done]", heard.toString()); + } + + @Test + void willPopScopeAnswersAsynchronously() { + final dart.async.Completer answer = new dart.async.Completer(); + WillPopScope w = new WillPopScope(); + w.onWillPop(() -> answer.future()); + w.child(new ProbeBox(10, 10)); + pushMounted(w); + dart.async.Future popped = Navigator.maybePop(null, null); + assertEquals(1, Navigator.stackSize(), "waits for onWillPop"); + answer.complete(Boolean.TRUE); + assertEquals(Boolean.TRUE, popped.getNow()); + assertEquals(0, Navigator.stackSize()); + } +} diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/AsyncAndDependencyTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/AsyncAndDependencyTest.java index 2a76dc71210..dfac5a266eb 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/AsyncAndDependencyTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/AsyncAndDependencyTest.java @@ -65,9 +65,9 @@ private Element mount(Widget root) { private static FutureBuilder futureBuilder(Object future, final AsyncSnapshot[] seen) { FutureBuilder fb = new FutureBuilder(); fb.future(future); - fb.builder(new Funcs.Func2() { + fb.builder(new Funcs.Func2, Widget>() { @Override - public Widget call(BuildContext context, AsyncSnapshot snapshot) { + public Widget call(BuildContext context, AsyncSnapshot snapshot) { seen[0] = snapshot; return new ProbeBox(1, 1); } diff --git a/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/GestureHitTestTest.java b/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/GestureHitTestTest.java index 912cbef3a59..2655ecf984e 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/GestureHitTestTest.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/flutter/widgets/GestureHitTestTest.java @@ -80,7 +80,7 @@ void theContentSubtreeIsReachable() { void anInnerButtonKeepsItsHandler() { final int[] pressed = {0}; IconButton inner = new IconButton(); - inner.icon(new Icon(com.codename1.flutter.Icons.search)); + inner.icon(new Icon(com.codename1.flutter.Icons.search())); inner.onPressed(new Funcs.VoidFunc0() { @Override public void call() { diff --git a/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M2Showcase.java b/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M2Showcase.java index c371cb84b13..b5f17aa97ac 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M2Showcase.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M2Showcase.java @@ -60,7 +60,7 @@ public Widget build(BuildContext context) { // GridView.count with every named argument var $t2 = GridView.count(null, null, null, null, 3L, 1.5, 4.0, 4.0, EdgeInsets.all(8.0), - DartList.of(new Icon(Icons.home), new Icon(Icons.settings))); + DartList.of(new Icon(Icons.home()), new Icon(Icons.settings()))); // Image.asset('logo.png', width: 100) var $t3 = Image.asset("logo.png", null, 100.0, null, null, @@ -79,7 +79,7 @@ public Widget build(BuildContext context) { var $t7 = new Positioned(); $t7.right(0.0); $t7.bottom(0.0); - $t7.child(new Icon(Icons.add)); + $t7.child(new Icon(Icons.add())); var $t8 = new Align(); $t8.alignment(Alignment.topCenter); $t8.child(new Text("aligned")); @@ -119,7 +119,7 @@ public Widget build(BuildContext context) { $t15.child(new Text("Outlined")); var $t16 = new IconButton(); $t16.onPressed(this::_onPressed); - $t16.icon(new Icon(Icons.share)); + $t16.icon(new Icon(Icons.share())); $t16.iconSize(32.0); $t16.color(Colors.red); diff --git a/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M3Showcase.java b/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M3Showcase.java index 9ae9223763d..ad44f564c9d 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M3Showcase.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/M3Showcase.java @@ -111,18 +111,18 @@ public Widget build(BuildContext context) { // ListTile var $t6 = new ListTile(); - $t6.leading(new Icon(Icons.home)); + $t6.leading(new Icon(Icons.home())); $t6.title(new Text("Home")); $t6.subtitle(new Text("Front page")); - $t6.trailing(new Icon(Icons.add)); + $t6.trailing(new Icon(Icons.add())); $t6.onTap(() -> { /* ... */ }); // BottomNavigationBar var $t7 = new BottomNavigationBarItem(); - $t7.icon(new Icon(Icons.home)); + $t7.icon(new Icon(Icons.home())); $t7.label("Home"); var $t8 = new BottomNavigationBarItem(); - $t8.icon(new Icon(Icons.settings)); + $t8.icon(new Icon(Icons.settings())); $t8.label("Settings"); var $t9 = new BottomNavigationBar(); $t9.items(DartList.of($t7, $t8)); diff --git a/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/_MyHomePageState.java b/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/_MyHomePageState.java index 17080fee67d..2108f9f5b49 100644 --- a/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/_MyHomePageState.java +++ b/maven/flutter-runtime/src/test/java/com/codename1/generated/flutter/_MyHomePageState.java @@ -57,7 +57,7 @@ public Widget build(BuildContext context) { var $t6 = new FloatingActionButton(); $t6.onPressed(this::_incrementCounter); $t6.tooltip("Increment"); - $t6.child(new Icon(Icons.add)); + $t6.child(new Icon(Icons.add())); $t0.floatingActionButton($t6); return $t0; } diff --git a/scripts/flutter-apps/Acceptance.java b/scripts/flutter-apps/Acceptance.java new file mode 100644 index 00000000000..34384f78230 --- /dev/null +++ b/scripts/flutter-apps/Acceptance.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +import com.codename1.build.Log; +import com.codename1.dart.transpiler.api.DartTranspiler; +import com.codename1.dart.transpiler.api.Diagnostic; +import com.codename1.dart.transpiler.api.TranspileRequest; +import com.codename1.dart.transpiler.api.TranspileResult; +import com.codename1.maven.FlutterProjectImporter; + +import java.io.File; +import java.lang.reflect.Proxy; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.tools.ToolProvider; + +/// Imports a Flutter app exactly as `cn1:import-flutter-project` would, transpiles it +/// with the stubs the runtime publishes, and compiles the generated Java against the +/// runtime classes. An "ordinary" app must pass all three steps with no edits. +/// +/// Usage: `java -cp Acceptance.java ` +/// where `` is the flutter-runtime classes or jar and `` the runtime +/// classpath (core, dart-runtime, flutter-runtime). `run-acceptance.sh` assembles both. +/// Exits 0 only when nothing is uncovered, nothing fails to transpile and javac is clean. +public class Acceptance { + public static void main(String[] args) throws Exception { + File app = new File(args[0]); + File work = new File(args[1]); + File common = new File(work, "common"); + Log log = (Log) Proxy.newProxyInstance(Acceptance.class.getClassLoader(), new Class[] {Log.class}, + (p, m, a) -> m.getReturnType() == boolean.class ? Boolean.FALSE : null); + FlutterProjectImporter.Result r = new FlutterProjectImporter(log) + .importProject(app, common, "com.example", "Main"); + System.out.println("== import " + r.appName + ": " + r.copiedFiles + " app file(s)"); + for (Map.Entry e : r.transpiledPackages.entrySet()) { + System.out.println(" transpiled " + e.getKey() + " (" + e.getValue() + " file(s))"); + } + System.out.println(" covered by the runtime: " + r.covered); + boolean ok = true; + for (String u : r.uncovered) { + System.out.println(" NOT supported: " + u); + ok = false; + } + for (Map.Entry e : r.unsupportedPackages.entrySet()) { + System.out.println(" NOT supported: " + e.getKey() + " -- " + e.getValue()); + } + + File out = new File(work, "generated"); + TranspileResult t = new DartTranspiler().transpile(new TranspileRequest() + .sourceRoot(new File(common, "src/main/flutter")) + .stubClasspathEntry(new File(args[2])) + .javaSourceRoot(new File(common, "src/main/java")) + .outputDir(out)); + List errors = t.errors(); + System.out.println("== transpile: " + t.getGeneratedFiles().size() + " file(s), " + errors.size() + + " error(s)"); + for (Diagnostic d : errors) { + System.out.println(" " + d); + } + ok &= errors.isEmpty(); + if (errors.isEmpty()) { + List javac = new ArrayList<>(); + File classes = new File(work, "classes"); + classes.mkdirs(); + javac.add("-nowarn"); + javac.add("-encoding"); + javac.add("UTF-8"); + javac.add("-Xmaxerrs"); + javac.add("200"); + javac.add("-d"); + javac.add(classes.getPath()); + javac.add("-cp"); + javac.add(args[3]); + try (java.util.stream.Stream s = Files.walk(out.toPath())) { + javac.addAll(s.filter(p -> p.toString().endsWith(".java")).map(Path::toString) + .collect(Collectors.toList())); + } + // The import-generated entry point too: it names the packs whose hooks it + // installs, and the transpiler copies only the packs something names. + Path own = new File(common, "src/main/java").toPath(); + if (Files.isDirectory(own)) { + try (java.util.stream.Stream s = Files.walk(own)) { + javac.addAll(s.filter(p -> p.toString().endsWith(".java")).map(Path::toString) + .collect(Collectors.toList())); + } + } + int rc = ToolProvider.getSystemJavaCompiler().run(null, null, null, javac.toArray(new String[0])); + System.out.println("== javac: " + (rc == 0 ? "clean" : "FAILED")); + ok &= rc == 0; + } + System.out.println(ok ? "ACCEPTED" : "REJECTED"); + System.exit(ok ? 0 : 1); + } +} diff --git a/scripts/flutter-apps/field_notes/.gitignore b/scripts/flutter-apps/field_notes/.gitignore new file mode 100644 index 00000000000..877ebfbd959 --- /dev/null +++ b/scripts/flutter-apps/field_notes/.gitignore @@ -0,0 +1,3 @@ +.dart_tool/ +.flutter-plugins-dependencies +build/ diff --git a/scripts/flutter-apps/field_notes/lib/main.dart b/scripts/flutter-apps/field_notes/lib/main.dart new file mode 100644 index 00000000000..8220e06fa9d --- /dev/null +++ b/scripts/flutter-apps/field_notes/lib/main.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; + +import 'screens/notes_screen.dart'; +import 'screens/sign_in_screen.dart'; +import 'services/notifier.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: const FirebaseOptions( + apiKey: 'demo-api-key', + appId: '1:1234:android:abcd', + messagingSenderId: '1234', + projectId: 'field-notes-demo', + ), + ); + await Notifier.instance.init(); + runApp(const FieldNotesApp()); +} + +class FieldNotesApp extends StatelessWidget { + const FieldNotesApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Field Notes', + theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true), + home: StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + final user = snapshot.data; + if (user == null) { + return const SignInScreen(); + } + FirebaseAnalytics.instance.logScreenView(screenName: 'notes'); + return NotesScreen(user: user); + }, + ), + ); + } +} diff --git a/scripts/flutter-apps/field_notes/lib/screens/new_note_screen.dart b/scripts/flutter-apps/field_notes/lib/screens/new_note_screen.dart new file mode 100644 index 00000000000..5707073fc1a --- /dev/null +++ b/scripts/flutter-apps/field_notes/lib/screens/new_note_screen.dart @@ -0,0 +1,152 @@ +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../services/note.dart'; +import '../services/notifier.dart'; + +class NewNoteScreen extends StatefulWidget { + const NewNoteScreen({super.key, this.initialTitle}); + + final String? initialTitle; + + @override + State createState() => _NewNoteScreenState(); +} + +class _NewNoteScreenState extends State { + late final TextEditingController _title = TextEditingController(text: widget.initialTitle ?? ''); + final _picker = ImagePicker(); + Position? _position; + XFile? _photo; + String? _status; + + bool get _dirty => _title.text.isNotEmpty || _photo != null; + + @override + void initState() { + super.initState(); + _locate(); + } + + Future _locate() async { + if (!await Geolocator.isLocationServiceEnabled()) { + setState(() => _status = 'Location is off'); + return; + } + var permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + } + if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) { + setState(() => _status = 'Location permission denied'); + return; + } + final position = await Geolocator.getCurrentPosition( + locationSettings: const LocationSettings(accuracy: LocationAccuracy.high), + ); + setState(() => _position = position); + } + + Future _takePhoto(ImageSource source) async { + final photo = await _picker.pickImage(source: source, maxWidth: 1600, imageQuality: 85); + if (photo != null) { + setState(() => _photo = photo); + } + } + + Future _save() async { + final position = _position; + if (_title.text.isEmpty || position == null) { + return; + } + final info = await DeviceInfoPlugin().deviceInfo; + debugPrint('saved on ${info.data['model'] ?? 'unknown device'}'); + await Notifier.instance.noteSaved(_title.text); + if (!mounted) { + return; + } + Navigator.of(context).pop(Note( + title: _title.text, + latitude: position.latitude, + longitude: position.longitude, + photo: _photo, + )); + } + + Future _confirmDiscard() async { + final discard = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Discard this note?'), + actions: [ + TextButton(onPressed: () => Navigator.of(context).pop(false), child: const Text('Keep editing')), + TextButton(onPressed: () => Navigator.of(context).pop(true), child: const Text('Discard')), + ], + ), + ); + return discard ?? false; + } + + @override + void dispose() { + _title.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final position = _position; + return PopScope( + canPop: !_dirty, + onPopInvokedWithResult: (didPop, result) async { + if (didPop) { + return; + } + if (await _confirmDiscard() && context.mounted) { + Navigator.of(context).pop(); + } + }, + child: Scaffold( + appBar: AppBar(title: const Text('New note')), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + TextField( + controller: _title, + decoration: const InputDecoration(labelText: 'What did you see?'), + onChanged: (_) => setState(() {}), + ), + const SizedBox(height: 16), + Text(position == null + ? (_status ?? 'Finding your location...') + : 'At ${position.latitude.toStringAsFixed(5)}, ${position.longitude.toStringAsFixed(5)}'), + const SizedBox(height: 16), + Row( + children: [ + OutlinedButton.icon( + onPressed: () => _takePhoto(ImageSource.camera), + icon: const Icon(Icons.camera_alt), + label: const Text('Camera'), + ), + const SizedBox(width: 12), + OutlinedButton.icon( + onPressed: () => _takePhoto(ImageSource.gallery), + icon: const Icon(Icons.photo_library), + label: const Text('Gallery'), + ), + ], + ), + if (_photo != null) Text('Photo: ${_photo!.name}'), + const SizedBox(height: 24), + FilledButton( + onPressed: position == null || _title.text.isEmpty ? null : _save, + child: const Text('Save'), + ), + ], + ), + ), + ); + } +} diff --git a/scripts/flutter-apps/field_notes/lib/screens/notes_screen.dart b/scripts/flutter-apps/field_notes/lib/screens/notes_screen.dart new file mode 100644 index 00000000000..a5a2eeee783 --- /dev/null +++ b/scripts/flutter-apps/field_notes/lib/screens/notes_screen.dart @@ -0,0 +1,165 @@ +import 'dart:async'; + +import 'package:app_links/app_links.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:share_plus/share_plus.dart'; + +import '../services/note.dart'; +import 'new_note_screen.dart'; + +class NotesScreen extends StatefulWidget { + const NotesScreen({super.key, required this.user}); + + final User user; + + @override + State createState() => _NotesScreenState(); +} + +class _NotesScreenState extends State { + final List _notes = []; + final _links = AppLinks(); + StreamSubscription>? _connectivity; + StreamSubscription? _linkSub; + StreamSubscription? _pushSub; + bool _offline = false; + bool _showMap = false; + String _version = ''; + GoogleMapController? _map; + + @override + void initState() { + super.initState(); + _connectivity = Connectivity().onConnectivityChanged.listen((results) { + setState(() => _offline = results.contains(ConnectivityResult.none)); + }); + _linkSub = _links.uriLinkStream.listen(_openLink); + _pushSub = FirebaseMessaging.onMessage.listen((message) { + final text = message.notification?.body ?? 'New message'; + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(text))); + }); + FirebaseMessaging.instance.requestPermission(); + PackageInfo.fromPlatform().then((info) { + setState(() => _version = '${info.version}+${info.buildNumber}'); + }); + } + + void _openLink(Uri uri) { + final title = uri.queryParameters['title']; + if (title != null) { + _addNote(context, initialTitle: title); + } + } + + @override + void dispose() { + _connectivity?.cancel(); + _linkSub?.cancel(); + _pushSub?.cancel(); + _map?.dispose(); + super.dispose(); + } + + Future _addNote(BuildContext context, {String? initialTitle}) async { + final note = await Navigator.of(context).push( + MaterialPageRoute(builder: (_) => NewNoteScreen(initialTitle: initialTitle)), + ); + if (note != null) { + setState(() => _notes.insert(0, note)); + await _map?.animateCamera(CameraUpdate.newLatLng(LatLng(note.latitude, note.longitude))); + } + } + + Future _share(Note note) async { + final photo = note.photo; + if (photo != null) { + await SharePlus.instance.share(ShareParams(text: note.summary, files: [photo])); + } else { + await SharePlus.instance.share(ShareParams(text: note.summary)); + } + } + + Set get _markers => { + for (final n in _notes) + Marker( + markerId: MarkerId(n.createdAt.toIso8601String()), + position: LatLng(n.latitude, n.longitude), + infoWindow: InfoWindow(title: n.title), + ), + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.user.email ?? 'Field Notes'), + actions: [ + IconButton( + icon: Icon(_showMap ? Icons.list : Icons.map), + onPressed: () => setState(() => _showMap = !_showMap), + ), + IconButton( + icon: const Icon(Icons.logout), + onPressed: () => FirebaseAuth.instance.signOut(), + ), + ], + ), + body: Column( + children: [ + if (_offline) + Container( + width: double.infinity, + color: Colors.amber, + padding: const EdgeInsets.all(8), + child: const Text('Offline -- notes are kept on this device'), + ), + Expanded( + child: _showMap + ? GoogleMap( + initialCameraPosition: const CameraPosition(target: LatLng(32.08, 34.78), zoom: 11), + markers: _markers, + onMapCreated: (controller) => _map = controller, + ) + : _notes.isEmpty + ? const Center(child: Text('No notes yet')) + : ListView.builder( + itemCount: _notes.length, + itemBuilder: (context, i) { + final note = _notes[i]; + return Dismissible( + key: ValueKey(note.createdAt), + direction: DismissDirection.endToStart, + background: Container(color: Colors.red), + onDismissed: (_) => setState(() => _notes.removeAt(i)), + child: ListTile( + leading: Icon(note.photo == null ? Icons.note : Icons.photo), + title: Text(note.title), + subtitle: Text(note.summary), + trailing: IconButton( + icon: const Icon(Icons.share), + onPressed: () => _share(note), + ), + ), + ); + }, + ), + ), + if (_version.isNotEmpty) + Padding( + padding: const EdgeInsets.all(4), + child: Text('v$_version', style: Theme.of(context).textTheme.bodySmall), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _addNote(context), + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/scripts/flutter-apps/field_notes/lib/screens/sign_in_screen.dart b/scripts/flutter-apps/field_notes/lib/screens/sign_in_screen.dart new file mode 100644 index 00000000000..2dc67749a35 --- /dev/null +++ b/scripts/flutter-apps/field_notes/lib/screens/sign_in_screen.dart @@ -0,0 +1,106 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; + +class SignInScreen extends StatefulWidget { + const SignInScreen({super.key}); + + @override + State createState() => _SignInScreenState(); +} + +class _SignInScreenState extends State { + final _email = TextEditingController(); + final _password = TextEditingController(); + String? _error; + bool _busy = false; + + Future _submit({required bool create}) async { + setState(() { + _busy = true; + _error = null; + }); + try { + if (create) { + await FirebaseAuth.instance.createUserWithEmailAndPassword( + email: _email.text.trim(), password: _password.text); + } else { + await FirebaseAuth.instance.signInWithEmailAndPassword( + email: _email.text.trim(), password: _password.text); + } + } on FirebaseAuthException catch (e) { + setState(() => _error = _message(e.code)); + } finally { + if (mounted) { + setState(() => _busy = false); + } + } + } + + String _message(String code) { + switch (code) { + case 'user-not-found': + case 'wrong-password': + case 'invalid-credential': + return 'Email or password is wrong'; + case 'email-already-in-use': + return 'That email already has an account'; + case 'weak-password': + return 'Choose a longer password'; + case 'network-request-failed': + return 'You appear to be offline'; + default: + return 'Sign-in failed ($code)'; + } + } + + @override + void dispose() { + _email.dispose(); + _password.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Field Notes')), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + controller: _email, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration(labelText: 'Email'), + ), + const SizedBox(height: 12), + TextField( + controller: _password, + obscureText: true, + decoration: const InputDecoration(labelText: 'Password'), + ), + const SizedBox(height: 24), + if (_error != null) + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Text(_error!, style: TextStyle(color: Theme.of(context).colorScheme.error)), + ), + FilledButton( + onPressed: _busy ? null : () => _submit(create: false), + child: const Text('Sign in'), + ), + TextButton( + onPressed: _busy ? null : () => _submit(create: true), + child: const Text('Create account'), + ), + TextButton( + onPressed: _busy ? null : () => FirebaseAuth.instance.signInAnonymously(), + child: const Text('Continue without an account'), + ), + ], + ), + ), + ); + } +} diff --git a/scripts/flutter-apps/field_notes/lib/services/note.dart b/scripts/flutter-apps/field_notes/lib/services/note.dart new file mode 100644 index 00000000000..12f7554f5a5 --- /dev/null +++ b/scripts/flutter-apps/field_notes/lib/services/note.dart @@ -0,0 +1,14 @@ +import 'package:cross_file/cross_file.dart'; + +class Note { + Note({required this.title, required this.latitude, required this.longitude, this.photo}) + : createdAt = DateTime.now(); + + final String title; + final double latitude; + final double longitude; + final XFile? photo; + final DateTime createdAt; + + String get summary => '$title (${latitude.toStringAsFixed(4)}, ${longitude.toStringAsFixed(4)})'; +} diff --git a/scripts/flutter-apps/field_notes/lib/services/notifier.dart b/scripts/flutter-apps/field_notes/lib/services/notifier.dart new file mode 100644 index 00000000000..7a2e8d69d24 --- /dev/null +++ b/scripts/flutter-apps/field_notes/lib/services/notifier.dart @@ -0,0 +1,34 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class Notifier { + Notifier._(); + + static final Notifier instance = Notifier._(); + + final FlutterLocalNotificationsPlugin _plugin = FlutterLocalNotificationsPlugin(); + int _nextId = 0; + + Future init() async { + const settings = InitializationSettings( + android: AndroidInitializationSettings('@mipmap/ic_launcher'), + iOS: DarwinInitializationSettings(), + ); + await _plugin.initialize(settings, onDidReceiveNotificationResponse: (response) { + lastOpened = response.payload; + }); + await _plugin + .resolvePlatformSpecificImplementation() + ?.requestNotificationsPermission(); + } + + String? lastOpened; + + Future noteSaved(String title) { + const details = NotificationDetails( + android: AndroidNotificationDetails('notes', 'Notes', + channelDescription: 'Saved notes', importance: Importance.high, priority: Priority.high), + iOS: DarwinNotificationDetails(), + ); + return _plugin.show(_nextId++, 'Note saved', title, details, payload: title); + } +} diff --git a/scripts/flutter-apps/field_notes/pubspec.lock b/scripts/flutter-apps/field_notes/pubspec.lock new file mode 100644 index 00000000000..5a195175d98 --- /dev/null +++ b/scripts/flutter-apps/field_notes/pubspec.lock @@ -0,0 +1,914 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + url: "https://pub.dev" + source: hosted + version: "1.3.59" + app_links: + dependency: "direct main" + description: + name: app_links + sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + url: "https://pub.dev" + source: hosted + version: "6.1.5" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + cross_file: + dependency: "direct main" + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + dbus: + dependency: transitive + description: + name: dbus + sha256: a48d5da28e89bd02196e80d81ed8d7954923d00a0f4a68cc20b575038f023383 + url: "https://pub.dev" + source: hosted + version: "0.7.15" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + url: "https://pub.dev" + source: hosted + version: "10.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + sha256: "4f85b161772e1d54a66893ef131c0a44bd9e552efa78b33d5f4f60d2caa5c8a3" + url: "https://pub.dev" + source: hosted + version: "11.6.0" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: a44b6d1155ed5cae7641e3de7163111cfd9f6f6c954ca916dc6a3bdfa86bf845 + url: "https://pub.dev" + source: hosted + version: "4.4.3" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: c7d1ed1f86ae64215757518af5576ff88341c8ce5741988c05cc3b2e07b0b273 + url: "https://pub.dev" + source: hosted + version: "0.5.10+16" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "0fed2133bee1369ee1118c1fef27b2ce0d84c54b7819a2b17dada5cfec3b03ff" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "871c9df4ec9a754d1a793f7eb42fa3b94249d464cfb19152ba93e14a5966b386" + url: "https://pub.dev" + source: hosted + version: "7.7.3" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: d9ada769c43261fd1b18decf113186e915c921a811bd5014f5ea08f4cf4bc57e + url: "https://pub.dev" + source: hosted + version: "5.15.3" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + url: "https://pub.dev" + source: hosted + version: "3.15.2" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + url: "https://pub.dev" + source: hosted + version: "2.24.1" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" + url: "https://pub.dev" + source: hosted + version: "15.2.10" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" + url: "https://pub.dev" + source: hosted + version: "4.6.10" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" + url: "https://pub.dev" + source: hosted + version: "3.10.10" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 + url: "https://pub.dev" + source: hosted + version: "18.0.1" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.dev" + source: hosted + version: "2.0.34" + flutter_test: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2 + url: "https://pub.dev" + source: hosted + version: "13.0.4" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d + url: "https://pub.dev" + source: hosted + version: "4.6.2" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: a596abaef55d63dd0a8a61acd96ad388f9b54017c88224741f54fd0400aa4ffe + url: "https://pub.dev" + source: hosted + version: "2.4.2" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: e012af6b7e2674e7500237ab11e44ac9119e87fd0105e7bb55cde1750e8e5d80 + url: "https://pub.dev" + source: hosted + version: "4.4.1" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "19e485a0f8d6a88abcf9c53cba3a4105e14b7435ed8ac1c108c067b938fe8429" + url: "https://pub.dev" + source: hosted + version: "4.1.4" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" + url: "https://pub.dev" + source: hosted + version: "0.2.5" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "6811afff50dc410b860622ac89df125f5be1eada3ad69197e4c24dd18f2ef0e0" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + sha256: "9b0d6dab3de6955837575dc371dd772fcb5d0a90f6a4954e8c066472f9938550" + url: "https://pub.dev" + source: hosted + version: "2.14.2" + google_maps_flutter_android: + dependency: transitive + description: + name: google_maps_flutter_android + sha256: f1eb5ffa34ba41f8591e53ce439f78af179a506e8386a1297d0ecd202e05c734 + url: "https://pub.dev" + source: hosted + version: "2.19.8" + google_maps_flutter_ios: + dependency: transitive + description: + name: google_maps_flutter_ios + sha256: a2e3c7ad2392ea65d6775704716d0aa3c3d226cb984fd0a688bca40f6be1a451 + url: "https://pub.dev" + source: hosted + version: "2.17.4" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + sha256: ddbe34435dfb34e83fca295c6a8dcc53c3b51487e9eec3c737ce4ae605574347 + url: "https://pub.dev" + source: hosted + version: "2.15.0" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: d416602944e1859f3cbbaa53e34785c223fa0a11eddb34a913c964c5cbb5d8cf + url: "https://pub.dev" + source: hosted + version: "0.5.14+3" + gtk: + dependency: transitive + description: + name: gtk + sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + html: + dependency: transitive + description: + name: html + sha256: "43b67b8f43321ab066817dfac5619596c98bb1b61624e77203bb4351785f9699" + url: "https://pub.dev" + source: hosted + version: "0.15.7" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f + url: "https://pub.dev" + source: hosted + version: "0.8.13+17" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad" + url: "https://pub.dev" + source: hosted + version: "0.8.13+3" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + url: "https://pub.dev" + source: hosted + version: "8.3.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + url: "https://pub.dev" + source: hosted + version: "2.2.23" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "60257e2b8fd8b13b7c755065c2639a6c800031bb5bd3e30a007fc6739c77b2e1" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "89b11d62e1040ee681af3b4f1c97a803ecbf010c6e8b9c5896ee5b08c3bf7b78" + url: "https://pub.dev" + source: hosted + version: "15.4.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/scripts/flutter-apps/field_notes/pubspec.yaml b/scripts/flutter-apps/field_notes/pubspec.yaml new file mode 100644 index 00000000000..578439298bb --- /dev/null +++ b/scripts/flutter-apps/field_notes/pubspec.yaml @@ -0,0 +1,28 @@ +name: field_notes +description: An ordinary Flutter app on device plugins -- location, camera, maps, sharing, notifications, Firebase sign-in -- used to check that Codename One imports such apps unmodified. +publish_to: none +version: 2.1.0+7 + +environment: + sdk: ">=3.4.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + firebase_core: ^3.6.0 + firebase_auth: ^5.3.1 + firebase_analytics: ^11.3.3 + firebase_messaging: ^15.1.3 + geolocator: ^13.0.1 + image_picker: ^1.1.2 + share_plus: ^11.0.0 + google_maps_flutter: ^2.9.0 + flutter_local_notifications: ^18.0.1 + connectivity_plus: ^6.0.5 + package_info_plus: ^8.0.2 + device_info_plus: ^10.1.2 + app_links: ^6.3.2 + cross_file: ^0.3.4 + +flutter: + uses-material-design: true diff --git a/scripts/flutter-apps/pet_adoption/.gitignore b/scripts/flutter-apps/pet_adoption/.gitignore new file mode 100644 index 00000000000..877ebfbd959 --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/.gitignore @@ -0,0 +1,3 @@ +.dart_tool/ +.flutter-plugins-dependencies +build/ diff --git a/scripts/flutter-apps/pet_adoption/lib/blocs/adoption_bloc.dart b/scripts/flutter-apps/pet_adoption/lib/blocs/adoption_bloc.dart new file mode 100644 index 00000000000..ce1c345ce75 --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/blocs/adoption_bloc.dart @@ -0,0 +1,54 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +sealed class AdoptionEvent extends Equatable { + const AdoptionEvent(); + @override + List get props => []; +} + +final class AdoptionRequested extends AdoptionEvent { + const AdoptionRequested(this.petId, this.applicant); + final int petId; + final String applicant; + @override + List get props => [petId, applicant]; +} + +final class AdoptionReset extends AdoptionEvent { + const AdoptionReset(); +} + +sealed class AdoptionState extends Equatable { + const AdoptionState(); + @override + List get props => []; +} + +final class AdoptionIdle extends AdoptionState { + const AdoptionIdle(); +} + +final class AdoptionSubmitting extends AdoptionState { + const AdoptionSubmitting(); +} + +final class AdoptionDone extends AdoptionState { + const AdoptionDone(this.message); + final String message; + @override + List get props => [message]; +} + +class AdoptionBloc extends Bloc { + AdoptionBloc() : super(const AdoptionIdle()) { + on(_onRequested); + on((event, emit) => emit(const AdoptionIdle())); + } + + Future _onRequested(AdoptionRequested event, Emitter emit) async { + emit(const AdoptionSubmitting()); + await Future.delayed(const Duration(milliseconds: 300)); + emit(AdoptionDone('Thanks ${event.applicant}! We will be in touch about pet #${event.petId}.')); + } +} diff --git a/scripts/flutter-apps/pet_adoption/lib/blocs/pets_cubit.dart b/scripts/flutter-apps/pet_adoption/lib/blocs/pets_cubit.dart new file mode 100644 index 00000000000..cf1affeffb9 --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/blocs/pets_cubit.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../data/pet_repository.dart'; +import '../models/pet.dart'; + +enum PetsStatus { initial, loading, loaded, failure } + +class PetsState extends Equatable { + const PetsState({ + this.status = PetsStatus.initial, + this.pets = const [], + this.favorites = const {}, + this.filter, + }); + + final PetsStatus status; + final List pets; + final Set favorites; + final Species? filter; + + List get visible => filter == null ? pets : pets.where((p) => p.species == filter).toList(); + + PetsState copyWith({PetsStatus? status, List? pets, Set? favorites, Species? Function()? filter}) { + return PetsState( + status: status ?? this.status, + pets: pets ?? this.pets, + favorites: favorites ?? this.favorites, + filter: filter != null ? filter() : this.filter, + ); + } + + @override + List get props => [status, pets, favorites, filter]; +} + +class PetsCubit extends Cubit { + PetsCubit(this._repository) : super(const PetsState()); + + final PetRepository _repository; + + Future load() async { + emit(state.copyWith(status: PetsStatus.loading)); + try { + final pets = await _repository.fetchPets(); + final favorites = await _repository.loadFavorites(); + emit(state.copyWith(status: PetsStatus.loaded, pets: pets, favorites: favorites)); + } catch (_) { + emit(state.copyWith(status: PetsStatus.failure)); + } + } + + void setFilter(Species? species) => emit(state.copyWith(filter: () => species)); + + Future toggleFavorite(int id) async { + final next = Set.of(state.favorites); + if (!next.remove(id)) { + next.add(id); + } + emit(state.copyWith(favorites: next)); + await _repository.saveFavorites(next); + } +} diff --git a/scripts/flutter-apps/pet_adoption/lib/data/pet_repository.dart b/scripts/flutter-apps/pet_adoption/lib/data/pet_repository.dart new file mode 100644 index 00000000000..3689a683b8e --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/data/pet_repository.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../models/pet.dart'; + +class PetRepository { + PetRepository({Dio? dio}) + : _dio = dio ?? + Dio(BaseOptions( + baseUrl: 'https://example.com/api', + connectTimeout: const Duration(seconds: 5), + receiveTimeout: const Duration(seconds: 5), + )); + + final Dio _dio; + static const _favoritesKey = 'favorites'; + + Future> fetchPets() async { + try { + final response = await _dio.get>('/pets'); + final data = response.data ?? const []; + return data.map((e) => Pet.fromJson(e as Map)).toList(); + } on DioException { + // Offline: fall back to the bundled sample list. + final list = jsonDecode(_sample) as List; + return list.map((e) => Pet.fromJson(e as Map)).toList(); + } + } + + Future> loadFavorites() async { + final prefs = await SharedPreferences.getInstance(); + final ids = prefs.getStringList(_favoritesKey) ?? const []; + return ids.map(int.parse).toSet(); + } + + Future saveFavorites(Set ids) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList(_favoritesKey, ids.map((e) => '$e').toList()); + } +} + +const _sample = ''' +[ + {"id": 1, "name": "Biscuit", "species": "dog", "ageMonths": 8, "photoUrl": null, "listedAt": "2026-09-01T10:00:00.000"}, + {"id": 2, "name": "Miso", "species": "cat", "ageMonths": 30, "photoUrl": null, "listedAt": "2026-08-14T09:30:00.000"}, + {"id": 3, "name": "Clover", "species": "rabbit", "ageMonths": 14, "photoUrl": null, "listedAt": "2026-10-02T16:45:00.000"} +] +'''; diff --git a/scripts/flutter-apps/pet_adoption/lib/main.dart b/scripts/flutter-apps/pet_adoption/lib/main.dart new file mode 100644 index 00000000000..393e52ade5e --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/main.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +import 'blocs/pets_cubit.dart'; +import 'data/pet_repository.dart'; +import 'screens/pet_detail_screen.dart'; +import 'screens/pet_list_screen.dart'; + +void main() { + runApp(PetAdoptionApp(repository: PetRepository())); +} + +final _router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const PetListScreen(), + routes: [ + GoRoute( + path: 'pets/:id', + builder: (context, state) => PetDetailScreen(petId: int.parse(state.pathParameters['id']!)), + ), + ], + ), + ], +); + +class PetAdoptionApp extends StatelessWidget { + const PetAdoptionApp({super.key, required this.repository}); + + final PetRepository repository; + + @override + Widget build(BuildContext context) { + return RepositoryProvider.value( + value: repository, + child: BlocProvider( + create: (_) => PetsCubit(repository)..load(), + child: MaterialApp.router( + title: 'Pet adoption', + theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true), + routerConfig: _router, + ), + ), + ); + } +} diff --git a/scripts/flutter-apps/pet_adoption/lib/models/pet.dart b/scripts/flutter-apps/pet_adoption/lib/models/pet.dart new file mode 100644 index 00000000000..c30d9cdf06a --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/models/pet.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'pet.g.dart'; + +enum Species { dog, cat, rabbit } + +@JsonSerializable() +class Pet extends Equatable { + final int id; + final String name; + final Species species; + final int ageMonths; + final String? photoUrl; + final DateTime listedAt; + + const Pet({ + required this.id, + required this.name, + required this.species, + required this.ageMonths, + this.photoUrl, + required this.listedAt, + }); + + factory Pet.fromJson(Map json) => _$PetFromJson(json); + + Map toJson() => _$PetToJson(this); + + String get ageLabel => ageMonths < 12 ? '$ageMonths mo' : '${ageMonths ~/ 12} yr'; + + @override + List get props => [id, name, species, ageMonths, photoUrl, listedAt]; +} diff --git a/scripts/flutter-apps/pet_adoption/lib/models/pet.g.dart b/scripts/flutter-apps/pet_adoption/lib/models/pet.g.dart new file mode 100644 index 00000000000..a509ee5407f --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/models/pet.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pet.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Pet _$PetFromJson(Map json) => Pet( + id: (json['id'] as num).toInt(), + name: json['name'] as String, + species: $enumDecode(_$SpeciesEnumMap, json['species']), + ageMonths: (json['ageMonths'] as num).toInt(), + photoUrl: json['photoUrl'] as String?, + listedAt: DateTime.parse(json['listedAt'] as String), + ); + +Map _$PetToJson(Pet instance) => { + 'id': instance.id, + 'name': instance.name, + 'species': _$SpeciesEnumMap[instance.species]!, + 'ageMonths': instance.ageMonths, + 'photoUrl': instance.photoUrl, + 'listedAt': instance.listedAt.toIso8601String(), + }; + +const _$SpeciesEnumMap = { + Species.dog: 'dog', + Species.cat: 'cat', + Species.rabbit: 'rabbit', +}; diff --git a/scripts/flutter-apps/pet_adoption/lib/screens/pet_detail_screen.dart b/scripts/flutter-apps/pet_adoption/lib/screens/pet_detail_screen.dart new file mode 100644 index 00000000000..ce665585383 --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/screens/pet_detail_screen.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +import '../blocs/adoption_bloc.dart'; +import '../blocs/pets_cubit.dart'; + +class PetDetailScreen extends StatefulWidget { + const PetDetailScreen({super.key, required this.petId}); + + final int petId; + + @override + State createState() => _PetDetailScreenState(); +} + +class _PetDetailScreenState extends State { + final _name = TextEditingController(); + + @override + void dispose() { + _name.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final pets = context.select((PetsCubit c) => c.state.pets); + final matches = pets.where((p) => p.id == widget.petId); + if (matches.isEmpty) { + return Scaffold(appBar: AppBar(), body: const Center(child: Text('Pet not found'))); + } + final pet = matches.first; + return BlocProvider( + create: (_) => AdoptionBloc(), + child: Scaffold( + appBar: AppBar( + title: Text(pet.name), + leading: BackButton(onPressed: () => context.go('/')), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: BlocConsumer( + listener: (context, state) { + if (state is AdoptionDone) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(state.message))); + } + }, + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('${pet.name} is a ${pet.ageLabel} old ${pet.species.name}.', + style: Theme.of(context).textTheme.titleMedium), + const SizedBox(height: 24), + TextField(controller: _name, decoration: const InputDecoration(labelText: 'Your name')), + const SizedBox(height: 16), + FilledButton( + onPressed: state is AdoptionSubmitting + ? null + : () => context.read().add(AdoptionRequested(pet.id, _name.text)), + child: Text(state is AdoptionSubmitting ? 'Sending...' : 'Apply to adopt'), + ), + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/scripts/flutter-apps/pet_adoption/lib/screens/pet_list_screen.dart b/scripts/flutter-apps/pet_adoption/lib/screens/pet_list_screen.dart new file mode 100644 index 00000000000..b7504119ae0 --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/lib/screens/pet_list_screen.dart @@ -0,0 +1,75 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; + +import '../blocs/pets_cubit.dart'; +import '../models/pet.dart'; + +class PetListScreen extends StatelessWidget { + const PetListScreen({super.key}); + + static final _listed = DateFormat.yMMMd(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Adopt a pet'), + actions: [ + PopupMenuButton( + icon: const Icon(Icons.filter_list), + onSelected: (s) => context.read().setFilter(s), + itemBuilder: (context) => [ + const PopupMenuItem(value: null, child: Text('All')), + for (final s in Species.values) PopupMenuItem(value: s, child: Text(s.name)), + ], + ), + ], + ), + body: BlocBuilder( + builder: (context, state) { + switch (state.status) { + case PetsStatus.initial: + case PetsStatus.loading: + return const Center(child: CircularProgressIndicator()); + case PetsStatus.failure: + return Center( + child: TextButton( + onPressed: () => context.read().load(), + child: const Text('Could not load pets. Retry'), + ), + ); + case PetsStatus.loaded: + final pets = state.visible; + return RefreshIndicator( + onRefresh: () => context.read().load(), + child: ListView.separated( + itemCount: pets.length, + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (context, i) { + final pet = pets[i]; + final favorite = state.favorites.contains(pet.id); + return ListTile( + leading: CircleAvatar( + backgroundImage: pet.photoUrl == null ? null : CachedNetworkImageProvider(pet.photoUrl!), + child: pet.photoUrl == null ? Text(pet.name[0]) : null, + ), + title: Text(pet.name), + subtitle: Text('${pet.species.name}, ${pet.ageLabel} - listed ${_listed.format(pet.listedAt)}'), + trailing: IconButton( + icon: Icon(favorite ? Icons.favorite : Icons.favorite_border), + onPressed: () => context.read().toggleFavorite(pet.id), + ), + onTap: () => context.go('/pets/${pet.id}'), + ); + }, + ), + ); + } + }, + ), + ); + } +} diff --git a/scripts/flutter-apps/pet_adoption/pubspec.lock b/scripts/flutter-apps/pet_adoption/pubspec.lock new file mode 100644 index 00000000000..95e59ac963d --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/pubspec.lock @@ -0,0 +1,509 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + bloc: + dependency: "direct main" + description: + name: bloc + sha256: e18b8e7825e9921d67a6d256dba0b6015ece8a577eb0a411845c46a352994d78 + url: "https://pub.dev" + source: hosted + version: "9.0.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: c752e2d08d088bf83742cb05bf83003f3e9d276ff1519b5c92f9d5e60e5ddd23 + url: "https://pub.dev" + source: hosted + version: "16.2.4" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + url: "https://pub.dev" + source: hosted + version: "2.2.23" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "0b0f98d535319cb5cdd4f65783c2a54ee6d417a2f093dbb18be3e36e4c3d181f" + url: "https://pub.dev" + source: hosted + version: "2.4.14" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/scripts/flutter-apps/pet_adoption/pubspec.yaml b/scripts/flutter-apps/pet_adoption/pubspec.yaml new file mode 100644 index 00000000000..decef2165a7 --- /dev/null +++ b/scripts/flutter-apps/pet_adoption/pubspec.yaml @@ -0,0 +1,23 @@ +name: pet_adoption +description: An ordinary Flutter app -- bloc, go_router, dio, shared_preferences -- used to check that Codename One imports such apps unmodified. +publish_to: none +version: 1.0.0+1 + +environment: + sdk: ">=3.4.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^9.1.0 + bloc: ^9.0.0 + equatable: ^2.0.5 + go_router: ^16.0.0 + dio: ^5.8.0 + shared_preferences: ^2.5.0 + cached_network_image: ^3.4.0 + intl: ^0.20.0 + json_annotation: ^4.9.0 + +flutter: + uses-material-design: true diff --git a/scripts/flutter-apps/run-acceptance.sh b/scripts/flutter-apps/run-acceptance.sh new file mode 100755 index 00000000000..556c4478144 --- /dev/null +++ b/scripts/flutter-apps/run-acceptance.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Imports, transpiles and compiles every app under scripts/flutter-apps with no +# edits (see Acceptance.java). Needs JDK 17 as JAVA17_HOME (or java on PATH), +# `flutter` on PATH for `pub get`, and the runtime modules already built: +# cd maven && mvn -B install -pl core,dart-runtime,flutter-runtime,dart-transpiler -am -DskipTests +# Usage: run-acceptance.sh [app-dir ...] (default: every app here) +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$HERE/../.." && pwd)" +M="$ROOT/maven" +JAVA_BIN="${JAVA17_HOME:+$JAVA17_HOME/bin/}" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +ANTLR="$(find "${MAVEN_REPO_LOCAL:-$HOME/.m2/repository}/org/antlr/antlr4-runtime" -name 'antlr4-runtime-*.jar' | sort | tail -1)" +if [ -z "$ANTLR" ]; then + echo "antlr4-runtime not found; build maven/dart-transpiler first" >&2 + exit 1 +fi + +# The importer is compiled from source: build-engine's Maven build drags in the +# whole JavaSE port, and the importer itself needs only project-model. +mkdir -p "$WORK/importer" +"${JAVA_BIN}javac" -nowarn -d "$WORK/importer" \ + -sourcepath "$M/build-engine/src/main/java:$M/project-model/src/main/java" \ + "$M/build-engine/src/main/java/com/codename1/maven/FlutterProjectImporter.java" + +CP="$WORK/importer:$M/dart-transpiler/target/classes:$ANTLR" +RT="$M/core/target/classes:$M/dart-runtime/target/classes:$M/flutter-runtime/target/classes" + +if [ "$#" -eq 0 ]; then + set -- $(find "$HERE" -mindepth 2 -maxdepth 2 -name pubspec.yaml -exec dirname {} \; | sort) +fi + +failed=0 +for app in "$@"; do + name="$(basename "$app")" + echo "::group::$name" + (cd "$app" && flutter pub get) + if "${JAVA_BIN}java" -cp "$CP" "$HERE/Acceptance.java" "$app" "$WORK/$name" \ + "$M/flutter-runtime/target/classes" "$RT"; then + echo "$name: ACCEPTED" + else + echo "::error::$name was not accepted" + failed=1 + fi + echo "::endgroup::" +done +exit "$failed" diff --git a/scripts/flutter-bench/run_bench.py b/scripts/flutter-bench/run_bench.py index 77583e9b408..833ec5a5b74 100755 --- a/scripts/flutter-bench/run_bench.py +++ b/scripts/flutter-bench/run_bench.py @@ -259,7 +259,7 @@ def main(argv=None): for line in benchlib.render_regressions(adapter.id, report.get("advisories") or []): print("note: " + line) - if report.get("advisories"): + if report.get("advisories") and report["gate"].get("fix"): print("note: to tighten: %s" % report["gate"]["fix"]) if findings or stale: print("\nGATE FAILED") @@ -267,7 +267,9 @@ def main(argv=None): print(" " + line) if stale: print(" this pull request's own overlay is stale: %s" % report["gate"]["stale_overlay"]) - if report.get("regressions") or stale: + # No "fix" when the baselines could not be read at all: the message above is + # the whole answer, and a KeyError here used to bury it under a traceback. + if (report.get("regressions") or stale) and report["gate"].get("fix"): print(" to accept the move: %s" % report["gate"]["fix"]) return 1 return 0