diff --git a/mobile/.editorconfig b/mobile/.editorconfig new file mode 100644 index 0000000..9d4c2fa --- /dev/null +++ b/mobile/.editorconfig @@ -0,0 +1,2 @@ +[*.dart] +indent_size = 2 diff --git a/mobile/.flutter_rust_bridge.yml b/mobile/.flutter_rust_bridge.yml new file mode 100644 index 0000000..f2a5bc8 --- /dev/null +++ b/mobile/.flutter_rust_bridge.yml @@ -0,0 +1,7 @@ +rust_input: + - native/src/api.rs +dart_output: + - lib/bridge_generated.dart +dart_decl_output: lib/bridge_definitions.dart +dart_format_line_length: 120 +wasm: false diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..09360f3 --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1,51 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release +native/target +xcuserdata +jniLibs + +Cargo.lock +pubspec.lock diff --git a/mobile/.metadata b/mobile/.metadata new file mode 100644 index 0000000..105695d --- /dev/null +++ b/mobile/.metadata @@ -0,0 +1,44 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 9944297138845a94256f1cf37beb88ff9a8e811a + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + - platform: android + create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + - platform: ios + create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + - platform: linux + create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + - platform: macos + create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + - platform: web + create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + - platform: windows + create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' diff --git a/mobile/.tool-versions b/mobile/.tool-versions new file mode 100644 index 0000000..fd85870 --- /dev/null +++ b/mobile/.tool-versions @@ -0,0 +1 @@ +java liberica-8u322+6 diff --git a/mobile/LICENSE-MIT b/mobile/LICENSE-MIT new file mode 100644 index 0000000..ff9f307 --- /dev/null +++ b/mobile/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright 2022 Viet Dinh. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/mobile/LICENSE.md b/mobile/LICENSE.md new file mode 100644 index 0000000..23034d6 --- /dev/null +++ b/mobile/LICENSE.md @@ -0,0 +1,3 @@ +This portion of the software is covered under the terms of the [Chaos +License](../LICENSE.md). Portions of the code in this mobile module are used under the terms of the +[MIT License](./LICENSE-MIT). diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/mobile/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/mobile/android/.gitignore b/mobile/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/mobile/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle new file mode 100644 index 0000000..8c5a631 --- /dev/null +++ b/mobile/android/app/build.gradle @@ -0,0 +1,99 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.nebcorp_hias.cuttle" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} + +[ + Debug: null, + Profile: '--release', + Release: '--release' +].each { + def taskPostfix = it.key + def profileMode = it.value + tasks.whenTaskAdded { task -> + if (task.name == "javaPreCompile$taskPostfix") { + task.dependsOn "cargoBuild$taskPostfix" + } + } + tasks.register("cargoBuild$taskPostfix", Exec) { + workingDir "../../native" + environment ANDROID_NDK_HOME: "$ANDROID_NDK" + commandLine 'cargo', 'ndk', + // the 2 ABIs below are used by real Android devices + '-t', 'armeabi-v7a', + '-t', 'arm64-v8a', + // for the simulator + '-t', 'x86_64', + '-o', '../android/app/src/main/jniLibs', 'build' + if (profileMode != null) { + args profileMode + } + } +} diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..9b15c7c --- /dev/null +++ b/mobile/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9d3008a --- /dev/null +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/kotlin/main/com/nebcorp-hias/cuttle/MainActivity.kt b/mobile/android/app/src/main/kotlin/main/com/nebcorp-hias/cuttle/MainActivity.kt new file mode 100644 index 0000000..10bcd27 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/main/com/nebcorp-hias/cuttle/MainActivity.kt @@ -0,0 +1,6 @@ +package com.nebcorp_hias.cuttle + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/android/app/src/main/res/drawable/launch_background.xml b/mobile/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/mobile/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobile/android/app/src/main/res/values-night/styles.xml b/mobile/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/mobile/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/android/app/src/main/res/values/styles.xml b/mobile/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/mobile/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..9b15c7c --- /dev/null +++ b/mobile/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle new file mode 100644 index 0000000..713d7f6 --- /dev/null +++ b/mobile/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/mobile/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/mobile/android/settings.gradle b/mobile/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/mobile/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/mobile/justfile b/mobile/justfile new file mode 100644 index 0000000..a16d174 --- /dev/null +++ b/mobile/justfile @@ -0,0 +1,18 @@ +default: gen lint + +gen: + flutter pub get + flutter_rust_bridge_codegen + +lint: + cd native && cargo fmt + dart format . + +clean: + flutter clean + cd native && cargo clean + +serve *args='': + flutter pub run flutter_rust_bridge:serve {{args}} + +# vim:expandtab:sw=4:ts=4 diff --git a/mobile/lib/bridge_definitions.dart b/mobile/lib/bridge_definitions.dart new file mode 100644 index 0000000..a82851a --- /dev/null +++ b/mobile/lib/bridge_definitions.dart @@ -0,0 +1,30 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const + +import 'dart:convert'; +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; + +abstract class Native { + Future platform({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kPlatformConstMeta; + + Future rustReleaseMode({dynamic hint}); + + FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta; +} + +enum Platform { + Unknown, + AndroidBish, + Ios, + Windows, + Unix, + MacIntel, + MacApple, + Wasm, +} diff --git a/mobile/lib/bridge_generated.dart b/mobile/lib/bridge_generated.dart new file mode 100644 index 0000000..bdbc6a2 --- /dev/null +++ b/mobile/lib/bridge_generated.dart @@ -0,0 +1,207 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const + +import "bridge_definitions.dart"; +import 'dart:convert'; +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; + +import 'dart:convert'; +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; + +import 'dart:ffi' as ffi; + +class NativeImpl implements Native { + final NativePlatform _platform; + factory NativeImpl(ExternalLibrary dylib) => NativeImpl.raw(NativePlatform(dylib)); + + /// Only valid on web/WASM platforms. + factory NativeImpl.wasm(FutureOr module) => NativeImpl(module as ExternalLibrary); + NativeImpl.raw(this._platform); + Future platform({dynamic hint}) { + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_platform(port_), + parseSuccessData: _wire2api_platform, + constMeta: kPlatformConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kPlatformConstMeta => const FlutterRustBridgeTaskConstMeta( + debugName: "platform", + argNames: [], + ); + + Future rustReleaseMode({dynamic hint}) { + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_rust_release_mode(port_), + parseSuccessData: _wire2api_bool, + constMeta: kRustReleaseModeConstMeta, + argValues: [], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta => const FlutterRustBridgeTaskConstMeta( + debugName: "rust_release_mode", + argNames: [], + ); + + void dispose() { + _platform.dispose(); + } +// Section: wire2api + + bool _wire2api_bool(dynamic raw) { + return raw as bool; + } + + int _wire2api_i32(dynamic raw) { + return raw as int; + } + + Platform _wire2api_platform(dynamic raw) { + return Platform.values[raw as int]; + } +} + +// Section: api2wire + +// Section: finalizer + +class NativePlatform extends FlutterRustBridgeBase { + NativePlatform(ffi.DynamicLibrary dylib) : super(NativeWire(dylib)); + +// Section: api2wire + +// Section: finalizer + +// Section: api_fill_to_wire +} + +// ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint + +/// generated by flutter_rust_bridge +class NativeWire implements FlutterRustBridgeWireBase { + @internal + late final dartApi = DartApiDl(init_frb_dart_api_dl); + + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + NativeWire(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + NativeWire.fromLookup(ffi.Pointer Function(String symbolName) lookup) : _lookup = lookup; + + void store_dart_post_cobject( + DartPostCObjectFnType ptr, + ) { + return _store_dart_post_cobject( + ptr, + ); + } + + late final _store_dart_post_cobjectPtr = + _lookup>('store_dart_post_cobject'); + late final _store_dart_post_cobject = _store_dart_post_cobjectPtr.asFunction(); + + Object get_dart_object( + int ptr, + ) { + return _get_dart_object( + ptr, + ); + } + + late final _get_dart_objectPtr = _lookup>('get_dart_object'); + late final _get_dart_object = _get_dart_objectPtr.asFunction(); + + void drop_dart_object( + int ptr, + ) { + return _drop_dart_object( + ptr, + ); + } + + late final _drop_dart_objectPtr = _lookup>('drop_dart_object'); + late final _drop_dart_object = _drop_dart_objectPtr.asFunction(); + + int new_dart_opaque( + Object handle, + ) { + return _new_dart_opaque( + handle, + ); + } + + late final _new_dart_opaquePtr = _lookup>('new_dart_opaque'); + late final _new_dart_opaque = _new_dart_opaquePtr.asFunction(); + + int init_frb_dart_api_dl( + ffi.Pointer obj, + ) { + return _init_frb_dart_api_dl( + obj, + ); + } + + late final _init_frb_dart_api_dlPtr = + _lookup)>>('init_frb_dart_api_dl'); + late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr.asFunction)>(); + + void wire_platform( + int port_, + ) { + return _wire_platform( + port_, + ); + } + + late final _wire_platformPtr = _lookup>('wire_platform'); + late final _wire_platform = _wire_platformPtr.asFunction(); + + void wire_rust_release_mode( + int port_, + ) { + return _wire_rust_release_mode( + port_, + ); + } + + late final _wire_rust_release_modePtr = + _lookup>('wire_rust_release_mode'); + late final _wire_rust_release_mode = _wire_rust_release_modePtr.asFunction(); + + void free_WireSyncReturn( + WireSyncReturn ptr, + ) { + return _free_WireSyncReturn( + ptr, + ); + } + + late final _free_WireSyncReturnPtr = + _lookup>('free_WireSyncReturn'); + late final _free_WireSyncReturn = _free_WireSyncReturnPtr.asFunction(); +} + +final class _Dart_Handle extends ffi.Opaque {} + +typedef DartPostCObjectFnType + = ffi.Pointer message)>>; +typedef DartPort = ffi.Int64; diff --git a/mobile/lib/bridge_generated.io.dart b/mobile/lib/bridge_generated.io.dart new file mode 100644 index 0000000..ae84555 --- /dev/null +++ b/mobile/lib/bridge_generated.io.dart @@ -0,0 +1,169 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const + +import "bridge_definitions.dart"; +import 'dart:convert'; +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; +import 'bridge_generated.dart'; +export 'bridge_generated.dart'; +import 'dart:ffi' as ffi; + +class NativePlatform extends FlutterRustBridgeBase { + NativePlatform(ffi.DynamicLibrary dylib) : super(NativeWire(dylib)); + +// Section: api2wire + +// Section: finalizer + +// Section: api_fill_to_wire +} + +// ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint + +/// generated by flutter_rust_bridge +class NativeWire implements FlutterRustBridgeWireBase { + @internal + late final dartApi = DartApiDl(init_frb_dart_api_dl); + + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + NativeWire(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + NativeWire.fromLookup( + ffi.Pointer Function(String symbolName) + lookup) + : _lookup = lookup; + + void store_dart_post_cobject( + DartPostCObjectFnType ptr, + ) { + return _store_dart_post_cobject( + ptr, + ); + } + + late final _store_dart_post_cobjectPtr = + _lookup>( + 'store_dart_post_cobject'); + late final _store_dart_post_cobject = _store_dart_post_cobjectPtr + .asFunction(); + + Object get_dart_object( + int ptr, + ) { + return _get_dart_object( + ptr, + ); + } + + late final _get_dart_objectPtr = + _lookup>( + 'get_dart_object'); + late final _get_dart_object = + _get_dart_objectPtr.asFunction(); + + void drop_dart_object( + int ptr, + ) { + return _drop_dart_object( + ptr, + ); + } + + late final _drop_dart_objectPtr = + _lookup>( + 'drop_dart_object'); + late final _drop_dart_object = + _drop_dart_objectPtr.asFunction(); + + int new_dart_opaque( + Object handle, + ) { + return _new_dart_opaque( + handle, + ); + } + + late final _new_dart_opaquePtr = + _lookup>( + 'new_dart_opaque'); + late final _new_dart_opaque = + _new_dart_opaquePtr.asFunction(); + + int init_frb_dart_api_dl( + ffi.Pointer obj, + ) { + return _init_frb_dart_api_dl( + obj, + ); + } + + late final _init_frb_dart_api_dlPtr = + _lookup)>>( + 'init_frb_dart_api_dl'); + late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr + .asFunction)>(); + + void wire_platform( + int port_, + ) { + return _wire_platform( + port_, + ); + } + + late final _wire_platformPtr = + _lookup>( + 'wire_platform'); + late final _wire_platform = + _wire_platformPtr.asFunction(); + + void wire_rust_release_mode( + int port_, + ) { + return _wire_rust_release_mode( + port_, + ); + } + + late final _wire_rust_release_modePtr = + _lookup>( + 'wire_rust_release_mode'); + late final _wire_rust_release_mode = + _wire_rust_release_modePtr.asFunction(); + + void free_WireSyncReturn( + WireSyncReturn ptr, + ) { + return _free_WireSyncReturn( + ptr, + ); + } + + late final _free_WireSyncReturnPtr = + _lookup>( + 'free_WireSyncReturn'); + late final _free_WireSyncReturn = + _free_WireSyncReturnPtr.asFunction(); +} + +final class _Dart_Handle extends ffi.Opaque {} + +typedef DartPostCObjectFnType = ffi.Pointer< + ffi.NativeFunction< + ffi.Bool Function(DartPort port_id, ffi.Pointer message)>>; +typedef DartPort = ffi.Int64; diff --git a/mobile/lib/ffi.dart b/mobile/lib/ffi.dart new file mode 100644 index 0000000..5e469a7 --- /dev/null +++ b/mobile/lib/ffi.dart @@ -0,0 +1,18 @@ +// This file initializes the dynamic library and connects it with the stub +// generated by flutter_rust_bridge_codegen. + +import 'dart:ffi'; + +import 'bridge_generated.dart'; +import 'bridge_definitions.dart'; +export 'bridge_definitions.dart'; + +// Re-export the bridge so it is only necessary to import this file. +export 'bridge_generated.dart'; +import 'dart:io' as io; + +const _base = 'native'; + +const _dylib = 'lib$_base.so'; + +final Native api = NativeImpl(DynamicLibrary.open(_dylib)); diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart new file mode 100644 index 0000000..5e2192a --- /dev/null +++ b/mobile/lib/main.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'ffi.dart' if (dart.library.html) 'ffi_web.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + // These futures belong to the state and are only initialized once, + // in the initState method. + late Future platform; + late Future isRelease; + + @override + void initState() { + super.initState(); + platform = api.platform(); + isRelease = api.rustReleaseMode(); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("You're running on"), + // To render the results of a Future, a FutureBuilder is used which + // turns a Future into an AsyncSnapshot, which can be used to + // extract the error state, the loading state and the data if + // available. + // + // Here, the generic type that the FutureBuilder manages is + // explicitly named, because if omitted the snapshot will have the + // type of AsyncSnapshot. + FutureBuilder>( + // We await two unrelated futures here, so the type has to be + // List. + future: Future.wait([platform, isRelease]), + builder: (context, snap) { + final style = Theme.of(context).textTheme.headlineMedium; + if (snap.error != null) { + // An error has been encountered, so give an appropriate response and + // pass the error details to an unobstructive tooltip. + debugPrint(snap.error.toString()); + return Tooltip( + message: snap.error.toString(), + child: Text('Unknown OS', style: style), + ); + } + + // Guard return here, the data is not ready yet. + final data = snap.data; + if (data == null) return const CircularProgressIndicator(); + + // Finally, retrieve the data expected in the same order provided + // to the FutureBuilder.future. + final Platform platform = data[0]; + final release = data[1] ? 'Release' : 'Debug'; + final text = const { + Platform.AndroidBish: 'Android, bish', + Platform.Ios: 'iOS', + Platform.MacApple: 'MacOS with Apple Silicon', + Platform.MacIntel: 'MacOS', + Platform.Windows: 'Windows', + Platform.Unix: 'Unix', + Platform.Wasm: 'the Web', + }[platform] ?? + 'Unknown OS'; + return Text('$text ($release)', style: style); + }, + ) + ], + ), + ), + ); + } +} diff --git a/mobile/native/.rustfmt.toml b/mobile/native/.rustfmt.toml new file mode 100644 index 0000000..4c8d0e1 --- /dev/null +++ b/mobile/native/.rustfmt.toml @@ -0,0 +1,4 @@ +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +wrap_comments = true +edition = "2021" diff --git a/mobile/native/Cargo.toml b/mobile/native/Cargo.toml new file mode 100644 index 0000000..fdf3002 --- /dev/null +++ b/mobile/native/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "native" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +anyhow = "1" +flutter_rust_bridge = "1" diff --git a/mobile/native/src/api.rs b/mobile/native/src/api.rs new file mode 100644 index 0000000..366517e --- /dev/null +++ b/mobile/native/src/api.rs @@ -0,0 +1,59 @@ +// This is the entry point of your Rust library. +// When adding new code to your project, note that only items used +// here will be transformed to their Dart equivalents. + +// A plain enum without any fields. This is similar to Dart- or C-style enums. +// flutter_rust_bridge is capable of generating code for enums with fields +// (@freezed classes in Dart and tagged unions in C). +pub enum Platform { + Unknown, + AndroidBish, + Ios, + Windows, + Unix, + MacIntel, + MacApple, + Wasm, +} + +// A function definition in Rust. Similar to Dart, the return type must always be named +// and is never inferred. +pub fn platform() -> Platform { + // This is a macro, a special expression that expands into code. In Rust, all macros + // end with an exclamation mark and can be invoked with all kinds of brackets (parentheses, + // brackets and curly braces). However, certain conventions exist, for example the + // vector macro is almost always invoked as vec![..]. + // + // The cfg!() macro returns a boolean value based on the current compiler configuration. + // When attached to expressions (#[cfg(..)] form), they show or hide the expression at compile time. + // Here, however, they evaluate to runtime values, which may or may not be optimized out + // by the compiler. A variety of configurations are demonstrated here which cover most of + // the modern oeprating systems. Try running the Flutter application on different machines + // and see if it matches your expected OS. + // + // Furthermore, in Rust, the last expression in a function is the return value and does + // not have the trailing semicolon. This entire if-else chain forms a single expression. + if cfg!(windows) { + Platform::Windows + } else if cfg!(target_os = "android") { + Platform::AndroidBish + } else if cfg!(target_os = "ios") { + Platform::Ios + } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) { + Platform::MacApple + } else if cfg!(target_os = "macos") { + Platform::MacIntel + } else if cfg!(target_family = "wasm") { + Platform::Wasm + } else if cfg!(unix) { + Platform::Unix + } else { + Platform::Unknown + } +} + +// The convention for Rust identifiers is the snake_case, +// and they are automatically converted to camelCase on the Dart side. +pub fn rust_release_mode() -> bool { + cfg!(not(debug_assertions)) +} diff --git a/mobile/native/src/bridge_generated.io.rs b/mobile/native/src/bridge_generated.io.rs new file mode 100644 index 0000000..a4ec960 --- /dev/null +++ b/mobile/native/src/bridge_generated.io.rs @@ -0,0 +1,41 @@ +use super::*; +// Section: wire functions + +#[no_mangle] +pub extern "C" fn wire_platform(port_: i64) { + wire_platform_impl(port_) +} + +#[no_mangle] +pub extern "C" fn wire_rust_release_mode(port_: i64) { + wire_rust_release_mode_impl(port_) +} + +// Section: allocate functions + +// Section: related functions + +// Section: impl Wire2Api + +// Section: wire structs + +// Section: impl NewWithNullPtr + +pub trait NewWithNullPtr { + fn new_with_null_ptr() -> Self; +} + +impl NewWithNullPtr for *mut T { + fn new_with_null_ptr() -> Self { + std::ptr::null_mut() + } +} + +// Section: sync execution mode utility + +#[no_mangle] +pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) { + unsafe { + let _ = support::box_from_leak_ptr(ptr); + }; +} diff --git a/mobile/native/src/bridge_generated.rs b/mobile/native/src/bridge_generated.rs new file mode 100644 index 0000000..0ede1cf --- /dev/null +++ b/mobile/native/src/bridge_generated.rs @@ -0,0 +1,101 @@ +#![allow( + non_camel_case_types, + unused, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::double_parens, + non_snake_case, + clippy::too_many_arguments +)] +// AUTO GENERATED FILE, DO NOT EDIT. +// Generated by `flutter_rust_bridge`@ 1.81.0. + +use core::panic::UnwindSafe; +use std::{ffi::c_void, sync::Arc}; + +use flutter_rust_bridge::{rust2dart::IntoIntoDart, *}; + +use crate::api::*; + +// Section: imports + +// Section: wire functions + +fn wire_platform_impl(port_: MessagePort) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Platform>( + WrapInfo { + debug_name: "platform", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(platform()), + ) +} +fn wire_rust_release_mode_impl(port_: MessagePort) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, bool>( + WrapInfo { + debug_name: "rust_release_mode", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(rust_release_mode()), + ) +} +// Section: wrapper structs + +// Section: static checks + +// Section: allocate functions + +// Section: related functions + +// Section: impl Wire2Api + +pub trait Wire2Api { + fn wire2api(self) -> T; +} + +impl Wire2Api> for *mut S +where + *mut S: Wire2Api, +{ + fn wire2api(self) -> Option { + (!self.is_null()).then(|| self.wire2api()) + } +} +// Section: impl IntoDart + +impl support::IntoDart for Platform { + fn into_dart(self) -> support::DartAbi { + match self { + Self::Unknown => 0, + Self::AndroidBish => 1, + Self::Ios => 2, + Self::Windows => 3, + Self::Unix => 4, + Self::MacIntel => 5, + Self::MacApple => 6, + Self::Wasm => 7, + } + .into_dart() + } +} +impl support::IntoDartExceptPrimitive for Platform {} +impl rust2dart::IntoIntoDart for Platform { + fn into_into_dart(self) -> Self { + self + } +} + +// Section: executor + +support::lazy_static! { + pub static ref FLUTTER_RUST_BRIDGE_HANDLER: support::DefaultHandler = Default::default(); +} + +#[cfg(not(target_family = "wasm"))] +#[path = "bridge_generated.io.rs"] +mod io; +#[cfg(not(target_family = "wasm"))] +pub use io::*; diff --git a/mobile/native/src/lib.rs b/mobile/native/src/lib.rs new file mode 100644 index 0000000..dfdd872 --- /dev/null +++ b/mobile/native/src/lib.rs @@ -0,0 +1,2 @@ +mod api; +mod bridge_generated; diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml new file mode 100644 index 0000000..672dd91 --- /dev/null +++ b/mobile/pubspec.yaml @@ -0,0 +1,94 @@ +name: cuttle +description: Optical data transfer with QR codes. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + ffi: ^2.0.1 + flutter_rust_bridge: ^1.45.0 + meta: ^1.8.0 + uuid: ^3.0.7 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + ffigen: ">=8.0.0 <9.0.0" + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/mobile/test/widget_test.dart b/mobile/test/widget_test.dart new file mode 100644 index 0000000..000f168 --- /dev/null +++ b/mobile/test/widget_test.dart @@ -0,0 +1,6 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Your tests go here. +}