From 525f78984447cb7bdd8720ccdaf71932e68a126a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=9C=D0=B0=D1=80?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2?= Date: Thu, 21 Dec 2023 12:20:08 +0300 Subject: [PATCH] main interfaces --- .gitignore | 29 ++ .gitmodules | 3 + .metadata | 33 +++ CHANGELOG.md | 3 + LICENSE | 1 + analysis_options.yaml | 4 + aurora/CMakeLists.txt | 34 +++ aurora/include/secure_storage/globals.h | 10 + .../secure_storage/secure_storage_plugin.h | 19 ++ aurora/secure_storage_plugin.cpp | 26 ++ aurora/sstore | 1 + example/.gitignore | 46 +++ example/README.md | 16 ++ example/analysis_options.yaml | 28 ++ example/aurora/.gitignore | 1 + example/aurora/CMakeLists.txt | 47 +++ ...com.example.secure_storage_example.desktop | 12 + example/aurora/icons/108x108.png | Bin 0 -> 9954 bytes example/aurora/icons/128x128.png | Bin 0 -> 13645 bytes example/aurora/icons/172x172.png | Bin 0 -> 23377 bytes example/aurora/icons/86x86.png | Bin 0 -> 6632 bytes example/aurora/main.cpp | 9 + .../com.example.secure_storage_example.spec | 32 +++ .../plugin_integration_test.dart | 25 ++ example/lib/main.dart | 63 +++++ example/linux/.gitignore | 1 + example/linux/CMakeLists.txt | 147 ++++++++++ example/linux/flutter/CMakeLists.txt | 88 ++++++ example/linux/main.cc | 6 + example/linux/my_application.cc | 104 +++++++ example/linux/my_application.h | 18 ++ example/pubspec.lock | 267 ++++++++++++++++++ example/pubspec.yaml | 85 ++++++ example/test/widget_test.dart | 27 ++ lib/secure_storage.dart | 8 + lib/secure_storage_method_channel.dart | 17 ++ lib/secure_storage_platform_interface.dart | 29 ++ linux/CMakeLists.txt | 94 ++++++ .../secure_storage/secure_storage_plugin.h | 26 ++ linux/secure_storage_plugin.cc | 76 +++++ linux/secure_storage_plugin_private.h | 10 + linux/test/secure_storage_plugin_test.cc | 31 ++ pubspec.yaml | 71 +++++ test/secure_storage_method_channel_test.dart | 27 ++ test/secure_storage_test.dart | 29 ++ 45 files changed, 1603 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .metadata create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 analysis_options.yaml create mode 100644 aurora/CMakeLists.txt create mode 100644 aurora/include/secure_storage/globals.h create mode 100644 aurora/include/secure_storage/secure_storage_plugin.h create mode 100644 aurora/secure_storage_plugin.cpp create mode 160000 aurora/sstore create mode 100644 example/.gitignore create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/aurora/.gitignore create mode 100644 example/aurora/CMakeLists.txt create mode 100644 example/aurora/desktop/com.example.secure_storage_example.desktop create mode 100644 example/aurora/icons/108x108.png create mode 100644 example/aurora/icons/128x128.png create mode 100644 example/aurora/icons/172x172.png create mode 100644 example/aurora/icons/86x86.png create mode 100644 example/aurora/main.cpp create mode 100644 example/aurora/rpm/com.example.secure_storage_example.spec create mode 100644 example/integration_test/plugin_integration_test.dart create mode 100644 example/lib/main.dart create mode 100644 example/linux/.gitignore create mode 100644 example/linux/CMakeLists.txt create mode 100644 example/linux/flutter/CMakeLists.txt create mode 100644 example/linux/main.cc create mode 100644 example/linux/my_application.cc create mode 100644 example/linux/my_application.h create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml create mode 100644 example/test/widget_test.dart create mode 100644 lib/secure_storage.dart create mode 100644 lib/secure_storage_method_channel.dart create mode 100644 lib/secure_storage_platform_interface.dart create mode 100644 linux/CMakeLists.txt create mode 100644 linux/include/secure_storage/secure_storage_plugin.h create mode 100644 linux/secure_storage_plugin.cc create mode 100644 linux/secure_storage_plugin_private.h create mode 100644 linux/test/secure_storage_plugin_test.cc create mode 100644 pubspec.yaml create mode 100644 test/secure_storage_method_channel_test.dart create mode 100644 test/secure_storage_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac5aa98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# 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 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8efb79d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "aurora/sstore"] + path = aurora/sstore + url = git@git.markow.su:aurora/sstore.git diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..13f0ada --- /dev/null +++ b/.metadata @@ -0,0 +1,33 @@ +# 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 and should not be manually edited. + +version: + revision: "d3e00ac70443dbc0df7d0b56bff94c18d760b02f" + channel: "master" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d3e00ac70443dbc0df7d0b56bff94c18d760b02f + base_revision: d3e00ac70443dbc0df7d0b56bff94c18d760b02f + - platform: linux + create_revision: d3e00ac70443dbc0df7d0b56bff94c18d760b02f + base_revision: d3e00ac70443dbc0df7d0b56bff94c18d760b02f + - platform: aurora + create_revision: d3e00ac70443dbc0df7d0b56bff94c18d760b02f + base_revision: d3e00ac70443dbc0df7d0b56bff94c18d760b02f + + # 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' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/aurora/CMakeLists.txt b/aurora/CMakeLists.txt new file mode 100644 index 0000000..13fbd23 --- /dev/null +++ b/aurora/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.10) + +set(PROJECT_NAME secure_storage) +set(PLUGIN_NAME secure_storage_platform_plugin) + +project(${PROJECT_NAME} LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-psabi") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +set(CMAKE_AUTOMOC ON) + +find_package(PkgConfig REQUIRED) +find_package(Qt5 COMPONENTS Core DBus REQUIRED) +pkg_check_modules(FlutterEmbedder REQUIRED IMPORTED_TARGET flutter-embedder) + +add_subdirectory(sstore) +add_library(${PLUGIN_NAME} SHARED + secure_storage_plugin.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sstore/include/datastorage.h + ${CMAKE_CURRENT_SOURCE_DIR}/sstore/datastorage.cpp) + +set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::FlutterEmbedder) +target_link_libraries(${PLUGIN_NAME} PUBLIC Qt5::Core Qt5::DBus) + +target_include_directories(${PLUGIN_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${SStoreInclude}) +target_compile_definitions(${PLUGIN_NAME} PRIVATE PLUGIN_IMPL) diff --git a/aurora/include/secure_storage/globals.h b/aurora/include/secure_storage/globals.h new file mode 100644 index 0000000..e2026b0 --- /dev/null +++ b/aurora/include/secure_storage/globals.h @@ -0,0 +1,10 @@ +#ifndef FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_GLOBALS_H +#define FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_GLOBALS_H + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +#endif /* FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_GLOBALS_H */ diff --git a/aurora/include/secure_storage/secure_storage_plugin.h b/aurora/include/secure_storage/secure_storage_plugin.h new file mode 100644 index 0000000..f430dfc --- /dev/null +++ b/aurora/include/secure_storage/secure_storage_plugin.h @@ -0,0 +1,19 @@ +#ifndef FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_H +#define FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_H + +#include +#include +#include + +class PLUGIN_EXPORT SecureStoragePlugin final : public PluginInterface +{ +public: + void RegisterWithRegistrar(PluginRegistrar ®istrar) override; + +private: + DataStorage storage{}; + void onMethodCall(const MethodCall &call); + void unimplemented(const MethodCall &call); +}; + +#endif /* FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_H */ diff --git a/aurora/secure_storage_plugin.cpp b/aurora/secure_storage_plugin.cpp new file mode 100644 index 0000000..0d87cc7 --- /dev/null +++ b/aurora/secure_storage_plugin.cpp @@ -0,0 +1,26 @@ +#include +#include + +void SecureStoragePlugin::RegisterWithRegistrar(PluginRegistrar ®istrar) +{ + registrar.RegisterMethodChannel("secure_storage", + MethodCodecType::Standard, + [this](const MethodCall &call) { this->onMethodCall(call); }); +} + +void SecureStoragePlugin::onMethodCall(const MethodCall &call) +{ + const auto &method = call.GetMethod(); + + if (method == "available") { + call.SendSuccessResponse(storage.available()); + return; + } + + unimplemented(call); +} + +void SecureStoragePlugin::unimplemented(const MethodCall &call) +{ + call.SendSuccessResponse(nullptr); +} diff --git a/aurora/sstore b/aurora/sstore new file mode 160000 index 0000000..321766b --- /dev/null +++ b/aurora/sstore @@ -0,0 +1 @@ +Subproject commit 321766b792a3e930cb2c175082be0bff5cab5ba1 diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..4ebbe92 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# 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 +.pub-cache/ +.pub/ +/build/ + +# 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 + +# Aurora generated +/aurora/flutter diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..16231f1 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# secure_storage_example + +Demonstrates how to use the secure_storage plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# 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.dev/lints. + # + # 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/example/aurora/.gitignore b/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/aurora/CMakeLists.txt b/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..eade80d --- /dev/null +++ b/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.secure_storage_example LANGUAGES CXX) + +include(GNUInstallDirs) + +set(BINARY_NAME ${CMAKE_PROJECT_NAME}) +set(FLUTTER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/flutter) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_FLAGS "-Wall -Wextra") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +set(CMAKE_SKIP_RPATH OFF) +set(CMAKE_INSTALL_RPATH "\$ORIGIN/../share/${BINARY_NAME}/lib") + +find_package(PkgConfig REQUIRED) +pkg_check_modules(FlutterEmbedder REQUIRED IMPORTED_TARGET flutter-embedder) + +add_executable(${BINARY_NAME} main.cpp ${FLUTTER_DIR}/generated_plugin_registrant.cpp) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::FlutterEmbedder) +target_include_directories(${BINARY_NAME} PRIVATE ${FLUTTER_DIR}) + +include(flutter/generated_plugins.cmake) + +set(PACKAGE_INSTALL_DIR ${CMAKE_INSTALL_DATADIR}/${BINARY_NAME}) +set(DESKTOP_INSTALL_DIR ${CMAKE_INSTALL_DATADIR}/applications) +set(ICONS_INSTALL_ROOT_DIR ${CMAKE_INSTALL_DATADIR}/icons/hicolor) + +add_custom_command(TARGET ${BINARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/libflutter-embedder.so + ${PROJECT_BINARY_DIR}/bundle/lib/libflutter-embedder.so) + +install(FILES ${PROJECT_BINARY_DIR}/bundle/icudtl.dat DESTINATION ${PACKAGE_INSTALL_DIR}) +install(DIRECTORY ${PROJECT_BINARY_DIR}/bundle/flutter_assets DESTINATION ${PACKAGE_INSTALL_DIR}) +install(DIRECTORY ${PROJECT_BINARY_DIR}/bundle/lib DESTINATION ${PACKAGE_INSTALL_DIR}) + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES desktop/${BINARY_NAME}.desktop DESTINATION ${DESKTOP_INSTALL_DIR}) + +foreach(ICONS_SIZE 86x86 108x108 128x128 172x172) + install(FILES icons/${ICONS_SIZE}.png + RENAME ${BINARY_NAME}.png + DESTINATION ${ICONS_INSTALL_ROOT_DIR}/${ICONS_SIZE}/apps/) +endforeach(ICONS_SIZE) diff --git a/example/aurora/desktop/com.example.secure_storage_example.desktop b/example/aurora/desktop/com.example.secure_storage_example.desktop new file mode 100644 index 0000000..07834a4 --- /dev/null +++ b/example/aurora/desktop/com.example.secure_storage_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=secure_storage_example +Comment=Demonstrates how to use the secure_storage plugin. +Icon=com.example.secure_storage_example +Exec=/usr/bin/com.example.secure_storage_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=secure_storage_example diff --git a/example/aurora/icons/108x108.png b/example/aurora/icons/108x108.png new file mode 100644 index 0000000000000000000000000000000000000000..984893df6c30793313b2b3d3912a6e95977e2333 GIT binary patch literal 9954 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�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φ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 literal 0 HcmV?d00001 diff --git a/example/aurora/icons/172x172.png b/example/aurora/icons/172x172.png new file mode 100644 index 0000000000000000000000000000000000000000..9dc271ba73a05054cdcb09780a4cc56cc05d5ea7 GIT binary patch literal 23377 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 literal 0 HcmV?d00001 diff --git a/example/aurora/icons/86x86.png b/example/aurora/icons/86x86.png new file mode 100644 index 0000000000000000000000000000000000000000..5923bb1113acac37ae5d280245b152cc4a1b242d GIT binary patch literal 6632 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 mdnuQ%0#85i!0Z3VDE}Y+awRoL1wwQH0000 +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/example/aurora/rpm/com.example.secure_storage_example.spec b/example/aurora/rpm/com.example.secure_storage_example.spec new file mode 100644 index 0000000..fd6b634 --- /dev/null +++ b/example/aurora/rpm/com.example.secure_storage_example.spec @@ -0,0 +1,32 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|.+_platform_plugin)\\.so.*$ + +Name: com.example.secure_storage_example +Summary: Demonstrates how to use the secure_storage plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: ninja +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -GNinja -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%ninja_build + +%install +%ninja_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart new file mode 100644 index 0000000..3e32e53 --- /dev/null +++ b/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,25 @@ +// This is a basic Flutter integration test. +// +// Since integration tests run in a full Flutter application, they can interact +// with the host side of a plugin implementation, unlike Dart unit tests. +// +// For more information about Flutter integration tests, please see +// https://docs.flutter.dev/cookbook/testing/integration/introduction + + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:secure_storage/secure_storage.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('getPlatformVersion test', (WidgetTester tester) async { + final SecureStorage plugin = SecureStorage(); + final String? version = await plugin.getPlatformVersion(); + // The version string depends on the host platform running the test, so + // just assert that some non-empty string is returned. + expect(version?.isNotEmpty, true); + }); +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..a0f6d3a --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:secure_storage/secure_storage.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String _platformVersion = 'Unknown'; + final _secureStoragePlugin = SecureStorage(); + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String platformVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + // We also handle the message potentially returning null. + try { + platformVersion = + await _secureStoragePlugin.getPlatformVersion() ?? 'Unknown platform version'; + } on PlatformException { + platformVersion = 'Failed to get platform version.'; + } + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _platformVersion = platformVersion; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Text('Running on: $_platformVersion\n'), + ), + ), + ); + } +} diff --git a/example/linux/.gitignore b/example/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt new file mode 100644 index 0000000..f5dec42 --- /dev/null +++ b/example/linux/CMakeLists.txt @@ -0,0 +1,147 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "secure_storage_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.secure_storage") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Enable the test target. +set(include_secure_storage_tests TRUE) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/example/linux/main.cc b/example/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc new file mode 100644 index 0000000..045fe76 --- /dev/null +++ b/example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "secure_storage_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "secure_storage_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/example/linux/my_application.h b/example/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..47993ef --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,267 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + platform: + dependency: transitive + description: + name: platform + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + url: "https://pub.dev" + source: hosted + version: "2.1.7" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + secure_storage: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + url: "https://pub.dev" + source: hosted + version: "11.10.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + url: "https://pub.dev" + source: hosted + version: "3.0.2" +sdks: + dart: ">=3.2.2 <4.0.0" + flutter: ">=3.3.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..ae7f267 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,85 @@ +name: secure_storage_example +description: "Demonstrates how to use the secure_storage plugin." +# 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 + +environment: + sdk: '>=3.2.2 <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 + + secure_storage: + # When depending on this package from a real application you should use: + # secure_storage: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + integration_test: + sdk: flutter + 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 + +# 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/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..4210048 --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:secure_storage_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/lib/secure_storage.dart b/lib/secure_storage.dart new file mode 100644 index 0000000..82e5b61 --- /dev/null +++ b/lib/secure_storage.dart @@ -0,0 +1,8 @@ + +import 'secure_storage_platform_interface.dart'; + +class SecureStorage { + Future getPlatformVersion() { + return SecureStoragePlatform.instance.getPlatformVersion(); + } +} diff --git a/lib/secure_storage_method_channel.dart b/lib/secure_storage_method_channel.dart new file mode 100644 index 0000000..600a6cd --- /dev/null +++ b/lib/secure_storage_method_channel.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'secure_storage_platform_interface.dart'; + +/// An implementation of [SecureStoragePlatform] that uses method channels. +class MethodChannelSecureStorage extends SecureStoragePlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('secure_storage'); + + @override + Future getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/lib/secure_storage_platform_interface.dart b/lib/secure_storage_platform_interface.dart new file mode 100644 index 0000000..4bf5256 --- /dev/null +++ b/lib/secure_storage_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'secure_storage_method_channel.dart'; + +abstract class SecureStoragePlatform extends PlatformInterface { + /// Constructs a SecureStoragePlatform. + SecureStoragePlatform() : super(token: _token); + + static final Object _token = Object(); + + static SecureStoragePlatform _instance = MethodChannelSecureStorage(); + + /// The default instance of [SecureStoragePlatform] to use. + /// + /// Defaults to [MethodChannelSecureStorage]. + static SecureStoragePlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [SecureStoragePlatform] when + /// they register themselves. + static set instance(SecureStoragePlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..434f3ff --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,94 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "secure_storage") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed. +set(PLUGIN_NAME "secure_storage_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "secure_storage_plugin.cc" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(secure_storage_bundled_libraries + "" + PARENT_SCOPE +) + +# === Tests === +# These unit tests can be run from a terminal after building the example. + +# Only enable test builds when building the example (which sets this variable) +# so that plugin clients aren't building the tests. +if (${include_${PROJECT_NAME}_tests}) +if(${CMAKE_VERSION} VERSION_LESS "3.11.0") +message("Unit tests require CMake 3.11.0 or later") +else() +set(TEST_RUNNER "${PROJECT_NAME}_test") +enable_testing() + +# Add the Google Test dependency. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.11.0.zip +) +# Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +# Disable install commands for gtest so it doesn't end up in the bundle. +set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) + +FetchContent_MakeAvailable(googletest) + +# The plugin's exported API is not very useful for unit testing, so build the +# sources directly into the test binary rather than using the shared library. +add_executable(${TEST_RUNNER} + test/secure_storage_plugin_test.cc + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(${TEST_RUNNER} PRIVATE flutter) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK) +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) + +# Enable automatic test discovery. +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) + +endif() # CMake version check +endif() # include_${PROJECT_NAME}_tests \ No newline at end of file diff --git a/linux/include/secure_storage/secure_storage_plugin.h b/linux/include/secure_storage/secure_storage_plugin.h new file mode 100644 index 0000000..b700cab --- /dev/null +++ b/linux/include/secure_storage/secure_storage_plugin.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_H_ +#define FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _SecureStoragePlugin SecureStoragePlugin; +typedef struct { + GObjectClass parent_class; +} SecureStoragePluginClass; + +FLUTTER_PLUGIN_EXPORT GType secure_storage_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void secure_storage_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_SECURE_STORAGE_PLUGIN_H_ diff --git a/linux/secure_storage_plugin.cc b/linux/secure_storage_plugin.cc new file mode 100644 index 0000000..4a51153 --- /dev/null +++ b/linux/secure_storage_plugin.cc @@ -0,0 +1,76 @@ +#include "include/secure_storage/secure_storage_plugin.h" + +#include +#include +#include + +#include + +#include "secure_storage_plugin_private.h" + +#define SECURE_STORAGE_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), secure_storage_plugin_get_type(), \ + SecureStoragePlugin)) + +struct _SecureStoragePlugin { + GObject parent_instance; +}; + +G_DEFINE_TYPE(SecureStoragePlugin, secure_storage_plugin, g_object_get_type()) + +// Called when a method call is received from Flutter. +static void secure_storage_plugin_handle_method_call( + SecureStoragePlugin* self, + FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "getPlatformVersion") == 0) { + response = get_platform_version(); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + fl_method_call_respond(method_call, response, nullptr); +} + +FlMethodResponse* get_platform_version() { + struct utsname uname_data = {}; + uname(&uname_data); + g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version); + g_autoptr(FlValue) result = fl_value_new_string(version); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +static void secure_storage_plugin_dispose(GObject* object) { + G_OBJECT_CLASS(secure_storage_plugin_parent_class)->dispose(object); +} + +static void secure_storage_plugin_class_init(SecureStoragePluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = secure_storage_plugin_dispose; +} + +static void secure_storage_plugin_init(SecureStoragePlugin* self) {} + +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + SecureStoragePlugin* plugin = SECURE_STORAGE_PLUGIN(user_data); + secure_storage_plugin_handle_method_call(plugin, method_call); +} + +void secure_storage_plugin_register_with_registrar(FlPluginRegistrar* registrar) { + SecureStoragePlugin* plugin = SECURE_STORAGE_PLUGIN( + g_object_new(secure_storage_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = + fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), + "secure_storage", + FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel, method_call_cb, + g_object_ref(plugin), + g_object_unref); + + g_object_unref(plugin); +} diff --git a/linux/secure_storage_plugin_private.h b/linux/secure_storage_plugin_private.h new file mode 100644 index 0000000..446f31c --- /dev/null +++ b/linux/secure_storage_plugin_private.h @@ -0,0 +1,10 @@ +#include + +#include "include/secure_storage/secure_storage_plugin.h" + +// This file exposes some plugin internals for unit testing. See +// https://github.com/flutter/flutter/issues/88724 for current limitations +// in the unit-testable API. + +// Handles the getPlatformVersion method call. +FlMethodResponse *get_platform_version(); diff --git a/linux/test/secure_storage_plugin_test.cc b/linux/test/secure_storage_plugin_test.cc new file mode 100644 index 0000000..bbe6765 --- /dev/null +++ b/linux/test/secure_storage_plugin_test.cc @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "include/secure_storage/secure_storage_plugin.h" +#include "secure_storage_plugin_private.h" + +// This demonstrates a simple unit test of the C portion of this plugin's +// implementation. +// +// Once you have built the plugin's example app, you can run these tests +// from the command line. For instance, for a plugin called my_plugin +// built for x64 debug, run: +// $ build/linux/x64/debug/plugins/my_plugin/my_plugin_test + +namespace secure_storage { +namespace test { + +TEST(SecureStoragePlugin, GetPlatformVersion) { + g_autoptr(FlMethodResponse) response = get_platform_version(); + ASSERT_NE(response, nullptr); + ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + FlValue* result = fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING); + // The full string varies, so just validate that it has the right format. + EXPECT_THAT(fl_value_get_string(result), testing::StartsWith("Linux ")); +} + +} // namespace test +} // namespace secure_storage diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..b917f3d --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,71 @@ +name: secure_storage +description: "A new Flutter plugin project." +version: 0.0.1 +homepage: + +environment: + sdk: '>=3.1.2 <4.0.0' + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.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: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + linux: + pluginClass: SecureStoragePlugin + aurora: + pluginClass: SecureStoragePlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, 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 in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/test/secure_storage_method_channel_test.dart b/test/secure_storage_method_channel_test.dart new file mode 100644 index 0000000..0ef4d21 --- /dev/null +++ b/test/secure_storage_method_channel_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:secure_storage/secure_storage_method_channel.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelSecureStorage platform = MethodChannelSecureStorage(); + const MethodChannel channel = MethodChannel('secure_storage'); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + (MethodCall methodCall) async { + return '42'; + }, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/test/secure_storage_test.dart b/test/secure_storage_test.dart new file mode 100644 index 0000000..3dbf530 --- /dev/null +++ b/test/secure_storage_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:secure_storage/secure_storage.dart'; +import 'package:secure_storage/secure_storage_platform_interface.dart'; +import 'package:secure_storage/secure_storage_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockSecureStoragePlatform + with MockPlatformInterfaceMixin + implements SecureStoragePlatform { + + @override + Future getPlatformVersion() => Future.value('42'); +} + +void main() { + final SecureStoragePlatform initialPlatform = SecureStoragePlatform.instance; + + test('$MethodChannelSecureStorage is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + SecureStorage secureStoragePlugin = SecureStorage(); + MockSecureStoragePlatform fakePlatform = MockSecureStoragePlatform(); + SecureStoragePlatform.instance = fakePlatform; + + expect(await secureStoragePlugin.getPlatformVersion(), '42'); + }); +}