diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2c8c65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea/ +/.vscode/ \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..12fa48c --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,7 @@ +# Authors + +* Denis Glazkov, + * Product owner, 2023 + * Maintainer, 2023 + +* Vitaliy Zarubin, diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f98331d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Code of Conduct + +## What is this code of conduct for? + +Code of conduct is a current set of rules of the Open Mobile Platform +which informs you how we expect +the members of the Open Mobile Platform community +will interact while contributing and communicating. +We are committed to providing a friendly, safe and welcoming +environment for all, regardless of gender, gender identity and expression, +sexual orientation, ability, physical appearance, body size, race, age, +socioeconomic status, religion (or lack thereof), +or other marginalized aspect of community members. +We expect all members of the Open Mobile Platform community +to abide by this Code of Conduct whenever interacting +in Open Mobile Platform venues +(merge requests, pull requests, issues, 1-1 or group chat, meetups, conferences, etc.) + +## Examples of appropriate behavior + +* Using welcoming and inclusive language. +* Being respectful of differing viewpoints and experiences. +* Gracefully accepting constructive criticism. +* Focusing on what is best for the community. +* Showing empathy towards other community members. + +## Examples of inappropriate behavior + +Because we come from a variety of backgrounds, +we do not want to assume that everyone has the same assumptions +about what is and is not appropriate. +Here are some examples of inappropriate behavior +that are incompatible with our community's ethos: + +* spamming, trolling, intentionally disrupting conversations, + or irrelevant solicitation or advertisement; +* making demeaning or discriminatory comments; +* making negative assumptions about someone's background, + abilities, or intentions; +* harassing or stalking individuals (online or in person). + +In general: treat others how you would like to be treated, +were you in their place. +Do ask questions. +Do keep conflicts productively focused on technical issues. +Do remember that we are all people, not robots, +and all equally deserving of sensitivity and respect. + +## What will organizers do about inappropriate behavior? + +If we notice you doing or saying something inappropriate, +an organizer will ask you to stop. +We will not demonize you. +But please do stop the inappropriate behavior +so we can get back to writing and discussing code in a safe environment. +If you have philosophical disagreements about what is actually inappropriate, +please take them to a separate public or private conversation +with an Open Mobile Platform maintainer +so we don't turn pull requests into an ethics debate. +If you keep doing unacceptable things, +we will likely ban you, report you to the administration, +or take other appropriate action. + +## What if I see or am subject to what feels like inappropriate behavior? + +Let us know. +Please notify a community organizer as soon as possible. +Full contact information is listed in the Contact Info section of this document. +All communications will be kept strictly confidential, +unless otherwise required by law. +No issue will be considered too inconsequential or unimportant for us +to have a conversation about. + +## Contact Info + +If you need to report an incident, +please contact . + +This work is licensed under a Creative Commons Attribution 3.0 Unported License +For attribution requirements: +«Open Mobile Platform Code of Conduct» +Copyright (c) 2021 Open Mobile Platform LLC, +used under a [Creative Commons Attribution Unported license](http://creativecommons.org/licenses/by/3.0/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5fc75b6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,169 @@ +# Contributor License Agreements + +Thank you for your interest in contributing +to software projects managed by Open Mobile Platform («We» or «Us»). +This contributor agreement («Agreement») +documents the rights granted by contributors to Us. +To make this document effective, +please sign it and send it to Us by email or electronic submission. +This is a legally binding document, +so please read it carefully before agreeing to it. +The Agreement may cover more than one software project managed by Us. + +## 1. Definitions + +**«We» or «Us»** means Open Mobile Platform Limited Liability Company +(Open Mobile Platform LLC), +420500, Republic of Tatarstan, Verkhneuslonsky District, +Innopolis, Universitetskaya Street, h. 7, office 59, +OGRN (Primary State Registration Number) 1161690087020. + +**«You»** means the individual who Submits a Contribution to Us. + +**«Contribution»** means any work of authorship +that is Submitted by You to Us +in which You own or assert ownership of the Copyright. + +**«Copyright»** means all rights protecting works of authorship +owned or controlled by You, +including copyright, moral and neighboring rights, as appropriate, +for the full term of their existence including any extensions by You. + +**«Material»** means the work of authorship +which is made available by Us to third parties. +When this Agreement covers more than one software project, +the Material means the work of authorship +to which the Contribution was Submitted. +After You Submit the Contribution, +it may be included in the Material. + +**«Submit»** means any form of electronic, verbal, or written communication +sent to Us or our representatives, +including but not limited to electronic mailing lists, +source code control systems, and issue tracking systems +that are managed by, or on behalf of, Us +for the purpose of discussing and improving the Material, +but excluding communication that is conspicuously marked +or otherwise designated in writing by You as «Not a Contribution». + +**«Submission Date»** means the date +on which You Submit a Contribution to Us. + +**«Effective Date»** means the date You execute this Agreement +or the date You first Submit a Contribution to Us, +whichever is earlier. + +**«Media»** means any portion of a Contribution which is not software. + +## 2. Grant of Rights + +### 2.1. Copyright License + +(a) You retain ownership of the Copyright in Your Contribution +and have the same rights to use or license the Contribution +which You would have had without entering into the Agreement. + +(b) To the maximum extent permitted by the relevant law, +You grant to Us a perpetual, worldwide, non-exclusive, +transferable, royalty-free, irrevocable license +under the Copyright covering the Contribution, +with the right to sublicense +such rights through multiple tiers of sublicensees, +to reproduce, modify, display, perform and distribute +the Contribution as part of the Material; +provided that this license is conditioned upon compliance with Section 2.3. + +### 2.2. Patent License + +For patent claims including, without limitation, +method, process, and apparatus claims +which You own, control or have the right to grant, now or in the future, +You grant to Us a perpetual, worldwide, non-exclusive, +transferable, royalty-free, irrevocable patent license, +with the right to sublicense these rights to multiple tiers of sublicensees, +to make, have made, use, sell, offer for sale, import +and otherwise transfer the Contribution +and the Contribution in combination with the Material +(and portions of such combination). +This license is granted only to the extent +that the exercise of the licensed rights infringes such patent claims; +and provided that this license is conditioned upon compliance with Section 2.3. + +### 2.3. Outbound License + +Based on the grant of rights in Sections 2.1 and 2.2, +if We include Your Contribution in a Material, +We may license the Contribution under any license, +including copyleft, permissive, commercial, or proprietary licenses. +As a condition on the exercise of this right, +We agree to also license the Contribution +under the terms of the license or licenses +which We are using for the Material on the Submission Date. + +### 2.4. Our Rights + +You acknowledge that We are not obligated +to use Your Contribution as part of the Material +and may decide to include any Contribution We consider appropriate. + +## 3. Agreement + +You confirm that: + +(a) You have the legal authority to enter into this Agreement. + +(b) You own the Copyright and patent claims +covering the Contribution which are required +to grant the rights under Section 2. + +(c) The grant of rights under Section 2 +does not violate any grant of rights +which You have made to third parties, including Your employer. +If You are an employee, +You have had Your employer approve this Agreement +or sign the Entity version of this document. +If You are less than eighteen years old, +please have Your parents or guardian sign the Agreement. + +## 4. Disclaimer + +EXCEPT FOR THE EXPRESS WARRANTIES IN SECTION 3, +THE CONTRIBUTION IS PROVIDED «AS IS». +MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES +INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE +AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US. +TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, +SUCH WARRANTY IS LIMITED IN DURATION TO THE MINIMUM PERIOD PERMITTED BY LAW. + +## 5. Miscellaneous + +5.1. This Agreement will be governed by and construed +in accordance with the laws of Russian Federation. + +5.2. This Agreement sets out the entire agreement between You +and Us for Your Contributions to Us +and overrides all other agreements or understandings. + +5.3. If You or We assign the rights or obligations +received through this Agreement to a third party, +as a condition of the assignment, +that third party must agree in writing +to abide by all the rights and obligations in the Agreement. + +5.4. The failure of either party to require performance +by the other party of any provision of this Agreement in one situation +shall not affect the right of a party +to require such performance at any time in the future. +A waiver of performance under a provision in one situation +shall not be considered a waiver of the performance +of the provision in the future or a waiver of the provision in its entirety. + +5.5. If any provision of this Agreement is found void and unenforceable, +such provision will be replaced to the extent possible with a provision +that comes closest to the meaning of the original provision +and which is enforceable. +The terms and conditions set forth in this Agreement +shall apply notwithstanding any failure of essential purpose +of this Agreement or any limited remedy +to the maximum extent possible under law. diff --git a/LICENSE.BSD-3-CLAUSE.md b/LICENSE.BSD-3-CLAUSE.md new file mode 100644 index 0000000..f040be6 --- /dev/null +++ b/LICENSE.BSD-3-CLAUSE.md @@ -0,0 +1,30 @@ +# The 3-Clause BSD License + +_Copyright (C) 2022 ru.auroraos_ + +Redistribution and use in source and binary forms, +with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain + the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce + the above copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2be87a --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Flutter Packages Aurora OS + +This repo is a companion repo to the main flutter repo. +It contains the source code for Aurora Flutter's packages (i.e., packages developed by the Aurora team). +Check the packages directory to see all packages. \ No newline at end of file diff --git a/packages/battery_plus/battery_plus_aurora/.gitignore b/packages/battery_plus/battery_plus_aurora/.gitignore new file mode 100644 index 0000000..c1631bb --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/.gitignore @@ -0,0 +1,32 @@ +# 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/ +.packages +build/ +.metadata +run.sh diff --git a/packages/battery_plus/battery_plus_aurora/README.md b/packages/battery_plus/battery_plus_aurora/README.md new file mode 100644 index 0000000..bc0feb3 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/README.md @@ -0,0 +1,38 @@ +# battery_plus_aurora + +The Aurora implementation of [`battery_plus`][https://pub.dev/packages/battery_plus]. + +## Usage +This package is not an _endorsed_ implementation of `battery_plus`. +Therefore, you have to include `battery_plus_aurora` alongside `battery_plus` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + battery_plus: ^4.0.1 + battery_plus_aurora: + path: # path to folder with plugin +``` + +```dart +// Import package +import 'package:battery_plus/battery_plus.dart'; + +// Instantiate it +var battery = Battery(); + +// Get current battery level +final batteryLevel = await _battery.batteryLevel; +// Get current battery state +final batteryState = await _battery.batteryState; +// Check is enable SaveMode +final isInBatterySaveMode = await _battery.isInBatterySaveMode; + +// Be informed when the state (full, charging, discharging) changes +_battery.onBatteryStateChanged.listen((BatteryState state) { + debugPrint(state.toString()); +}); +``` + +### Preview example + +![preview.png](data%2Fpreview.png) diff --git a/packages/battery_plus/battery_plus_aurora/analysis_options.yaml b/packages/battery_plus/battery_plus_aurora/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/battery_plus/battery_plus_aurora/data/com.nokia.mce.request.xml b/packages/battery_plus/battery_plus_aurora/data/com.nokia.mce.request.xml new file mode 100644 index 0000000..5de3ec4 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/data/com.nokia.mce.request.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/packages/battery_plus/battery_plus_aurora/data/com.nokia.mce.signal.xml b/packages/battery_plus/battery_plus_aurora/data/com.nokia.mce.signal.xml new file mode 100644 index 0000000..0d85476 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/data/com.nokia.mce.signal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/packages/battery_plus/battery_plus_aurora/data/preview.png b/packages/battery_plus/battery_plus_aurora/data/preview.png new file mode 100644 index 0000000..924f871 Binary files /dev/null and b/packages/battery_plus/battery_plus_aurora/data/preview.png differ diff --git a/packages/battery_plus/battery_plus_aurora/example/.gitignore b/packages/battery_plus/battery_plus_aurora/example/.gitignore new file mode 100644 index 0000000..66cf84f --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/.gitignore @@ -0,0 +1,45 @@ +# 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 +.packages +.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 +pubspec.lock diff --git a/packages/battery_plus/battery_plus_aurora/example/README.md b/packages/battery_plus/battery_plus_aurora/example/README.md new file mode 100644 index 0000000..cc85198 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/README.md @@ -0,0 +1,17 @@ +# battery_plus_aurora_example + +Demonstrates how to use the battery_plus_aurora plugin. + +## Build + +```shell +# Add an alias if it doesn't already exist +alias flutter-aurora=$HOME/.local/opt/flutter-sdk/bin/flutter +# Get dependencies +flutter-aurora pub get +# Run build +flutter-aurora build aurora --release +``` + +You can collect, sign, run an example on the device with a script located in the `script/build_example.sh` +More information in `build_example.sh`. diff --git a/packages/battery_plus/battery_plus_aurora/example/analysis_options.yaml b/packages/battery_plus/battery_plus_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/.gitignore b/packages/battery_plus/battery_plus_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/CMakeLists.txt b/packages/battery_plus/battery_plus_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..32c841f --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.battery_plus_aurora_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/packages/battery_plus/battery_plus_aurora/example/aurora/desktop/com.example.battery_plus_aurora_example.desktop b/packages/battery_plus/battery_plus_aurora/example/aurora/desktop/com.example.battery_plus_aurora_example.desktop new file mode 100644 index 0000000..a6aeb93 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/desktop/com.example.battery_plus_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=battery_plus_aurora_example +Comment=Demonstrates how to use the battery_plus_aurora plugin. +Icon=com.example.battery_plus_aurora_example +Exec=/usr/bin/com.example.battery_plus_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=battery_plus_aurora_example diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..b315972 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + }); +} diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..ae4d0a2 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,30 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/icons/108x108.png b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/icons/128x128.png b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/icons/172x172.png b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/icons/86x86.png b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/battery_plus/battery_plus_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/main.cpp b/packages/battery_plus/battery_plus_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/battery_plus/battery_plus_aurora/example/aurora/rpm/com.example.battery_plus_aurora_example.spec b/packages/battery_plus/battery_plus_aurora/example/aurora/rpm/com.example.battery_plus_aurora_example.spec new file mode 100644 index 0000000..a06d8f3 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/aurora/rpm/com.example.battery_plus_aurora_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.battery_plus_aurora_example +Summary: Demonstrates how to use the battery_plus_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/battery_plus/battery_plus_aurora/example/lib/main.dart b/packages/battery_plus/battery_plus_aurora/example/lib/main.dart new file mode 100644 index 0000000..51ced71 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/lib/main.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:battery_plus/battery_plus.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + final _battery = Battery(); + String? _error; + int? _batteryLevel; + String? _batteryState; + bool? _isInBatterySaveMode; + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + try { + // Get current battery level + final batteryLevel = await _battery.batteryLevel; + // Get current battery state + final batteryState = await _battery.batteryState; + // Check is enable SaveMode + final isInBatterySaveMode = await _battery.isInBatterySaveMode; + + // Be informed when the state (full, charging, discharging) changes + _battery.onBatteryStateChanged.listen((BatteryState state) { + debugPrint(state.toString()); + }); + + setState(() { + _batteryLevel = batteryLevel; + _batteryState = batteryState.name; + _isInBatterySaveMode = isInBatterySaveMode; + }); + } on Exception catch (e) { + setState(() { + _error = e.toString(); + }); + } + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + const textStyleTitle = TextStyle(fontSize: 20, color: Colors.black); + const textStylePath = TextStyle(fontSize: 18, color: Colors.black54); + + const spaceMedium = SizedBox(height: 20); + const spaceSmall = SizedBox(height: 10); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example battery_plus'), + ), + body: Stack( + children: [ + // Error message + Visibility( + visible: _error != null, + child: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.redAccent, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + _error ?? '', + style: textStyleWhite, + ), + ), + ), + ), + ), + // List directories path + Visibility( + visible: _error == null, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + // Info + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: + BorderRadius.all(Radius.circular(10.0)), + ), + child: const Text( + 'Demo application demonstration implementation of battery_plus', + style: textStyleWhite, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + + const Text( + 'Battery Level', + style: textStyleTitle, + ), + spaceSmall, + Text( + "$_batteryLevel%", + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Battery State', + style: textStyleTitle, + ), + spaceSmall, + Text( + _batteryState.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Is In Battery SaveMode', + style: textStyleTitle, + ), + spaceSmall, + Text( + _isInBatterySaveMode.toString(), + style: textStylePath, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/battery_plus/battery_plus_aurora/example/pubspec.yaml b/packages/battery_plus/battery_plus_aurora/example/pubspec.yaml new file mode 100644 index 0000000..b270708 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/example/pubspec.yaml @@ -0,0 +1,23 @@ +name: battery_plus_aurora_example +description: Demonstrates how to use the battery_plus_aurora plugin. + +publish_to: 'none' + +environment: + sdk: '>=2.18.6 <3.0.0' + +dependencies: + flutter: + sdk: flutter + battery_plus: ^4.0.1 + battery_plus_aurora: + path: ../ + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true diff --git a/packages/battery_plus/battery_plus_aurora/lib/battery_plus_aurora.dart b/packages/battery_plus/battery_plus_aurora/lib/battery_plus_aurora.dart new file mode 100644 index 0000000..c4617b8 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/lib/battery_plus_aurora.dart @@ -0,0 +1,88 @@ +import 'package:battery_plus_aurora/com_nokia_mce_request.dart'; +import 'package:battery_plus_aurora/com_nokia_mce_signal.dart'; +import 'package:dbus/dbus.dart'; +import 'package:battery_plus_platform_interface/battery_plus_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'dart:async' show Stream; +import 'package:async/async.dart' show StreamGroup; + +class BatteryPlusAurora extends BatteryPlatform { + /// Register this dart class as the platform implementation for aurora + static void registerWith() { + if (TargetPlatform.aurora == defaultTargetPlatform) { + BatteryPlatform.instance = BatteryPlusAurora(); + } + } + + /// Returns the current battery level in percent. + @override + Future get batteryLevel async { + final client = DBusClient.system(); + final request = ComNokiaMceRequest(client, 'com.nokia.mce'); + final level = await request.callget_battery_level(); + await client.close(); + return level; + } + + /// Returns true if the device is on battery save mode + @override + Future get isInBatterySaveMode async { + final client = DBusClient.system(); + final request = ComNokiaMceRequest(client, 'com.nokia.mce'); + final state = await request.callget_psm_state(); + await client.close(); + return state; + } + + /// Returns the current battery state in percent. + @override + Future get batteryState async { + final client = DBusClient.system(); + final request = ComNokiaMceRequest(client, 'com.nokia.mce'); + + final level = await request.callget_battery_level(); + final status = await request.callget_charger_state(); + + await client.close(); + + if (level == 100) { + return BatteryState.full; + } else if (status == 'on') { + return BatteryState.charging; + } else { + return BatteryState.discharging; + } + } + + /// Returns a Stream of BatteryState changes. + @override + Stream get onBatteryStateChanged async* { + final client = DBusClient.system(); + final signal = ComNokiaMceSignal(client, 'com.nokia.mce'); + final request = ComNokiaMceRequest(client, 'com.nokia.mce'); + + var steam = StreamGroup.merge([ + signal.battery_status_ind, + signal.charger_state_ind, + ]); + + await for (final event in steam) { + if (event.name == 'battery_status_ind') { + if (event.values.first.toNative() == 'full') { + yield BatteryState.full; + } + } else { + if (event.values.first.toNative() == 'on') { + yield BatteryState.charging; + final level = await request.callget_battery_level(); + if (level == 100) { + yield BatteryState.full; + } + } else { + yield BatteryState.discharging; + } + } + } + await client.close(); + } +} diff --git a/packages/battery_plus/battery_plus_aurora/lib/com_nokia_mce_request.dart b/packages/battery_plus/battery_plus_aurora/lib/com_nokia_mce_request.dart new file mode 100644 index 0000000..dac1934 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/lib/com_nokia_mce_request.dart @@ -0,0 +1,46 @@ +// This file was generated using the following command and may be overwritten. +// dart-dbus generate-remote-object data/com.nokia.mce.request.xml + +import 'package:dbus/dbus.dart'; + +class ComNokiaMceRequest extends DBusRemoteObject { + ComNokiaMceRequest(DBusClient client, String destination, + {DBusObjectPath path = + const DBusObjectPath.unchecked('/com/nokia/mce/request')}) + : super(client, name: destination, path: path); + + /// Invokes com.nokia.mce.request.get_psm_state() + Future callget_psm_state( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod('com.nokia.mce.request', 'get_psm_state', [], + replySignature: DBusSignature('b'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asBoolean(); + } + + /// Invokes com.nokia.mce.request.get_battery_level() + Future callget_battery_level( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'com.nokia.mce.request', 'get_battery_level', [], + replySignature: DBusSignature('i'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asInt32(); + } + + /// Invokes com.nokia.mce.request.get_charger_state() + Future callget_charger_state( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'com.nokia.mce.request', 'get_charger_state', [], + replySignature: DBusSignature('s'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asString(); + } +} diff --git a/packages/battery_plus/battery_plus_aurora/lib/com_nokia_mce_signal.dart b/packages/battery_plus/battery_plus_aurora/lib/com_nokia_mce_signal.dart new file mode 100644 index 0000000..1bdce4c --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/lib/com_nokia_mce_signal.dart @@ -0,0 +1,32 @@ +// This file was generated using the following command and may be overwritten. +// dart-dbus generate-remote-object data/com.nokia.mce.signal.xml + +import 'package:dbus/dbus.dart'; + +/// Signal data for com.nokia.mce.signal.battery_status_ind. +class ComNokiaMceSignalbattery_status_ind extends DBusSignal { + String get battery_status => values[0].asString(); + + ComNokiaMceSignalbattery_status_ind(DBusSignal signal) : super(sender: signal.sender, path: signal.path, interface: signal.interface, name: signal.name, values: signal.values); +} + +/// Signal data for com.nokia.mce.signal.charger_state_ind. +class ComNokiaMceSignalcharger_state_ind extends DBusSignal { + String get charger_state => values[0].asString(); + + ComNokiaMceSignalcharger_state_ind(DBusSignal signal) : super(sender: signal.sender, path: signal.path, interface: signal.interface, name: signal.name, values: signal.values); +} + +class ComNokiaMceSignal extends DBusRemoteObject { + /// Stream of com.nokia.mce.signal.battery_status_ind signals. + late final Stream battery_status_ind; + + /// Stream of com.nokia.mce.signal.charger_state_ind signals. + late final Stream charger_state_ind; + + ComNokiaMceSignal(DBusClient client, String destination, {DBusObjectPath path = const DBusObjectPath.unchecked('/com/nokia/mce/signal')}) : super(client, name: destination, path: path) { + battery_status_ind = DBusRemoteObjectSignalStream(object: this, interface: 'com.nokia.mce.signal', name: 'battery_status_ind', signature: DBusSignature('s')).asBroadcastStream().map((signal) => ComNokiaMceSignalbattery_status_ind(signal)); + + charger_state_ind = DBusRemoteObjectSignalStream(object: this, interface: 'com.nokia.mce.signal', name: 'charger_state_ind', signature: DBusSignature('s')).asBroadcastStream().map((signal) => ComNokiaMceSignalcharger_state_ind(signal)); + } +} diff --git a/packages/battery_plus/battery_plus_aurora/pubspec.yaml b/packages/battery_plus/battery_plus_aurora/pubspec.yaml new file mode 100644 index 0000000..3971814 --- /dev/null +++ b/packages/battery_plus/battery_plus_aurora/pubspec.yaml @@ -0,0 +1,27 @@ +name: battery_plus_aurora +description: The Aurora OS implementation of battery_plus. +version: 0.0.1 +homepage: + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + dbus: ^0.7.8 + async: ^2.11.0 + plugin_platform_interface: ^2.0.2 + battery_plus_platform_interface: ^1.2.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + aurora: + dartPluginClass: BatteryPlusAurora diff --git a/packages/device_info_plus/device_info_plus_aurora/.gitignore b/packages/device_info_plus/device_info_plus_aurora/.gitignore new file mode 100644 index 0000000..c1631bb --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/.gitignore @@ -0,0 +1,32 @@ +# 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/ +.packages +build/ +.metadata +run.sh diff --git a/packages/device_info_plus/device_info_plus_aurora/README.md b/packages/device_info_plus/device_info_plus_aurora/README.md new file mode 100644 index 0000000..ef549a1 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/README.md @@ -0,0 +1,56 @@ +# device_info_plus_aurora + +The Aurora implementation of [`device_info_plus`](https://pub.dev/packages/device_info_plus). + +Permission: `DeviceInfo` + +Information available: + +- `id` - Target platform. +- `name` - Name OS. +- `version` - Versions OS. +- `prettyName` - Name and version OS. +- `hasGNSS` - The presence of GNSS in the device. +- `hasNFC` - The presence of NFC in the device. +- `hasBluetooth` - The presence of bluetooth in the device. +- `hasWlan` - The presence of wlan in the device. +- `maxCpuClockSpeed` - Max CPU clock speed. +- `numberCpuCores` - Number CPU cores. +- `batteryChargePercentage` - Device battery charge percentage. +- `mainCameraResolution` - Device main camera resolution. +- `frontalCameraResolution` - Device frontal camera resolution. +- `ramTotalSize` - Size total ram. +- `ramFreeSize` - Size free ram. +- `screenResolution` - Device screen resolution. +- `osVersion` - Name and version OS. +- `deviceModel` - Device name model. +- `internalStorage` - Map with information on internal storage. +- `internalStorage` - Map with information on internal storage. +- `simCards` - Array with information about SIM cards. + +## Usage +This package is not an _endorsed_ implementation of `device_info_plus`. +Therefore, you have to include `device_info_plus_aurora` alongside `device_info_plus` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + device_info_plus: ^9.0.1 + device_info_plus_aurora: + path: # path to folder with plugin +``` + +After that you can use like this: + +```dart +import 'package:device_info_plus/device_info_plus.dart'; + +final deviceInfoPlugin = DeviceInfoPlugin(); +final deviceInfo = await deviceInfoPlugin.linuxInfo as AuroraDeviceInfo; + +debutPrint(deviceInfo.data); +``` + +### Preview example + +![preview.png](data%2Fpreview.png) + diff --git a/packages/device_info_plus/device_info_plus_aurora/analysis_options.yaml b/packages/device_info_plus/device_info_plus_aurora/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/device_info_plus/device_info_plus_aurora/data/preview.png b/packages/device_info_plus/device_info_plus_aurora/data/preview.png new file mode 100644 index 0000000..b7d356e Binary files /dev/null and b/packages/device_info_plus/device_info_plus_aurora/data/preview.png differ diff --git a/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.Features.xml b/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.Features.xml new file mode 100644 index 0000000..3c38d5a --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.Features.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.SIM.xml b/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.SIM.xml new file mode 100644 index 0000000..0b2bc14 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.SIM.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.Storages.xml b/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.Storages.xml new file mode 100644 index 0000000..d7fbd32 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/data/ru.omp.deviceinfo.Storages.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/device_info_plus/device_info_plus_aurora/example/.gitignore b/packages/device_info_plus/device_info_plus_aurora/example/.gitignore new file mode 100644 index 0000000..065dc2c --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/.gitignore @@ -0,0 +1,45 @@ +# 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 +.packages +.pub-cache/ +.pub/ +/build/ +pubspec.lock + +# 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 diff --git a/packages/device_info_plus/device_info_plus_aurora/example/README.md b/packages/device_info_plus/device_info_plus_aurora/example/README.md new file mode 100644 index 0000000..1d9bacb --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/README.md @@ -0,0 +1,17 @@ +# device_info_plus_aurora + +Demonstrates how to use the device_info_plus_aurora plugin. + +## Build + +```shell +# Add an alias if it doesn't already exist +alias flutter-aurora=$HOME/.local/opt/flutter-sdk/bin/flutter +# Get dependencies +flutter-aurora pub get +# Run build +flutter-aurora build aurora --release +``` + +You can collect, sign, run an example on the device with a script located in the `script/build_example.sh` +More information in `build_example.sh`. diff --git a/packages/device_info_plus/device_info_plus_aurora/example/analysis_options.yaml b/packages/device_info_plus/device_info_plus_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/.gitignore b/packages/device_info_plus/device_info_plus_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/CMakeLists.txt b/packages/device_info_plus/device_info_plus_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..5a6d895 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.device_info_plus_aurora_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/packages/device_info_plus/device_info_plus_aurora/example/aurora/desktop/com.example.device_info_plus_aurora_example.desktop b/packages/device_info_plus/device_info_plus_aurora/example/aurora/desktop/com.example.device_info_plus_aurora_example.desktop new file mode 100644 index 0000000..88c8b51 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/desktop/com.example.device_info_plus_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=device_info_plus_aurora_example +Comment=Demonstrates how to use the device_info_plus_aurora plugin. +Icon=com.example.device_info_plus_aurora_example +Exec=/usr/bin/com.example.device_info_plus_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions=DeviceInfo +OrganizationName=com.example +ApplicationName=device_info_plus_aurora_example diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..b315972 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + }); +} diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..ae4d0a2 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,30 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/108x108.png b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/128x128.png b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/172x172.png b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/86x86.png b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/device_info_plus/device_info_plus_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/main.cpp b/packages/device_info_plus/device_info_plus_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/device_info_plus/device_info_plus_aurora/example/aurora/rpm/com.example.device_info_plus_aurora_example.spec b/packages/device_info_plus/device_info_plus_aurora/example/aurora/rpm/com.example.device_info_plus_aurora_example.spec new file mode 100644 index 0000000..8706033 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/aurora/rpm/com.example.device_info_plus_aurora_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.device_info_plus_aurora_example +Summary: Demonstrates how to use the device_info_plus_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/device_info_plus/device_info_plus_aurora/example/lib/main.dart b/packages/device_info_plus/device_info_plus_aurora/example/lib/main.dart new file mode 100644 index 0000000..d1e4010 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/lib/main.dart @@ -0,0 +1,385 @@ +import 'package:device_info_plus_aurora/aurora_device_info.dart'; +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:device_info_plus/device_info_plus.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String? _error; + String? _id; + String? _name; + String? _version; + String? _prettyName; + bool? _hasGNSS; + bool? _hasNFC; + bool? _hasBluetooth; + bool? _hasWlan; + int? _maxCpuClockSpeed; + int? _numberCpuCores; + int? _batteryChargePercentage; + double? _mainCameraResolution; + double? _frontalCameraResolution; + int? _ramTotalSize; + int? _ramFreeSize; + String? _screenResolution; + String? _osVersion; + String? _deviceModel; + Map? _externalStorage; + Map? _internalStorage; + List>? _simCards; + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + final deviceInfoPlugin = DeviceInfoPlugin(); + + try { + final deviceInfo = await deviceInfoPlugin.linuxInfo as AuroraDeviceInfo; + setState(() { + _id = deviceInfo.id; + _name = deviceInfo.name; + _version = deviceInfo.version; + _prettyName = deviceInfo.prettyName; + _hasGNSS = deviceInfo.hasGNSS; + _hasNFC = deviceInfo.hasNFC; + _hasBluetooth = deviceInfo.hasBluetooth; + _hasWlan = deviceInfo.hasWlan; + _maxCpuClockSpeed = deviceInfo.maxCpuClockSpeed; + _numberCpuCores = deviceInfo.numberCpuCores; + _batteryChargePercentage = deviceInfo.batteryChargePercentage; + _mainCameraResolution = deviceInfo.mainCameraResolution; + _frontalCameraResolution = deviceInfo.frontalCameraResolution; + _ramTotalSize = deviceInfo.ramTotalSize; + _ramFreeSize = deviceInfo.ramFreeSize; + _screenResolution = deviceInfo.screenResolution; + _osVersion = deviceInfo.osVersion; + _deviceModel = deviceInfo.deviceModel; + _externalStorage = deviceInfo.externalStorage; + _internalStorage = deviceInfo.internalStorage; + _simCards = deviceInfo.simCards; + }); + } on Exception catch (e) { + setState(() { + _error = e.toString(); + }); + } + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + const textStyleTitle = TextStyle(fontSize: 20, color: Colors.black); + const textStylePath = TextStyle(fontSize: 18, color: Colors.black54); + + const spaceMedium = SizedBox(height: 20); + const spaceSmall = SizedBox(height: 10); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example device_info_plus'), + ), + body: Stack( + children: [ + // Error message + Visibility( + visible: _error != null, + child: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.redAccent, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + _error ?? '', + style: textStyleWhite, + ), + ), + ), + ), + ), + // List directories path + Visibility( + visible: _error == null, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + // Info + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: + BorderRadius.all(Radius.circular(10.0)), + ), + child: const Text( + 'Demo application demonstration implementation of device_info_plus', + style: textStyleWhite, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + + const Text( + 'ID', + style: textStyleTitle, + ), + spaceSmall, + Text( + _id.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Name', + style: textStyleTitle, + ), + spaceSmall, + Text( + _name.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Version', + style: textStyleTitle, + ), + spaceSmall, + Text( + _version.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Pretty Name', + style: textStyleTitle, + ), + spaceSmall, + Text( + _prettyName.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Has GNSS', + style: textStyleTitle, + ), + spaceSmall, + Text( + _hasGNSS.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Has NFC', + style: textStyleTitle, + ), + spaceSmall, + Text( + _hasNFC.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Has Bluetooth', + style: textStyleTitle, + ), + spaceSmall, + Text( + _hasBluetooth.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Has Wlan', + style: textStyleTitle, + ), + spaceSmall, + Text( + _hasWlan.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Max Cpu Clock Speed', + style: textStyleTitle, + ), + spaceSmall, + Text( + _maxCpuClockSpeed.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Number Cpu Cores', + style: textStyleTitle, + ), + spaceSmall, + Text( + _numberCpuCores.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Battery Charge Percentage', + style: textStyleTitle, + ), + spaceSmall, + Text( + _batteryChargePercentage.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Main Camera Resolution', + style: textStyleTitle, + ), + spaceSmall, + Text( + _mainCameraResolution.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Frontal Camera Resolution', + style: textStyleTitle, + ), + spaceSmall, + Text( + _frontalCameraResolution.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Ram Total Size', + style: textStyleTitle, + ), + spaceSmall, + Text( + _ramTotalSize.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Ram Free Size', + style: textStyleTitle, + ), + spaceSmall, + Text( + _ramFreeSize.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Screen Resolution', + style: textStyleTitle, + ), + spaceSmall, + Text( + _screenResolution.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'OS Version', + style: textStyleTitle, + ), + spaceSmall, + Text( + _osVersion.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Device Model', + style: textStyleTitle, + ), + spaceSmall, + Text( + _deviceModel.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'External Storage Info', + style: textStyleTitle, + ), + spaceSmall, + Text( + _externalStorage.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Internal Storage Info', + style: textStyleTitle, + ), + spaceSmall, + Text( + _internalStorage.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'SIM Cards Info', + style: textStyleTitle, + ), + spaceSmall, + Text( + _simCards.toString(), + style: textStylePath, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/device_info_plus/device_info_plus_aurora/example/pubspec.yaml b/packages/device_info_plus/device_info_plus_aurora/example/pubspec.yaml new file mode 100644 index 0000000..88edf55 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/example/pubspec.yaml @@ -0,0 +1,23 @@ +name: device_info_plus_aurora_example +description: Demonstrates how to use the device_info_plus_aurora plugin. + +publish_to: 'none' + +environment: + sdk: '>=2.18.6 <3.0.0' + +dependencies: + flutter: + sdk: flutter + device_info_plus: ^9.0.1 + device_info_plus_aurora: + path: ../ + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true diff --git a/packages/device_info_plus/device_info_plus_aurora/lib/aurora_device_info.dart b/packages/device_info_plus/device_info_plus_aurora/lib/aurora_device_info.dart new file mode 100644 index 0000000..69342ac --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/lib/aurora_device_info.dart @@ -0,0 +1,106 @@ +import 'package:device_info_plus/device_info_plus.dart'; + +class AuroraDeviceInfo implements LinuxDeviceInfo { + /// Constructs a AuroraDeviceInfo. + AuroraDeviceInfo({ + required this.hasGNSS, + required this.hasNFC, + required this.hasBluetooth, + required this.hasWlan, + required this.maxCpuClockSpeed, + required this.numberCpuCores, + required this.batteryChargePercentage, + required this.mainCameraResolution, + required this.frontalCameraResolution, + required this.ramTotalSize, + required this.ramFreeSize, + required this.screenResolution, + required this.osVersion, + required this.deviceModel, + required this.externalStorage, + required this.internalStorage, + required this.simCards, + }); + + @override + String get id => 'aurora'; + + @override + String get name => osVersion.split(' ').first; + + @override + String? get version => osVersion.split(' ').last; + + @override + String get prettyName => osVersion; + + final bool hasGNSS; + final bool hasNFC; + final bool hasBluetooth; + final bool hasWlan; + final int maxCpuClockSpeed; + final int numberCpuCores; + final int batteryChargePercentage; + final double mainCameraResolution; + final double frontalCameraResolution; + final int ramTotalSize; + final int ramFreeSize; + final String screenResolution; + final String osVersion; + final String deviceModel; + final Map externalStorage; + final Map internalStorage; + final List> simCards; + + @override + Map get data => { + 'id': id, + 'name': name, + 'version': version, + 'prettyName': prettyName, + 'hasGNSS': hasGNSS, + 'hasNFC': hasNFC, + 'hasBluetooth': hasBluetooth, + 'hasWlan': hasWlan, + 'maxCpuClockSpeed': maxCpuClockSpeed, + 'numberCpuCores': numberCpuCores, + 'batteryChargePercentage': batteryChargePercentage, + 'mainCameraResolution': mainCameraResolution, + 'frontalCameraResolution': frontalCameraResolution, + 'ramTotalSize': ramTotalSize, + 'ramFreeSize': ramFreeSize, + 'screenResolution': screenResolution, + 'osVersion': osVersion, + 'deviceModel': deviceModel, + 'externalStorage': externalStorage, + 'internalStorage': internalStorage, + 'simCards': simCards, + }; + + @override + String? get buildId => null; + + @override + List? get idLike => null; + + @override + String? get machineId => null; + + @override + String? get variant => null; + + @override + String? get variantId => null; + + @override + String? get versionCodename => null; + + @override + String? get versionId => null; + + @Deprecated('Use [data] getter instead') + @override + Map toMap() { + return data; + } +} diff --git a/packages/device_info_plus/device_info_plus_aurora/lib/device_info_plus_aurora.dart b/packages/device_info_plus/device_info_plus_aurora/lib/device_info_plus_aurora.dart new file mode 100644 index 0000000..28b5140 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/lib/device_info_plus_aurora.dart @@ -0,0 +1,94 @@ +import 'package:dbus/dbus.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:device_info_plus_aurora/ru_omp_deviceinfo_features.dart'; +import 'package:device_info_plus_aurora/ru_omp_deviceinfo_sim.dart'; +import 'package:device_info_plus_aurora/ru_omp_deviceinfo_storages.dart'; +import 'package:device_info_plus_platform_interface/device_info_plus_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'aurora_device_info.dart'; + +class DeviceInfoPlusAurora extends DeviceInfoPlatform { + /// Register this dart class as the platform implementation for aurora + static void registerWith() { + if (TargetPlatform.aurora == defaultTargetPlatform) { + DeviceInfoPlatform.instance = DeviceInfoPlusAurora(); + } else { + DeviceInfoPlatform.instance = DeviceInfoPlusLinuxPlugin(); + } + } + + @override + Future deviceInfo() async { + final client = DBusClient.session(); + + // Features + final features = RuOmpDeviceinfoFeatures(client, 'ru.omp.deviceinfo', + DBusObjectPath('/ru/omp/deviceinfo/Features')); + + final hasGNSS = await features.callhasGNSS(); + final hasNFC = await features.callhasNFC(); + final hasBluetooth = await features.callhasBluetooth(); + final hasWlan = await features.callhasWlan(); + final maxCpuClockSpeed = await features.callgetMaxCpuClockSpeed(); + final numberCpuCores = await features.callgetNumberCpuCores(); + final batteryChargePercentage = + await features.callgetBatteryChargePercentage(); + final mainCameraResolution = await features.callgetMainCameraResolution(); + final frontalCameraResolution = + await features.callgetFrontalCameraResolution(); + final ramTotalSize = await features.callgetRamTotalSize(); + final ramFreeSize = await features.callgetRamFreeSize(); + final screenResolution = await features.callgetScreenResolution(); + final osVersion = await features.callgetOsVersion(); + final deviceModel = await features.callgetDeviceModel(); + + // Storages + final storages = RuOmpDeviceinfoStorages(client, 'ru.omp.deviceinfo', + DBusObjectPath('/ru/omp/deviceinfo/Storages')); + + final Map internalStorage = {}; + (await storages.callgetInternalStorageInfo()).forEach((key, value) { + internalStorage[key] = value.toNative(); + }); + + final Map externalStorage = {}; + (await storages.callgetExternalStorageInfo()).forEach((key, value) { + externalStorage[key] = value.toNative(); + }); + + // SIM + final infoSIM = RuOmpDeviceinfoSIM( + client, 'ru.omp.deviceinfo', DBusObjectPath('/ru/omp/deviceinfo/SIM')); + + final List> simCards = []; + for (var element in await infoSIM.callgetSimCardsInfo()) { + final Map simCard = {}; + element.forEach((key, value) { + simCard[key] = value.toNative(); + }); + simCards.add(simCard); + } + + await client.close(); + + return AuroraDeviceInfo( + hasGNSS: hasGNSS, + hasNFC: hasNFC, + hasBluetooth: hasBluetooth, + hasWlan: hasWlan, + maxCpuClockSpeed: maxCpuClockSpeed, + numberCpuCores: numberCpuCores, + batteryChargePercentage: batteryChargePercentage, + mainCameraResolution: mainCameraResolution, + frontalCameraResolution: frontalCameraResolution, + ramTotalSize: ramTotalSize, + ramFreeSize: ramFreeSize, + screenResolution: screenResolution, + osVersion: osVersion, + deviceModel: deviceModel, + externalStorage: externalStorage, + internalStorage: internalStorage, + simCards: simCards, + ); + } +} diff --git a/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_features.dart b/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_features.dart new file mode 100644 index 0000000..0d79b84 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_features.dart @@ -0,0 +1,237 @@ +// This file was generated using the following command and may be overwritten. +// dart-dbus generate-remote-object data/ru.omp.deviceinfo.Features.xml + +import 'package:dbus/dbus.dart'; + +/// dart-dbus generate-remote-object data/ru.omp.deviceinfo.Features.xml -o lib/ru_omp_deviceinfo_features.dart +/// Signal data for ru.omp.deviceinfo.Features.cameraEnabledChanged. +class RuOmpDeviceinfoFeaturescameraEnabledChanged extends DBusSignal { + bool get enabled => values[0].asBoolean(); + + RuOmpDeviceinfoFeaturescameraEnabledChanged(DBusSignal signal) + : super( + sender: signal.sender, + path: signal.path, + interface: signal.interface, + name: signal.name, + values: signal.values); +} + +class RuOmpDeviceinfoFeatures extends DBusRemoteObject { + /// Stream of ru.omp.deviceinfo.Features.cameraEnabledChanged signals. + late final Stream + cameraEnabledChanged; + + RuOmpDeviceinfoFeatures( + DBusClient client, String destination, DBusObjectPath path) + : super(client, name: destination, path: path) { + cameraEnabledChanged = DBusRemoteObjectSignalStream( + object: this, + interface: 'ru.omp.deviceinfo.Features', + name: 'cameraEnabledChanged', + signature: DBusSignature('b')) + .asBroadcastStream() + .map((signal) => RuOmpDeviceinfoFeaturescameraEnabledChanged(signal)); + } + + /// Invokes ru.omp.deviceinfo.Features.hasGNSS() + Future callhasGNSS( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod('ru.omp.deviceinfo.Features', 'hasGNSS', [], + replySignature: DBusSignature('b'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asBoolean(); + } + + /// Invokes ru.omp.deviceinfo.Features.hasNFC() + Future callhasNFC( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod('ru.omp.deviceinfo.Features', 'hasNFC', [], + replySignature: DBusSignature('b'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asBoolean(); + } + + /// Invokes ru.omp.deviceinfo.Features.hasBluetooth() + Future callhasBluetooth( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'hasBluetooth', [], + replySignature: DBusSignature('b'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asBoolean(); + } + + /// Invokes ru.omp.deviceinfo.Features.hasWlan() + Future callhasWlan( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod('ru.omp.deviceinfo.Features', 'hasWlan', [], + replySignature: DBusSignature('b'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asBoolean(); + } + + /// Invokes ru.omp.deviceinfo.Features.getMaxCpuClockSpeed() + Future callgetMaxCpuClockSpeed( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getMaxCpuClockSpeed', [], + replySignature: DBusSignature('u'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asUint32(); + } + + /// Invokes ru.omp.deviceinfo.Features.getNumberCpuCores() + Future callgetNumberCpuCores( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getNumberCpuCores', [], + replySignature: DBusSignature('u'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asUint32(); + } + + /// Invokes ru.omp.deviceinfo.Features.getCpuModel() + Future callgetCpuModel( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getCpuModel', [], + replySignature: DBusSignature('s'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asString(); + } + + /// Invokes ru.omp.deviceinfo.Features.getMaxCpuCoresClockSpeed() + Future> callgetMaxCpuCoresClockSpeed( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getMaxCpuCoresClockSpeed', [], + replySignature: DBusSignature('av'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asVariantArray().toList(); + } + + /// Invokes ru.omp.deviceinfo.Features.getBatteryChargePercentage() + Future callgetBatteryChargePercentage( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getBatteryChargePercentage', [], + replySignature: DBusSignature('u'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asUint32(); + } + + /// Invokes ru.omp.deviceinfo.Features.getMainCameraResolution() + Future callgetMainCameraResolution( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getMainCameraResolution', [], + replySignature: DBusSignature('d'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asDouble(); + } + + /// Invokes ru.omp.deviceinfo.Features.getFrontalCameraResolution() + Future callgetFrontalCameraResolution( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getFrontalCameraResolution', [], + replySignature: DBusSignature('d'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asDouble(); + } + + /// Invokes ru.omp.deviceinfo.Features.getRamTotalSize() + Future callgetRamTotalSize( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getRamTotalSize', [], + replySignature: DBusSignature('t'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asUint64(); + } + + /// Invokes ru.omp.deviceinfo.Features.getRamFreeSize() + Future callgetRamFreeSize( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getRamFreeSize', [], + replySignature: DBusSignature('t'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asUint64(); + } + + /// Invokes ru.omp.deviceinfo.Features.getScreenResolution() + Future callgetScreenResolution( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getScreenResolution', [], + replySignature: DBusSignature('s'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asString(); + } + + /// Invokes ru.omp.deviceinfo.Features.getOsVersion() + Future callgetOsVersion( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getOsVersion', [], + replySignature: DBusSignature('s'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asString(); + } + + /// Invokes ru.omp.deviceinfo.Features.getDeviceModel() + Future callgetDeviceModel( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getDeviceModel', [], + replySignature: DBusSignature('s'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asString(); + } + + /// Invokes ru.omp.deviceinfo.Features.getSerialNumber() + Future callgetSerialNumber( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Features', 'getSerialNumber', [], + replySignature: DBusSignature('s'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asString(); + } +} diff --git a/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_sim.dart b/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_sim.dart new file mode 100644 index 0000000..129d1c8 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_sim.dart @@ -0,0 +1,105 @@ +// This file was generated using the following command and may be overwritten. +// dart-dbus generate-remote-object data/ru.omp.deviceinfo.SIM.xml + +import 'package:dbus/dbus.dart'; + +/// dart-dbus generate-remote-object data/ru.omp.deviceinfo.SIM.xml -o lib/ru_omp_deviceinfo_sim.dart +/// Signal data for ru.omp.deviceinfo.SIM.simCardsEnabledChanged. +class RuOmpDeviceinfoSIMsimCardsEnabledChanged extends DBusSignal { + List> get updatedSimCards => + values[0].asArray().map((child) => child.asStringVariantDict()).toList(); + + RuOmpDeviceinfoSIMsimCardsEnabledChanged(DBusSignal signal) + : super( + sender: signal.sender, + path: signal.path, + interface: signal.interface, + name: signal.name, + values: signal.values); +} + +/// Signal data for ru.omp.deviceinfo.SIM.preferredDataTransferSimChanged. +class RuOmpDeviceinfoSIMpreferredDataTransferSimChanged extends DBusSignal { + List> get updatedSimCards => + values[0].asArray().map((child) => child.asStringVariantDict()).toList(); + + RuOmpDeviceinfoSIMpreferredDataTransferSimChanged(DBusSignal signal) + : super( + sender: signal.sender, + path: signal.path, + interface: signal.interface, + name: signal.name, + values: signal.values); +} + +/// Signal data for ru.omp.deviceinfo.SIM.preferredVoiceCallSimChanged. +class RuOmpDeviceinfoSIMpreferredVoiceCallSimChanged extends DBusSignal { + List> get updatedSimCards => + values[0].asArray().map((child) => child.asStringVariantDict()).toList(); + + RuOmpDeviceinfoSIMpreferredVoiceCallSimChanged(DBusSignal signal) + : super( + sender: signal.sender, + path: signal.path, + interface: signal.interface, + name: signal.name, + values: signal.values); +} + +class RuOmpDeviceinfoSIM extends DBusRemoteObject { + /// Stream of ru.omp.deviceinfo.SIM.simCardsEnabledChanged signals. + late final Stream + simCardsEnabledChanged; + + /// Stream of ru.omp.deviceinfo.SIM.preferredDataTransferSimChanged signals. + late final Stream + preferredDataTransferSimChanged; + + /// Stream of ru.omp.deviceinfo.SIM.preferredVoiceCallSimChanged signals. + late final Stream + preferredVoiceCallSimChanged; + + RuOmpDeviceinfoSIM(DBusClient client, String destination, DBusObjectPath path) + : super(client, name: destination, path: path) { + simCardsEnabledChanged = DBusRemoteObjectSignalStream( + object: this, + interface: 'ru.omp.deviceinfo.SIM', + name: 'simCardsEnabledChanged', + signature: DBusSignature('aa{sv}')) + .asBroadcastStream() + .map((signal) => RuOmpDeviceinfoSIMsimCardsEnabledChanged(signal)); + + preferredDataTransferSimChanged = DBusRemoteObjectSignalStream( + object: this, + interface: 'ru.omp.deviceinfo.SIM', + name: 'preferredDataTransferSimChanged', + signature: DBusSignature('aa{sv}')) + .asBroadcastStream() + .map((signal) => + RuOmpDeviceinfoSIMpreferredDataTransferSimChanged(signal)); + + preferredVoiceCallSimChanged = DBusRemoteObjectSignalStream( + object: this, + interface: 'ru.omp.deviceinfo.SIM', + name: 'preferredVoiceCallSimChanged', + signature: DBusSignature('aa{sv}')) + .asBroadcastStream() + .map( + (signal) => RuOmpDeviceinfoSIMpreferredVoiceCallSimChanged(signal)); + } + + /// Invokes ru.omp.deviceinfo.SIM.getSimCardsInfo() + Future>> callgetSimCardsInfo( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.SIM', 'getSimCardsInfo', [], + replySignature: DBusSignature('aa{sv}'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0] + .asArray() + .map((child) => child.asStringVariantDict()) + .toList(); + } +} diff --git a/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_storages.dart b/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_storages.dart new file mode 100644 index 0000000..ee8a96d --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/lib/ru_omp_deviceinfo_storages.dart @@ -0,0 +1,72 @@ +// This file was generated using the following command and may be overwritten. +// dart-dbus generate-remote-object data/ru.omp.deviceinfo.Storages.xml + +import 'package:dbus/dbus.dart'; + +/// dart-dbus generate-remote-object data/ru.omp.deviceinfo.Storages.xml -o lib/ru_omp_deviceinfo_storages.dart +/// Signal data for ru.omp.deviceinfo.Storages.externalStorageChanged. +class RuOmpDeviceinfoStoragesexternalStorageChanged extends DBusSignal { + Map get updatedStorage => values[0].asStringVariantDict(); + + RuOmpDeviceinfoStoragesexternalStorageChanged(DBusSignal signal) + : super( + sender: signal.sender, + path: signal.path, + interface: signal.interface, + name: signal.name, + values: signal.values); +} + +class RuOmpDeviceinfoStorages extends DBusRemoteObject { + /// Stream of ru.omp.deviceinfo.Storages.externalStorageChanged signals. + late final Stream + externalStorageChanged; + + RuOmpDeviceinfoStorages( + DBusClient client, String destination, DBusObjectPath path) + : super(client, name: destination, path: path) { + externalStorageChanged = DBusRemoteObjectSignalStream( + object: this, + interface: 'ru.omp.deviceinfo.Storages', + name: 'externalStorageChanged', + signature: DBusSignature('a{sv}')) + .asBroadcastStream() + .map((signal) => RuOmpDeviceinfoStoragesexternalStorageChanged(signal)); + } + + /// Invokes ru.omp.deviceinfo.Storages.getInternalStorageInfo() + Future> callgetInternalStorageInfo( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Storages', 'getInternalStorageInfo', [], + replySignature: DBusSignature('a{sv}'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asStringVariantDict(); + } + + /// Invokes ru.omp.deviceinfo.Storages.getInternalUserPartitionInfo() + Future> callgetInternalUserPartitionInfo( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Storages', 'getInternalUserPartitionInfo', [], + replySignature: DBusSignature('a{sv}'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asStringVariantDict(); + } + + /// Invokes ru.omp.deviceinfo.Storages.getExternalStorageInfo() + Future> callgetExternalStorageInfo( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'ru.omp.deviceinfo.Storages', 'getExternalStorageInfo', [], + replySignature: DBusSignature('a{sv}'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asStringVariantDict(); + } +} diff --git a/packages/device_info_plus/device_info_plus_aurora/pubspec.yaml b/packages/device_info_plus/device_info_plus_aurora/pubspec.yaml new file mode 100644 index 0000000..76e1962 --- /dev/null +++ b/packages/device_info_plus/device_info_plus_aurora/pubspec.yaml @@ -0,0 +1,27 @@ +name: device_info_plus_aurora +description: The Aurora OS implementation of flutter_local_notifications. +version: 0.0.1 +homepage: + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + dbus: ^0.7.8 + device_info_plus: ^9.0.1 + plugin_platform_interface: ^2.0.2 + device_info_plus_platform_interface: ^7.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + aurora: + dartPluginClass: DeviceInfoPlusAurora diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/.gitignore b/packages/flutter_local_notifications/flutter_local_notifications_aurora/.gitignore new file mode 100644 index 0000000..9dcdec5 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/.gitignore @@ -0,0 +1,32 @@ +# 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/ +.packages +build/ +run.sh +.metadata diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/README.md b/packages/flutter_local_notifications/flutter_local_notifications_aurora/README.md new file mode 100644 index 0000000..7bdbd98 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/README.md @@ -0,0 +1,18 @@ +# flutter_local_notifications_aurora + +The Aurora implementation of [`flutter_local_notifications`][https://github.com/MaikuB/flutter_local_notifications]. + +## Usage +This package is not an _endorsed_ implementation of `flutter_local_notifications`. +Therefore, you have to include `flutter_local_notifications_aurora` alongside `flutter_local_notifications` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + flutter_local_notifications: 14.0.0+2 + flutter_local_notifications_aurora: + path: # path to folder with plugin +``` + +### Preview example + +![preview.png](data%2Fpreview.gif) diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/analysis_options.yaml b/packages/flutter_local_notifications/flutter_local_notifications_aurora/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/data/org.freedesktop.Notifications.xml b/packages/flutter_local_notifications/flutter_local_notifications_aurora/data/org.freedesktop.Notifications.xml new file mode 100644 index 0000000..62345f2 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/data/org.freedesktop.Notifications.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/data/preview.gif b/packages/flutter_local_notifications/flutter_local_notifications_aurora/data/preview.gif new file mode 100644 index 0000000..f2975bf Binary files /dev/null and b/packages/flutter_local_notifications/flutter_local_notifications_aurora/data/preview.gif differ diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/.gitignore b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/.gitignore new file mode 100644 index 0000000..88d5455 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/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 +.packages +.pub-cache/ +.pub/ +/build/ +.metadata +pubspec.lock + +# 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 diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/README.md b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/README.md new file mode 100644 index 0000000..0ac9bde --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/README.md @@ -0,0 +1,17 @@ +# flutter_local_notifications_aurora_example + +Demonstrates how to use the flutter_local_notifications_aurora plugin. + +## Build + +```shell +# Add an alias if it doesn't already exist +alias flutter-aurora=$HOME/.local/opt/flutter-sdk/bin/flutter +# Get dependencies +flutter-aurora pub get +# Run build +flutter-aurora build aurora --release +``` + +You can collect, sign, run an example on the device with a script located in the `script/build_example.sh` +More information in `build_example.sh`. diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/analysis_options.yaml b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/.gitignore b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/CMakeLists.txt b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..94479ca --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.flutter_local_notifications_aurora_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/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/desktop/com.example.flutter_local_notifications_aurora_example.desktop b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/desktop/com.example.flutter_local_notifications_aurora_example.desktop new file mode 100644 index 0000000..d2a6a7e --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/desktop/com.example.flutter_local_notifications_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=Flutter Notification +Comment=Demonstrates how to use the flutter_local_notifications_aurora plugin. +Icon=com.example.flutter_local_notifications_aurora_example +Exec=/usr/bin/com.example.flutter_local_notifications_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=flutter_local_notifications_aurora_example diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..d6335b1 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + std::make_shared(), + }); +} diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..4957f88 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,31 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST + package_info_plus_aurora +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/108x108.png b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/128x128.png b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/172x172.png b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/86x86.png b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/main.cpp b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/rpm/com.example.flutter_local_notifications_aurora_example.spec b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/rpm/com.example.flutter_local_notifications_aurora_example.spec new file mode 100644 index 0000000..640103e --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/aurora/rpm/com.example.flutter_local_notifications_aurora_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.flutter_local_notifications_aurora_example +Summary: Demonstrates how to use the flutter_local_notifications_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/lib/main.dart b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/lib/main.dart new file mode 100644 index 0000000..5b62e87 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/lib/main.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + final notificationID = 1; + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + @override + void initState() { + super.initState(); + } + + Future _showNotification() async { + await flutterLocalNotificationsPlugin.show( + notificationID, + "Title notification", + "My long body text notification", + null, + ); + } + + Future _cancelNotification() async { + await flutterLocalNotificationsPlugin.cancel(notificationID); + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + const spaceMedium = SizedBox(height: 20); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example flutter_local_notifications'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + // Info + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: const Text( + 'Demo application demonstration implementation of flutter_local_notifications', + style: textStyleWhite, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + + ElevatedButton( + onPressed: _showNotification, + child: const Text('Show notification'), + ), + + spaceMedium, + + ElevatedButton( + onPressed: _cancelNotification, + child: const Text('Cancel notification'), + ) + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/pubspec.yaml b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/pubspec.yaml new file mode 100644 index 0000000..45b6d4f --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/example/pubspec.yaml @@ -0,0 +1,23 @@ +name: flutter_local_notifications_aurora_example +description: Demonstrates how to use the flutter_local_notifications_aurora plugin. + +publish_to: 'none' + +environment: + sdk: '>=2.18.6 <3.0.0' + +dependencies: + flutter: + sdk: flutter + flutter_local_notifications: ^14.0.0+2 + flutter_local_notifications_aurora: + path: ../ + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/lib/flutter_local_notifications_aurora.dart b/packages/flutter_local_notifications/flutter_local_notifications_aurora/lib/flutter_local_notifications_aurora.dart new file mode 100644 index 0000000..a4a1151 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/lib/flutter_local_notifications_aurora.dart @@ -0,0 +1,63 @@ +import 'dart:async'; + +import 'package:dbus/dbus.dart'; +import 'package:flutter_local_notifications_aurora/org_freedesktop_notifications.dart'; +import 'package:flutter_local_notifications_platform_interface/flutter_local_notifications_platform_interface.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class FlutterLocalNotificationsAurora + extends FlutterLocalNotificationsPlatform { + /// Registers this class as the default instance of [FlutterLocalNotificationsPlatform]. + static void registerWith() { + FlutterLocalNotificationsPlatform.instance = + FlutterLocalNotificationsAurora(); + } + + final Map> auroraIDs = {}; + + @override + Future show( + int id, + String? title, + String? body, { + String? payload, + }) async { + final appName = (await PackageInfo.fromPlatform()).appName; + final client = DBusClient.session(); + + final object = OrgFreedesktopNotifications( + client, + 'org.freedesktop.Notifications', + DBusObjectPath('/org/freedesktop/Notifications')); + + auroraIDs[id] = auroraIDs[id] ?? []; + auroraIDs[id]!.add(await object.callNotify( + appName, + 0, + ' ', + title ?? '', + body ?? '', + [], + {}, + -1, + )); + await client.close(); + } + + @override + Future cancel(int id) async { + final ids = auroraIDs[id] ?? []; + if (auroraIDs.isNotEmpty) { + final client = DBusClient.session(); + final object = OrgFreedesktopNotifications( + client, + 'org.freedesktop.Notifications', + DBusObjectPath('/org/freedesktop/Notifications')); + for (final auroraID in ids) { + await object.callCloseNotification(auroraID); + } + ids.remove(id); + await client.close(); + } + } +} diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/lib/org_freedesktop_notifications.dart b/packages/flutter_local_notifications/flutter_local_notifications_aurora/lib/org_freedesktop_notifications.dart new file mode 100644 index 0000000..325fa10 --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/lib/org_freedesktop_notifications.dart @@ -0,0 +1,129 @@ +// This file was generated using the following command and may be overwritten. +// dart-dbus generate-remote-object data/org.freedesktop.Notifications.xml + +// dart pub global activate dbus +// dart-dbus generate-remote-object data/org.freedesktop.Notifications.xml -o lib/org_freedesktop_notifications.dart +import 'package:dbus/dbus.dart'; + +/// Signal data for org.freedesktop.Notifications.NotificationClosed. +class OrgFreedesktopNotificationsNotificationClosed extends DBusSignal { + int get id => values[0].asUint32(); + int get reason => values[1].asUint32(); + + OrgFreedesktopNotificationsNotificationClosed(DBusSignal signal) + : super( + sender: signal.sender, + path: signal.path, + interface: signal.interface, + name: signal.name, + values: signal.values); +} + +/// Signal data for org.freedesktop.Notifications.ActionInvoked. +class OrgFreedesktopNotificationsActionInvoked extends DBusSignal { + int get id => values[0].asUint32(); + String get action_key => values[1].asString(); + + OrgFreedesktopNotificationsActionInvoked(DBusSignal signal) + : super( + sender: signal.sender, + path: signal.path, + interface: signal.interface, + name: signal.name, + values: signal.values); +} + +class OrgFreedesktopNotifications extends DBusRemoteObject { + /// Stream of org.freedesktop.Notifications.NotificationClosed signals. + late final Stream + notificationClosed; + + /// Stream of org.freedesktop.Notifications.ActionInvoked signals. + late final Stream actionInvoked; + + OrgFreedesktopNotifications( + DBusClient client, String destination, DBusObjectPath path) + : super(client, name: destination, path: path) { + notificationClosed = DBusRemoteObjectSignalStream( + object: this, + interface: 'org.freedesktop.Notifications', + name: 'NotificationClosed', + signature: DBusSignature('uu')) + .asBroadcastStream() + .map((signal) => OrgFreedesktopNotificationsNotificationClosed(signal)); + + actionInvoked = DBusRemoteObjectSignalStream( + object: this, + interface: 'org.freedesktop.Notifications', + name: 'ActionInvoked', + signature: DBusSignature('us')) + .asBroadcastStream() + .map((signal) => OrgFreedesktopNotificationsActionInvoked(signal)); + } + + /// Invokes org.freedesktop.Notifications.Notify() + Future callNotify( + String app_name, + int replaces_id, + String app_icon, + String summary, + String body, + List actions, + Map hints, + int timeout, + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'org.freedesktop.Notifications', + 'Notify', + [ + DBusString(app_name), + DBusUint32(replaces_id), + DBusString(app_icon), + DBusString(summary), + DBusString(body), + DBusArray.string(actions), + DBusDict.stringVariant(hints), + DBusInt32(timeout) + ], + replySignature: DBusSignature('u'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asUint32(); + } + + /// Invokes org.freedesktop.Notifications.CloseNotification() + Future callCloseNotification(int id, + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + await callMethod( + 'org.freedesktop.Notifications', 'CloseNotification', [DBusUint32(id)], + replySignature: DBusSignature(''), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + } + + /// Invokes org.freedesktop.Notifications.GetCapabilities() + Future> callGetCapabilities( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'org.freedesktop.Notifications', 'GetCapabilities', [], + replySignature: DBusSignature('as'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues[0].asStringArray().toList(); + } + + /// Invokes org.freedesktop.Notifications.GetServerInformation() + Future> callGetServerInformation( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + var result = await callMethod( + 'org.freedesktop.Notifications', 'GetServerInformation', [], + replySignature: DBusSignature('ssss'), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + return result.returnValues; + } +} diff --git a/packages/flutter_local_notifications/flutter_local_notifications_aurora/pubspec.yaml b/packages/flutter_local_notifications/flutter_local_notifications_aurora/pubspec.yaml new file mode 100644 index 0000000..742f4ac --- /dev/null +++ b/packages/flutter_local_notifications/flutter_local_notifications_aurora/pubspec.yaml @@ -0,0 +1,29 @@ +name: flutter_local_notifications_aurora +description: The Aurora OS implementation of flutter_local_notifications. +version: 0.0.1 +homepage: + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + dbus: ^0.7.8 + plugin_platform_interface: ^2.0.2 + flutter_local_notifications_platform_interface: ^7.0.0 + package_info_plus: ^4.0.0 + package_info_plus_aurora: + path: ../../package_info_plus/package_info_plus_aurora + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + aurora: + dartPluginClass: FlutterLocalNotificationsAurora diff --git a/packages/package_info_plus/package_info_plus_aurora/.gitignore b/packages/package_info_plus/package_info_plus_aurora/.gitignore new file mode 100644 index 0000000..c1631bb --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/.gitignore @@ -0,0 +1,32 @@ +# 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/ +.packages +build/ +.metadata +run.sh diff --git a/packages/package_info_plus/package_info_plus_aurora/README.md b/packages/package_info_plus/package_info_plus_aurora/README.md new file mode 100644 index 0000000..1a9dc6c --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/README.md @@ -0,0 +1,18 @@ +# package_info_plus_aurora + +The Aurora implementation of [`package_info_plus`](https://pub.dev/packages/package_info_plus). + +## Usage +This package is not an _endorsed_ implementation of `package_info_plus`. +Therefore, you have to include `package_info_plus_aurora` alongside `package_info_plus` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + package_info_plus: 4.0.0 + package_info_plus_aurora: + path: # path to folder with plugin +``` + +### Preview example + +![preview.png](data%2Fpreview.png) diff --git a/packages/package_info_plus/package_info_plus_aurora/analysis_options.yaml b/packages/package_info_plus/package_info_plus_aurora/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/package_info_plus/package_info_plus_aurora/aurora/CMakeLists.txt b/packages/package_info_plus/package_info_plus_aurora/aurora/CMakeLists.txt new file mode 100644 index 0000000..59838f1 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/aurora/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) + +set(PROJECT_NAME package_info_plus_aurora) +set(PLUGIN_NAME package_info_plus_aurora_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") + +find_package(PkgConfig REQUIRED) +pkg_check_modules(FlutterEmbedder REQUIRED IMPORTED_TARGET flutter-embedder) + +add_library(${PLUGIN_NAME} SHARED package_info_plus_aurora_plugin.cpp) + +set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::FlutterEmbedder) + +target_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_compile_definitions(${PLUGIN_NAME} PRIVATE PLUGIN_IMPL) diff --git a/packages/package_info_plus/package_info_plus_aurora/aurora/include/package_info_plus_aurora/package_info_plus_aurora_plugin.h b/packages/package_info_plus/package_info_plus_aurora/aurora/include/package_info_plus_aurora/package_info_plus_aurora_plugin.h new file mode 100644 index 0000000..3e4f80b --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/aurora/include/package_info_plus_aurora/package_info_plus_aurora_plugin.h @@ -0,0 +1,24 @@ +#ifndef FLUTTER_PLUGIN_PACKAGE_INFO_PLUS_AURORA_PLUGIN_H +#define FLUTTER_PLUGIN_PACKAGE_INFO_PLUS_AURORA_PLUGIN_H + +#include + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +class PLUGIN_EXPORT PackageInfoPlusAuroraPlugin final : public PluginInterface +{ +public: + void RegisterWithRegistrar(PluginRegistrar ®istrar) override; + +private: + void onMethodCall(const MethodCall &call); + void onGetApplicationOrg(const MethodCall &call); + void onGetApplicationName(const MethodCall &call); + void unimplemented(const MethodCall &call); +}; + +#endif /* FLUTTER_PLUGIN_PACKAGE_INFO_PLUS_AURORA_PLUGIN_H */ diff --git a/packages/package_info_plus/package_info_plus_aurora/aurora/package_info_plus_aurora_plugin.cpp b/packages/package_info_plus/package_info_plus_aurora/aurora/package_info_plus_aurora_plugin.cpp new file mode 100644 index 0000000..064a3b6 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/aurora/package_info_plus_aurora_plugin.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +void PackageInfoPlusAuroraPlugin::RegisterWithRegistrar(PluginRegistrar ®istrar) +{ + registrar.RegisterMethodChannel("package_info_plus_aurora", + MethodCodecType::Standard, + [this](const MethodCall &call) { this->onMethodCall(call); }); +} + +void PackageInfoPlusAuroraPlugin::onMethodCall(const MethodCall &call) +{ + const auto &method = call.GetMethod(); + + if (method == "getApplicationOrg") { + onGetApplicationOrg(call); + return; + } + + if (method == "getApplicationName") { + onGetApplicationName(call); + return; + } + + unimplemented(call); +} + +void PackageInfoPlusAuroraPlugin::onGetApplicationOrg(const MethodCall &call) +{ + call.SendSuccessResponse(Application::GetID().orgname); +} + +void PackageInfoPlusAuroraPlugin::onGetApplicationName(const MethodCall &call) +{ + call.SendSuccessResponse(Application::GetID().appname); +} + +void PackageInfoPlusAuroraPlugin::unimplemented(const MethodCall &call) +{ + call.SendSuccessResponse(nullptr); +} diff --git a/packages/package_info_plus/package_info_plus_aurora/data/preview.png b/packages/package_info_plus/package_info_plus_aurora/data/preview.png new file mode 100644 index 0000000..e70f4b1 Binary files /dev/null and b/packages/package_info_plus/package_info_plus_aurora/data/preview.png differ diff --git a/packages/package_info_plus/package_info_plus_aurora/example/.gitignore b/packages/package_info_plus/package_info_plus_aurora/example/.gitignore new file mode 100644 index 0000000..065dc2c --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/.gitignore @@ -0,0 +1,45 @@ +# 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 +.packages +.pub-cache/ +.pub/ +/build/ +pubspec.lock + +# 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 diff --git a/packages/package_info_plus/package_info_plus_aurora/example/README.md b/packages/package_info_plus/package_info_plus_aurora/example/README.md new file mode 100644 index 0000000..7d1c871 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/README.md @@ -0,0 +1,17 @@ +# package_info_plus_aurora_example + +Demonstrates how to use the package_info_plus_aurora_example plugin. + +## Build + +```shell +# Add an alias if it doesn't already exist +alias flutter-aurora=$HOME/.local/opt/flutter-sdk/bin/flutter +# Get dependencies +flutter-aurora pub get +# Run build +flutter-aurora build aurora --release +``` + +You can collect, sign, run an example on the device with a script located in the `script/build_example.sh` +More information in `build_example.sh`. diff --git a/packages/package_info_plus/package_info_plus_aurora/example/analysis_options.yaml b/packages/package_info_plus/package_info_plus_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/.gitignore b/packages/package_info_plus/package_info_plus_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/CMakeLists.txt b/packages/package_info_plus/package_info_plus_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..43c5051 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.package_info_plus_aurora_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/packages/package_info_plus/package_info_plus_aurora/example/aurora/desktop/com.example.package_info_plus_aurora_example.desktop b/packages/package_info_plus/package_info_plus_aurora/example/aurora/desktop/com.example.package_info_plus_aurora_example.desktop new file mode 100644 index 0000000..1e01856 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/desktop/com.example.package_info_plus_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=Example Info Plus +Comment=Demonstrates how to use the package_info_plus_aurora plugin. +Icon=com.example.package_info_plus_aurora_example +Exec=/usr/bin/com.example.package_info_plus_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=package_info_plus_aurora_example diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..d6335b1 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + std::make_shared(), + }); +} diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..4957f88 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,31 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST + package_info_plus_aurora +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/108x108.png b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/128x128.png b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/172x172.png b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/86x86.png b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/package_info_plus/package_info_plus_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/main.cpp b/packages/package_info_plus/package_info_plus_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/package_info_plus/package_info_plus_aurora/example/aurora/rpm/com.example.package_info_plus_aurora_example.spec b/packages/package_info_plus/package_info_plus_aurora/example/aurora/rpm/com.example.package_info_plus_aurora_example.spec new file mode 100644 index 0000000..6a0c214 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/aurora/rpm/com.example.package_info_plus_aurora_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.package_info_plus_aurora_example +Summary: Demonstrates how to use the package_info_plus_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/package_info_plus/package_info_plus_aurora/example/lib/main.dart b/packages/package_info_plus/package_info_plus_aurora/example/lib/main.dart new file mode 100644 index 0000000..2dfbe8d --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/lib/main.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:package_info_plus/package_info_plus.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String? _error; + String? _appName; + String? _packageName; + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + Future initPlatformState() async { + try { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + + String appName = packageInfo.appName; + String packageName = packageInfo.packageName; + + // Update state variable + setState(() { + _appName = appName; + _packageName = packageName; + }); + } on Exception catch (e) { + setState(() { + _error = e.toString(); + }); + } + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + const textStyleTitle = TextStyle(fontSize: 20, color: Colors.black); + const textStylePath = TextStyle(fontSize: 18, color: Colors.black54); + + const spaceMedium = SizedBox(height: 20); + const spaceSmall = SizedBox(height: 10); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example package_info_plus'), + ), + body: Stack( + children: [ + // Error message + Visibility( + visible: _error != null, + child: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.redAccent, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + _error ?? '', + style: textStyleWhite, + ), + ), + ), + ), + ), + // List directories path + Visibility( + visible: _error == null, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + // Info + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: + BorderRadius.all(Radius.circular(10.0)), + ), + child: const Text( + 'Demo application demonstration implementation of package_info_plus', + style: textStyleWhite, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + + const Text( + 'Application Name', + style: textStyleTitle, + ), + spaceSmall, + Text( + _appName ?? 'Not found.', + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Package Name', + style: textStyleTitle, + ), + spaceSmall, + Text( + _packageName ?? 'Not found.', + style: textStylePath, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/package_info_plus/package_info_plus_aurora/example/pubspec.yaml b/packages/package_info_plus/package_info_plus_aurora/example/pubspec.yaml new file mode 100644 index 0000000..ba56b59 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/example/pubspec.yaml @@ -0,0 +1,23 @@ +name: package_info_plus_aurora_example +description: Demonstrates how to use the package_info_plus_aurora plugin. + +publish_to: 'none' + +environment: + sdk: '>=2.18.6 <3.0.0' + +dependencies: + flutter: + sdk: flutter + package_info_plus: ^4.0.0 + package_info_plus_aurora: + path: ../ + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true diff --git a/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora.dart b/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora.dart new file mode 100644 index 0000000..a6d2eab --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora.dart @@ -0,0 +1,56 @@ +import 'dart:io'; +import 'package:flutter/widgets.dart'; +import 'package_info_plus_aurora_platform_interface.dart'; +import 'package:package_info_plus_platform_interface/package_info_platform_interface.dart'; +import 'package:package_info_plus_platform_interface/package_info_data.dart'; + +class PackageInfoPlusAurora extends PackageInfoPlatform { + /// Register this dart class as the platform implementation for aurora + static void registerWith() { + PackageInfoPlatform.instance = PackageInfoPlusAurora(); + } + + final _platform = PackageInfoPlusAuroraPlatform.instance; + + @override + Future getAll() async { + final versionJson = await _getVersionJson(); + return PackageInfoData( + appName: versionJson['app_name'] ?? '', + packageName: versionJson['package_name'] ?? '', + version: versionJson['version'] ?? '', + buildNumber: versionJson['build_number'] ?? '', + buildSignature: '', + ); + } + + Future> _getVersionJson() async { + try { + // Get package from aurora platform + final org = await _platform.getApplicationOrg(); + final name = await _platform.getApplicationName(); + final packageName = '$org.$name'; + + // Get application name + final desktop = + (await File('/usr/share/applications/$packageName.desktop') + .readAsLines()) + .where((element) => element.contains('Name=')); + + // @todo + // Get application versions + // rpm -q --queryformat %{VERSION} + // not working even with Compatibility permission + + return { + 'app_name': desktop.isNotEmpty ? desktop.first.substring(5) : null, + 'package_name': packageName, + 'version': '', + 'build_number': '', + }; + } catch (e) { + debugPrint(e.toString()); + return {}; + } + } +} diff --git a/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora_method_channel.dart b/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora_method_channel.dart new file mode 100644 index 0000000..28519ef --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora_method_channel.dart @@ -0,0 +1,21 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'package_info_plus_aurora_platform_interface.dart'; + +/// An implementation of [PackageInfoPlusAuroraPlatform] that uses method channels. +class MethodChannelPackageInfoPlusAurora extends PackageInfoPlusAuroraPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('package_info_plus_aurora'); + + @override + Future getApplicationOrg() async { + return await methodChannel.invokeMethod('getApplicationOrg'); + } + + @override + Future getApplicationName() async { + return await methodChannel.invokeMethod('getApplicationName'); + } +} diff --git a/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora_platform_interface.dart b/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora_platform_interface.dart new file mode 100644 index 0000000..070a634 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/lib/package_info_plus_aurora_platform_interface.dart @@ -0,0 +1,34 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'package_info_plus_aurora_method_channel.dart'; + +abstract class PackageInfoPlusAuroraPlatform extends PlatformInterface { + /// Constructs a PackageInfoPlusAuroraPlatform. + PackageInfoPlusAuroraPlatform() : super(token: _token); + + static final Object _token = Object(); + + static PackageInfoPlusAuroraPlatform _instance = + MethodChannelPackageInfoPlusAurora(); + + /// The default instance of [PackageInfoPlusAuroraPlatform] to use. + /// + /// Defaults to [MethodChannelPackageInfoPlusAurora]. + static PackageInfoPlusAuroraPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [PackageInfoPlusAuroraPlatform] when + /// they register themselves. + static set instance(PackageInfoPlusAuroraPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getApplicationOrg() { + throw UnimplementedError('getApplicationOrg() has not been implemented.'); + } + + Future getApplicationName() { + throw UnimplementedError('getApplicationName() has not been implemented.'); + } +} diff --git a/packages/package_info_plus/package_info_plus_aurora/pubspec.yaml b/packages/package_info_plus/package_info_plus_aurora/pubspec.yaml new file mode 100644 index 0000000..33ce5f6 --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/pubspec.yaml @@ -0,0 +1,26 @@ +name: package_info_plus_aurora +description: A new Flutter plugin project. +version: 0.0.1 +homepage: + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + package_info_plus_platform_interface: ^2.0.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + aurora: + pluginClass: PackageInfoPlusAuroraPlugin + dartPluginClass: PackageInfoPlusAurora diff --git a/packages/package_info_plus/package_info_plus_aurora/test/package_info_plus_aurora_method_channel_test.dart b/packages/package_info_plus/package_info_plus_aurora/test/package_info_plus_aurora_method_channel_test.dart new file mode 100644 index 0000000..4c3ad7c --- /dev/null +++ b/packages/package_info_plus/package_info_plus_aurora/test/package_info_plus_aurora_method_channel_test.dart @@ -0,0 +1,35 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:package_info_plus_aurora/package_info_plus_aurora_method_channel.dart'; + +void main() { + MethodChannelPackageInfoPlusAurora platform = + MethodChannelPackageInfoPlusAurora(); + const MethodChannel channel = MethodChannel('package_info_plus_aurora'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + switch (methodCall.method) { + case 'getApplicationOrg': + return 'com.example'; + case 'getApplicationName': + return 'path_provider_aurora'; + } + return ''; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('onGetApplicationOrg', () async { + expect(await platform.getApplicationOrg(), 'com.example'); + }); + + test('onGetApplicationName', () async { + expect(await platform.getApplicationName(), 'path_provider_aurora'); + }); +} diff --git a/packages/path_provider/path_provider_aurora/.gitignore b/packages/path_provider/path_provider_aurora/.gitignore new file mode 100644 index 0000000..6f08bb1 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/.gitignore @@ -0,0 +1,31 @@ +# 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/ +.packages +build/ +/.metadata diff --git a/packages/path_provider/path_provider_aurora/README.md b/packages/path_provider/path_provider_aurora/README.md new file mode 100644 index 0000000..d192514 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/README.md @@ -0,0 +1,41 @@ +# path_provider_aurora + +The Aurora OS implementation of [`path_provider`](https://pub.dev/packages/path_provider). +Documentation for setting permissions can be found [here](https://developer.auroraos.ru/doc/software_development/reference/user_data). + +## Usage + +This package is not an _endorsed_ implementation of `path_provider`. +Therefore, you have to include `path_provider_aurora` alongside `path_provider` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + path_provider: ^2.0.14 + path_provider_aurora: ^0.0.0 # @todo Not published +``` + +Then you can import `path_provider` in your Dart code: + +```dart +import 'package:path_provider/path_provider.dart'; +``` + +## Supported APIs + +- [x] `getTemporaryDirectory` +- [ ] `getApplicationSupportDirectory` +- [ ] `getLibraryDirectory` +- [x] `getApplicationDocumentsDirectory` +- [ ] `getExternalStorageDirectory` +- [ ] `getExternalCacheDirectories` +- [x] `getExternalStorageDirectories` (There is no concept of External in Aurora OS, but this interface allows you to get the pictures/music/movies directory) +- [x] `getDownloadsDirectory` + +## Extra methods + +PathProviderAurora.getApplicationOrg(); +PathProviderAurora.getApplicationName(); + +### Preview example + +![preview.png](data%2Fpreview.png) \ No newline at end of file diff --git a/packages/path_provider/path_provider_aurora/analysis_options.yaml b/packages/path_provider/path_provider_aurora/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/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/packages/path_provider/path_provider_aurora/aurora/CMakeLists.txt b/packages/path_provider/path_provider_aurora/aurora/CMakeLists.txt new file mode 100644 index 0000000..68335fe --- /dev/null +++ b/packages/path_provider/path_provider_aurora/aurora/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) + +set(PROJECT_NAME path_provider_aurora) +set(PLUGIN_NAME path_provider_aurora_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") + +find_package(PkgConfig REQUIRED) +pkg_check_modules(FlutterEmbedder REQUIRED IMPORTED_TARGET flutter-embedder) + +add_library(${PLUGIN_NAME} SHARED path_provider_aurora_plugin.cpp) + +set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::FlutterEmbedder) + +target_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_compile_definitions(${PLUGIN_NAME} PRIVATE PLUGIN_IMPL) diff --git a/packages/path_provider/path_provider_aurora/aurora/include/path_provider_aurora/path_provider_aurora_plugin.h b/packages/path_provider/path_provider_aurora/aurora/include/path_provider_aurora/path_provider_aurora_plugin.h new file mode 100644 index 0000000..d6c9faf --- /dev/null +++ b/packages/path_provider/path_provider_aurora/aurora/include/path_provider_aurora/path_provider_aurora_plugin.h @@ -0,0 +1,24 @@ +#ifndef FLUTTER_PLUGIN_PATH_PROVIDER_AURORA_PLUGIN_H +#define FLUTTER_PLUGIN_PATH_PROVIDER_AURORA_PLUGIN_H + +#include + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +class PLUGIN_EXPORT PathProviderAuroraPlugin final : public PluginInterface +{ +public: + void RegisterWithRegistrar(PluginRegistrar ®istrar) override; + +private: + void onMethodCall(const MethodCall &call); + void onGetApplicationOrg(const MethodCall &call); + void onGetApplicationName(const MethodCall &call); + void unimplemented(const MethodCall &call); +}; + +#endif /* FLUTTER_PLUGIN_PATH_PROVIDER_AURORA_PLUGIN_H */ diff --git a/packages/path_provider/path_provider_aurora/aurora/path_provider_aurora_plugin.cpp b/packages/path_provider/path_provider_aurora/aurora/path_provider_aurora_plugin.cpp new file mode 100644 index 0000000..04103cd --- /dev/null +++ b/packages/path_provider/path_provider_aurora/aurora/path_provider_aurora_plugin.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +void PathProviderAuroraPlugin::RegisterWithRegistrar(PluginRegistrar ®istrar) +{ + registrar.RegisterMethodChannel("path_provider_aurora", + MethodCodecType::Standard, + [this](const MethodCall &call) { this->onMethodCall(call); }); +} + +void PathProviderAuroraPlugin::onMethodCall(const MethodCall &call) +{ + const auto &method = call.GetMethod(); + + if (method == "getApplicationOrg") { + onGetApplicationOrg(call); + return; + } + + if (method == "getApplicationName") { + onGetApplicationName(call); + return; + } + + unimplemented(call); +} + +void PathProviderAuroraPlugin::onGetApplicationOrg(const MethodCall &call) +{ + call.SendSuccessResponse(Application::GetID().orgname); +} + +void PathProviderAuroraPlugin::onGetApplicationName(const MethodCall &call) +{ + call.SendSuccessResponse(Application::GetID().appname); +} + +void PathProviderAuroraPlugin::unimplemented(const MethodCall &call) +{ + call.SendSuccessResponse(nullptr); +} diff --git a/packages/path_provider/path_provider_aurora/data/preview.png b/packages/path_provider/path_provider_aurora/data/preview.png new file mode 100644 index 0000000..311649c Binary files /dev/null and b/packages/path_provider/path_provider_aurora/data/preview.png differ diff --git a/packages/path_provider/path_provider_aurora/example/.gitignore b/packages/path_provider/path_provider_aurora/example/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/.gitignore @@ -0,0 +1,44 @@ +# 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 +.packages +.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 diff --git a/packages/path_provider/path_provider_aurora/example/README.md b/packages/path_provider/path_provider_aurora/example/README.md new file mode 100644 index 0000000..9820643 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/README.md @@ -0,0 +1,16 @@ +# path_provider_aurora_example + +Demonstrates how to use the path_provider_aurora 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/packages/path_provider/path_provider_aurora/example/analysis_options.yaml b/packages/path_provider/path_provider_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/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/packages/path_provider/path_provider_aurora/example/aurora/.gitignore b/packages/path_provider/path_provider_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/path_provider/path_provider_aurora/example/aurora/CMakeLists.txt b/packages/path_provider/path_provider_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..9652e3f --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.path_provider_aurora_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/packages/path_provider/path_provider_aurora/example/aurora/desktop/com.example.path_provider_aurora_example.desktop b/packages/path_provider/path_provider_aurora/example/aurora/desktop/com.example.path_provider_aurora_example.desktop new file mode 100644 index 0000000..69eeffc --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/desktop/com.example.path_provider_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=path_provider_aurora_example +Comment=Demonstrates how to use the path_provider_aurora plugin. +Icon=com.example.path_provider_aurora_example +Exec=/usr/bin/com.example.path_provider_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions=UserDirs +OrganizationName=com.example +ApplicationName=path_provider_aurora_example diff --git a/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..bd5a44d --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + std::make_shared(), + }); +} diff --git a/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..fa1ac5f --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST + path_provider_aurora +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + xdga_directories +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/path_provider/path_provider_aurora/example/aurora/icons/108x108.png b/packages/path_provider/path_provider_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/path_provider/path_provider_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/path_provider/path_provider_aurora/example/aurora/icons/128x128.png b/packages/path_provider/path_provider_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/path_provider/path_provider_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/path_provider/path_provider_aurora/example/aurora/icons/172x172.png b/packages/path_provider/path_provider_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/path_provider/path_provider_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/path_provider/path_provider_aurora/example/aurora/icons/86x86.png b/packages/path_provider/path_provider_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/path_provider/path_provider_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/path_provider/path_provider_aurora/example/aurora/main.cpp b/packages/path_provider/path_provider_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/path_provider/path_provider_aurora/example/aurora/rpm/com.example.path_provider_aurora_example.spec b/packages/path_provider/path_provider_aurora/example/aurora/rpm/com.example.path_provider_aurora_example.spec new file mode 100644 index 0000000..fb1eaa6 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/aurora/rpm/com.example.path_provider_aurora_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.path_provider_aurora_example +Summary: Demonstrates how to use the path_provider_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/path_provider/path_provider_aurora/example/lib/main.dart b/packages/path_provider/path_provider_aurora/example/lib/main.dart new file mode 100644 index 0000000..156bb67 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/lib/main.dart @@ -0,0 +1,249 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path_provider_aurora/path_provider_aurora.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String? _error; + String? _applicationOrg; + String? _applicationName; + String? _pathApplicationSupportDirectory; + String? _pathTempDirectory; + String? _pathApplicationDocumentsPath; + String? _pathDownloadsPath; + String? _pathPictures; + String? _pathMusic; + String? _pathMovies; + + @override + void initState() { + super.initState(); + loadPathDirectory(); + } + + /// Asynchronous function receiving directory paths + Future loadPathDirectory() async { + try { + // Get names + String? applicationOrg = await PathProviderAurora.getApplicationOrg(); + String? applicationName = await PathProviderAurora.getApplicationName(); + + // Get directories + Directory? applicationSupportDirectory = await getApplicationSupportDirectory(); + Directory? tempDirectory = await getTemporaryDirectory(); + Directory? pathApplicationDocumentsPath = await getApplicationDocumentsDirectory(); + Directory? pathDownloadsPath = await getDownloadsDirectory(); + List? pathPictures = await getExternalStorageDirectories(type: StorageDirectory.pictures); + List? pathMusic = await getExternalStorageDirectories(type: StorageDirectory.music); + List? pathMovies = await getExternalStorageDirectories(type: StorageDirectory.movies); + + // Update state variable + setState(() { + _applicationOrg = applicationOrg; + _applicationName = applicationName; + + _pathApplicationSupportDirectory = applicationSupportDirectory.path; + _pathTempDirectory = tempDirectory.path; + _pathApplicationDocumentsPath = pathApplicationDocumentsPath.path; + _pathDownloadsPath = pathDownloadsPath?.path; + _pathPictures = pathPictures?.first.path; + _pathMusic = pathMusic?.first.path; + _pathMovies = pathMovies?.first.path; + }); + } on Exception catch (e) { + setState(() { + _error = e.toString(); + }); + } + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + const textStyleTitle = TextStyle(fontSize: 20, color: Colors.black); + const textStylePath = TextStyle(fontSize: 18, color: Colors.black54); + + const spaceMedium = SizedBox(height: 20); + const spaceSmall = SizedBox(height: 10); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example path_provider'), + ), + body: Stack( + children: [ + // Error message + Visibility( + visible: _error != null, + child: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.redAccent, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + _error ?? '', + style: textStyleWhite, + ), + ), + ), + ), + ), + // List directories path + Visibility( + visible: _error == null, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + // Info + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: const Text( + 'Demo application demonstration implementation of path_provider', + style: textStyleWhite, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + + // ApplicationOrg + const Text( + 'Application Org', + style: textStyleTitle, + ), + spaceSmall, + Text( + _applicationOrg ?? 'Not found.', + style: textStylePath, + ), + + spaceMedium, + // ApplicationName + const Text( + 'Application Name', + style: textStyleTitle, + ), + spaceSmall, + Text( + _applicationName ?? 'Not found.', + style: textStylePath, + ), + + spaceMedium, + // TempDirectory + const Text( + 'ApplicationSupportDirectory', + style: textStyleTitle, + ), + spaceSmall, + Text( + _pathApplicationSupportDirectory ?? 'Not found.', + style: textStylePath, + ), + + spaceMedium, + // TempDirectory + const Text( + 'TempDirectory', + style: textStyleTitle, + ), + spaceSmall, + Text( + _pathTempDirectory ?? 'Not found.', + style: textStylePath, + ), + spaceMedium, + + // ApplicationDocumentsPath + const Text( + 'ApplicationDocumentsPath', + style: textStyleTitle, + ), + spaceSmall, + Text( + _pathApplicationDocumentsPath ?? 'Not found.', + style: textStylePath, + ), + spaceMedium, + + // DownloadsPath + const Text( + 'DownloadsPath', + style: textStyleTitle, + ), + spaceSmall, + Text( + _pathDownloadsPath ?? 'Not found.', + style: textStylePath, + ), + spaceMedium, + + // Pictures + const Text( + 'Pictures', + style: textStyleTitle, + ), + spaceSmall, + Text( + _pathPictures ?? 'Not found.', + style: textStylePath, + ), + spaceMedium, + + // Music + const Text( + 'Music', + style: textStyleTitle, + ), + spaceSmall, + Text( + _pathMusic ?? 'Not found.', + style: textStylePath, + ), + spaceMedium, + + // Movies + const Text( + 'Movies', + style: textStyleTitle, + ), + spaceSmall, + Text( + _pathMovies ?? 'Not found.', + style: textStylePath, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/path_provider/path_provider_aurora/example/pubspec.lock b/packages/path_provider/path_provider_aurora/example/pubspec.lock new file mode 100644 index 0000000..db8cf64 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/pubspec.lock @@ -0,0 +1,266 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.14" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.26" + path_provider_aurora: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.12" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + xdga_directories: + dependency: transitive + description: + path: "../../../xdga_directories" + relative: false + source: path + version: "0.0.1" +sdks: + dart: ">=2.18.6 <3.0.0" + flutter: ">=3.3.0" diff --git a/packages/path_provider/path_provider_aurora/example/pubspec.yaml b/packages/path_provider/path_provider_aurora/example/pubspec.yaml new file mode 100644 index 0000000..3bb4f22 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/example/pubspec.yaml @@ -0,0 +1,85 @@ +name: path_provider_aurora_example +description: Demonstrates how to use the path_provider_aurora 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: '>=2.18.6 <3.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 + + path_provider: ^2.0.7 + path_provider_aurora: + # When depending on this package from a real application you should use: + # path_provider_aurora: ^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: + 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/packages/path_provider/path_provider_aurora/example/test/widget_test.dart b/packages/path_provider/path_provider_aurora/example/test/widget_test.dart new file mode 100644 index 0000000..47354c8 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/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:path_provider_aurora_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/packages/path_provider/path_provider_aurora/lib/path_provider_aurora.dart b/packages/path_provider/path_provider_aurora/lib/path_provider_aurora.dart new file mode 100644 index 0000000..285bf65 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/lib/path_provider_aurora.dart @@ -0,0 +1,81 @@ +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:xdga_directories/xdga_directories.dart' as xdga_directories; +import 'package:path/path.dart' as p; + +import 'path_provider_aurora_platform_interface.dart'; + +/// The aurora implementation of [PathProviderPlatform] +/// +/// This class implements the `package:path_provider` functionality for Aurora. +class PathProviderAurora extends PathProviderPlatform { + /// Registers this class as the default instance of [PathProviderPlatform] + static void registerWith() { + PathProviderPlatform.instance = PathProviderAurora(); + } + + /// Get application name + static Future getApplicationName() { + return PathProviderAuroraPlatform.instance.getApplicationName(); + } + + /// Get application org + static Future getApplicationOrg() { + return PathProviderAuroraPlatform.instance.getApplicationOrg(); + } + + /// Path to a directory where the application may place application support files. + @override + Future getApplicationSupportPath() async { + String? org = await getApplicationOrg(); + String? name = await getApplicationName(); + // QStandardPaths::AppDataLocation + return p.join(xdga_directories.getAppDataLocation(), org, name); + } + + /// Path to the temporary directory on the device that is not backed up and is + /// suitable for storing caches of downloaded files. + @override + Future getTemporaryPath() async { + String? org = await getApplicationOrg(); + String? name = await getApplicationName(); + // QStandardPaths::CacheLocation + return p.join(xdga_directories.getCacheLocation(), org, name); + } + + /// Path to a directory where the application may place data that is + /// user-generated, or that cannot otherwise be recreated by your application. + @override + Future getApplicationDocumentsPath() async { + // QStandardPaths::DocumentsLocation + return xdga_directories.getDocumentsLocation(); + } + + /// Path to the directory where downloaded files can be stored. + /// This is typically only relevant on desktop operating systems. + @override + Future getDownloadsPath() async { + // QStandardPaths::DownloadLocation + return xdga_directories.getDownloadLocation(); + } + + /// Paths to directories where application specific data can be stored. + /// These paths typically reside on external storage like separate partitions + /// or SD cards. Phones may have multiple storage directories available. + @override + Future?> getExternalStoragePaths({ + /// Optional parameter. See [StorageDirectory] for more informations on + /// how this type translates to Android storage directories. + StorageDirectory? type, + }) async { + switch (type) { + case StorageDirectory.pictures: + return [xdga_directories.getPicturesLocation()]; // QStandardPaths::PicturesLocation + case StorageDirectory.music: + return [xdga_directories.getMusicLocation()]; // QStandardPaths::MusicLocation + case StorageDirectory.movies: + return [xdga_directories.getMoviesLocation()]; // QStandardPaths::MoviesLocation + default: + throw UnimplementedError('Type "$type" not supported.'); + } + } +} diff --git a/packages/path_provider/path_provider_aurora/lib/path_provider_aurora_method_channel.dart b/packages/path_provider/path_provider_aurora/lib/path_provider_aurora_method_channel.dart new file mode 100644 index 0000000..2d275df --- /dev/null +++ b/packages/path_provider/path_provider_aurora/lib/path_provider_aurora_method_channel.dart @@ -0,0 +1,21 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'path_provider_aurora_platform_interface.dart'; + +/// An implementation of [PathProviderAuroraPlatform] that uses method channels. +class MethodChannelPathProviderAurora extends PathProviderAuroraPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('path_provider_aurora'); + + @override + Future getApplicationOrg() async { + return await methodChannel.invokeMethod('getApplicationOrg'); + } + + @override + Future getApplicationName() async { + return await methodChannel.invokeMethod('getApplicationName'); + } +} diff --git a/packages/path_provider/path_provider_aurora/lib/path_provider_aurora_platform_interface.dart b/packages/path_provider/path_provider_aurora/lib/path_provider_aurora_platform_interface.dart new file mode 100644 index 0000000..d465660 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/lib/path_provider_aurora_platform_interface.dart @@ -0,0 +1,33 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'path_provider_aurora_method_channel.dart'; + +abstract class PathProviderAuroraPlatform extends PlatformInterface { + /// Constructs a PathProviderAuroraPlatform. + PathProviderAuroraPlatform() : super(token: _token); + + static final Object _token = Object(); + + static PathProviderAuroraPlatform _instance = MethodChannelPathProviderAurora(); + + /// The default instance of [PathProviderAuroraPlatform] to use. + /// + /// Defaults to [MethodChannelPathProviderAurora]. + static PathProviderAuroraPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [PathProviderAuroraPlatform] when + /// they register themselves. + static set instance(PathProviderAuroraPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getApplicationOrg() { + throw UnimplementedError('getApplicationOrg() has not been implemented.'); + } + + Future getApplicationName() { + throw UnimplementedError('getApplicationName() has not been implemented.'); + } +} diff --git a/packages/path_provider/path_provider_aurora/pubspec.yaml b/packages/path_provider/path_provider_aurora/pubspec.yaml new file mode 100644 index 0000000..a37a56d --- /dev/null +++ b/packages/path_provider/path_provider_aurora/pubspec.yaml @@ -0,0 +1,32 @@ +name: path_provider_aurora +description: The Aurora OS implementation of path_provider. +version: 0.0.1 +homepage: https://os-git.omprussia.ru/non-oss/flutter/flutter-plugins/packages/xdga_directories + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +flutter: + plugin: + implements: path_provider + platforms: + aurora: + dartPluginClass: PathProviderAurora + pluginClass: PathProviderAuroraPlugin + +dependencies: + flutter: + sdk: flutter + path: ^1.8.2 + plugin_platform_interface: ^2.0.2 + path_provider_platform_interface: ^2.0.6 + xdga_directories: + path: ../../xdga_directories + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + + diff --git a/packages/path_provider/path_provider_aurora/test/path_provider_aurora_method_channel_test.dart b/packages/path_provider/path_provider_aurora/test/path_provider_aurora_method_channel_test.dart new file mode 100644 index 0000000..137a103 --- /dev/null +++ b/packages/path_provider/path_provider_aurora/test/path_provider_aurora_method_channel_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider_aurora/path_provider_aurora_method_channel.dart'; + +void main() { + MethodChannelPathProviderAurora platform = MethodChannelPathProviderAurora(); + const MethodChannel channel = MethodChannel('path_provider_aurora'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + switch (methodCall.method) { + case 'getApplicationOrg': + return 'com.example'; + case 'getApplicationName': + return 'path_provider_aurora'; + } + return ''; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('onGetApplicationOrg', () async { + expect(await platform.getApplicationOrg(), 'com.example'); + }); + + test('onGetApplicationName', () async { + expect(await platform.getApplicationName(), 'path_provider_aurora'); + }); +} diff --git a/packages/shared_preferences/shared_preferences_aurora/.gitignore b/packages/shared_preferences/shared_preferences_aurora/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/.gitignore @@ -0,0 +1,30 @@ +# 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/ +.packages +build/ diff --git a/packages/shared_preferences/shared_preferences_aurora/.metadata b/packages/shared_preferences/shared_preferences_aurora/.metadata new file mode 100644 index 0000000..a1644dc --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/.metadata @@ -0,0 +1,30 @@ +# 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: 135454af32477f815a7525073027a3ff9eff1bfd + channel: unknown + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 135454af32477f815a7525073027a3ff9eff1bfd + base_revision: 135454af32477f815a7525073027a3ff9eff1bfd + - platform: aurora + create_revision: 135454af32477f815a7525073027a3ff9eff1bfd + base_revision: 135454af32477f815a7525073027a3ff9eff1bfd + + # 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/packages/shared_preferences/shared_preferences_aurora/README.md b/packages/shared_preferences/shared_preferences_aurora/README.md new file mode 100644 index 0000000..da8b050 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/README.md @@ -0,0 +1,17 @@ +# shared_preferences_aurora + +The Aurora implementation of [`shared_preferences`][https://pub.dev/packages/shared_preferences]. + +## Usage +This package is not an _endorsed_ implementation of `shared_preferences`. +Therefore, you have to include `path_provider_aurora` alongside `shared_preferences` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + shared_preferences: ^2.1.1 + shared_preferences_aurora: ^0.0.0 # @todo Not published +``` + +### Preview example + +![preview.png](data%2Fpreview.png) \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_aurora/analysis_options.yaml b/packages/shared_preferences/shared_preferences_aurora/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/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/packages/shared_preferences/shared_preferences_aurora/aurora/CMakeLists.txt b/packages/shared_preferences/shared_preferences_aurora/aurora/CMakeLists.txt new file mode 100644 index 0000000..5871417 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/aurora/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) + +set(PROJECT_NAME shared_preferences_aurora) +set(PLUGIN_NAME shared_preferences_aurora_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") + +find_package(PkgConfig REQUIRED) +pkg_check_modules(FlutterEmbedder REQUIRED IMPORTED_TARGET flutter-embedder) +pkg_check_modules(Qt5Core REQUIRED IMPORTED_TARGET Qt5Core) + +add_library(${PLUGIN_NAME} SHARED shared_preferences_aurora_plugin.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 PkgConfig::Qt5Core) + +target_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_compile_definitions(${PLUGIN_NAME} PRIVATE PLUGIN_IMPL) diff --git a/packages/shared_preferences/shared_preferences_aurora/aurora/include/shared_preferences_aurora/shared_preferences_aurora_plugin.h b/packages/shared_preferences/shared_preferences_aurora/aurora/include/shared_preferences_aurora/shared_preferences_aurora_plugin.h new file mode 100644 index 0000000..3f4e6d8 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/aurora/include/shared_preferences_aurora/shared_preferences_aurora_plugin.h @@ -0,0 +1,66 @@ +#ifndef FLUTTER_PLUGIN_SHARED_PREFERENCES_AURORA_PLUGIN_H +#define FLUTTER_PLUGIN_SHARED_PREFERENCES_AURORA_PLUGIN_H + +#include +#include +#include +#include + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +class PLUGIN_EXPORT SharedPreferencesAuroraPlugin final : public PluginInterface +{ +public: + SharedPreferencesAuroraPlugin(); + void RegisterWithRegistrar(PluginRegistrar ®istrar) override; + + struct ARGS + { + QString prefix; + QString key; + Encodable value; + }; + + enum Types + { + Int, + Bool, + Double, + String, + List + }; + + enum Methods + { + setInt, + setBool, + setDouble, + setString, + setStringList, + clearWithPrefix, + remove, + getAllWithPrefix, + }; + +private: + void onMethodCall(const MethodCall &call); + void unimplemented(const MethodCall &call); + ARGS getArguments(const MethodCall &call); + void onSetInt(const MethodCall &call); + void onSetBool(const MethodCall &call); + void onSetDouble(const MethodCall &call); + void onSetString(const MethodCall &call); + void onSetStringList(const MethodCall &call); + void onClearWithPrefix(const MethodCall &call); + void onRemove(const MethodCall &call); + void onGetAllWithPrefix(const MethodCall &call); + + QSettings settings; + std::map mapping; +}; + +#endif /* FLUTTER_PLUGIN_SHARED_PREFERENCES_AURORA_PLUGIN_H */ diff --git a/packages/shared_preferences/shared_preferences_aurora/aurora/shared_preferences_aurora_plugin.cpp b/packages/shared_preferences/shared_preferences_aurora/aurora/shared_preferences_aurora_plugin.cpp new file mode 100644 index 0000000..348cf72 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/aurora/shared_preferences_aurora_plugin.cpp @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include +#include + +namespace { + +QString defaultSettingsFile() +{ + const auto [orgname, appname] = Application::GetID(); + + return QStringLiteral("%1/.local/share/%2/%3/.flutter_shared_preferences.conf") + .arg(QDir::homePath()) + .arg(QString::fromStdString(orgname)) + .arg(QString::fromStdString(appname)); +} + +} /* namespace */ + +SharedPreferencesAuroraPlugin::SharedPreferencesAuroraPlugin(): settings( + defaultSettingsFile(), + QSettings::NativeFormat +) { + // map methods + this->mapping = std::map ({ + {"setInt", Methods::setInt}, + {"setBool", Methods::setBool}, + {"setDouble", Methods::setDouble}, + {"setString", Methods::setString}, + {"setStringList", Methods::setStringList}, + {"clearWithPrefix", Methods::clearWithPrefix}, + {"remove", Methods::remove}, + {"getAllWithPrefix", Methods::getAllWithPrefix}, + }); +} + +void SharedPreferencesAuroraPlugin::RegisterWithRegistrar(PluginRegistrar ®istrar) +{ + registrar.RegisterMethodChannel("shared_preferences_aurora", + MethodCodecType::Standard, + [this](const MethodCall &call) { this->onMethodCall(call); }); +} + +SharedPreferencesAuroraPlugin::ARGS SharedPreferencesAuroraPlugin::getArguments(const MethodCall &call) +{ + QString key = QString::fromStdString(call.GetArguments()["key"].GetString()); + Encodable value = call.GetArguments()["value"]; + ARGS args; + + args.prefix = key.split('.').first(); + args.key = key.mid(args.prefix.length() + 1, key.length()); + args.value = value; + + return args; +} + +void SharedPreferencesAuroraPlugin::onMethodCall(const MethodCall &call) +{ + const auto &method = call.GetMethod(); + + switch (this->mapping[method]) + { + case Methods::setInt: + onSetInt(call); + return; + case Methods::setBool: + onSetBool(call); + return; + case Methods::setDouble: + onSetDouble(call); + return; + case Methods::setString: + onSetString(call); + return; + case Methods::setStringList: + onSetStringList(call); + return; + case Methods::clearWithPrefix: + onClearWithPrefix(call); + return; + case Methods::remove: + onRemove(call); + return; + case Methods::getAllWithPrefix: + onGetAllWithPrefix(call); + return; + } + + unimplemented(call); +} + +void SharedPreferencesAuroraPlugin::onSetInt(const MethodCall &call) +{ + const auto [prefix, key, value] = this->getArguments(call); + + settings.beginGroup(prefix); + settings.setValue( + QStringLiteral("%1:").arg(Types::Int) + key, + value.GetInt() + ); + settings.sync(); + settings.endGroup(); + + call.SendSuccessResponse(true); +} + +void SharedPreferencesAuroraPlugin::onSetBool(const MethodCall &call) +{ + const auto [prefix, key, value] = this->getArguments(call); + + settings.beginGroup(prefix); + settings.setValue( + QStringLiteral("%1:").arg(Types::Bool) + key, + value.GetBoolean() + ); + settings.sync(); + settings.endGroup(); + + call.SendSuccessResponse(true); +} + +void SharedPreferencesAuroraPlugin::onSetDouble(const MethodCall &call) +{ + const auto [prefix, key, value] = this->getArguments(call); + + settings.beginGroup(prefix); + settings.setValue( + QStringLiteral("%1:").arg(Types::Double) + key, + value.GetFloat() + ); + settings.sync(); + settings.endGroup(); + + call.SendSuccessResponse(true); +} + +void SharedPreferencesAuroraPlugin::onSetString(const MethodCall &call) +{ + const auto [prefix, key, value] = this->getArguments(call); + + settings.beginGroup(prefix); + settings.setValue( + QStringLiteral("%1:").arg(Types::String) + key, + QString::fromStdString(value.GetString()) + ); + settings.sync(); + settings.endGroup(); + + call.SendSuccessResponse(true); +} + +void SharedPreferencesAuroraPlugin::onSetStringList(const MethodCall &call) +{ + const auto [prefix, key, value] = this->getArguments(call); + const auto vec = value.GetList(); + + QStringList strings; + + for (const auto& item : vec) + { + strings.append(QString::fromStdString(item.GetString())); + } + + settings.beginGroup(prefix); + settings.setValue( + QStringLiteral("%1:").arg(Types::List) + key, + strings + ); + settings.sync(); + settings.endGroup(); + call.SendSuccessResponse(true); +} + +void SharedPreferencesAuroraPlugin::onClearWithPrefix(const MethodCall &call) +{ + const auto prefix = QString::fromStdString(call.GetArguments()["prefix"].GetString()) + .replace(".", ""); + + settings.beginGroup(prefix); + settings.remove(""); + settings.sync(); + settings.endGroup(); + + call.SendSuccessResponse(true); +} + +void SharedPreferencesAuroraPlugin::onRemove(const MethodCall &call) +{ + QString raw = QString::fromStdString(call.GetArguments()["key"].GetString()); + + const auto prefix = raw.split('.').first(); + const auto key = raw.mid(prefix.length() + 1, raw.length()); + + settings.beginGroup(prefix); + settings.remove(QStringLiteral("%1:").arg(Types::Int) + key); + settings.remove(QStringLiteral("%1:").arg(Types::Bool) + key); + settings.remove(QStringLiteral("%1:").arg(Types::Double) + key); + settings.remove(QStringLiteral("%1:").arg(Types::String) + key); + settings.remove(QStringLiteral("%1:").arg(Types::List) + key); + settings.sync(); + settings.endGroup(); + + call.SendSuccessResponse(true); +} + +void SharedPreferencesAuroraPlugin::onGetAllWithPrefix(const MethodCall &call) +{ + const auto prefix = QString::fromStdString(call.GetArguments()["prefix"].GetString()) + .replace(".", ""); + + std::map map; + + settings.beginGroup(prefix); + + for (const auto& item : settings.allKeys()) + { + const auto type = item.split(":").first(); + const auto key = prefix + "." + item.mid(type.length() + 1, item.length()); + const auto variant = settings.value(item); + + switch (type.toInt()) + { + case Types::Int: + map[key.toStdString()] = Encodable(variant.toInt()); + break; + case Types::Bool: + map[key.toStdString()] = Encodable(variant.toBool()); + break; + case Types::Double: + map[key.toStdString()] = Encodable(variant.toDouble()); + break; + case Types::String: + map[key.toStdString()] = Encodable(variant.toString().toStdString()); + break; + case Types::List: + std::vector vec; + QStringList list = variant.toStringList(); + for (const auto& item : list) + { + vec.push_back(item.toStdString()); + } + map[key.toStdString()] = vec; + } + } + + settings.endGroup(); + + call.SendSuccessResponse(map); +} + +void SharedPreferencesAuroraPlugin::unimplemented(const MethodCall &call) +{ + call.SendSuccessResponse(nullptr); +} \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_aurora/data/preview.png b/packages/shared_preferences/shared_preferences_aurora/data/preview.png new file mode 100644 index 0000000..c6f0708 Binary files /dev/null and b/packages/shared_preferences/shared_preferences_aurora/data/preview.png differ diff --git a/packages/shared_preferences/shared_preferences_aurora/example/.gitignore b/packages/shared_preferences/shared_preferences_aurora/example/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/.gitignore @@ -0,0 +1,44 @@ +# 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 +.packages +.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 diff --git a/packages/shared_preferences/shared_preferences_aurora/example/README.md b/packages/shared_preferences/shared_preferences_aurora/example/README.md new file mode 100644 index 0000000..405c877 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/README.md @@ -0,0 +1,16 @@ +# shared_preferences_aurora_example + +Demonstrates how to use the shared_preferences_aurora 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/packages/shared_preferences/shared_preferences_aurora/example/analysis_options.yaml b/packages/shared_preferences/shared_preferences_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/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/packages/shared_preferences/shared_preferences_aurora/example/aurora/.gitignore b/packages/shared_preferences/shared_preferences_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/CMakeLists.txt b/packages/shared_preferences/shared_preferences_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..57b3d4e --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.shared_preferences_aurora_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/packages/shared_preferences/shared_preferences_aurora/example/aurora/desktop/com.example.shared_preferences_aurora_example.desktop b/packages/shared_preferences/shared_preferences_aurora/example/aurora/desktop/com.example.shared_preferences_aurora_example.desktop new file mode 100644 index 0000000..ebb83d1 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/desktop/com.example.shared_preferences_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=shared_preferences_aurora_example +Comment=Demonstrates how to use the shared_preferences_aurora plugin. +Icon=com.example.shared_preferences_aurora_example +Exec=/usr/bin/com.example.shared_preferences_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=shared_preferences_aurora_example diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..32f7152 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + std::make_shared(), + }); +} diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..3e5cadb --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,31 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST + shared_preferences_aurora +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/108x108.png b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/128x128.png b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/172x172.png b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/86x86.png b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/shared_preferences/shared_preferences_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/main.cpp b/packages/shared_preferences/shared_preferences_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/shared_preferences/shared_preferences_aurora/example/aurora/rpm/com.example.shared_preferences_aurora_example.spec b/packages/shared_preferences/shared_preferences_aurora/example/aurora/rpm/com.example.shared_preferences_aurora_example.spec new file mode 100644 index 0000000..a05f586 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/aurora/rpm/com.example.shared_preferences_aurora_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.shared_preferences_aurora_example +Summary: Demonstrates how to use the shared_preferences_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/shared_preferences/shared_preferences_aurora/example/lib/main.dart b/packages/shared_preferences/shared_preferences_aurora/example/lib/main.dart new file mode 100644 index 0000000..9a92a43 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/lib/main.dart @@ -0,0 +1,197 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + int? _counter; + bool? _repeat; + double? _decimal; + String? _action; + List? _items; + String? _error; + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + Future initPlatformState() async { + try { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + // Save an integer value to 'counter' key. + await prefs.setInt('counter', 10); + // Save an boolean value to 'repeat' key. + await prefs.setBool('repeat', true); + // Save an double value to 'decimal' key. + await prefs.setDouble('decimal', 1.5); + // Save an String value to 'action' key. + await prefs.setString('action', 'Start'); + // Save an list of strings to 'items' key. + await prefs.setStringList('items', ['Earth', 'Moon', 'Sun']); + + // Try reading data from the 'counter' key. If it doesn't exist, returns null. + final int? counter = prefs.getInt('counter'); + // Try reading data from the 'repeat' key. If it doesn't exist, returns null. + final bool? repeat = prefs.getBool('repeat'); + // Try reading data from the 'decimal' key. If it doesn't exist, returns null. + final double? decimal = prefs.getDouble('decimal'); + // Try reading data from the 'action' key. If it doesn't exist, returns null. + final String? action = prefs.getString('action'); + // Try reading data from the 'items' key. If it doesn't exist, returns null. + final List? items = prefs.getStringList('items'); + + setState(() { + _counter = counter; + _repeat = repeat; + _decimal = decimal; + _action = action; + _items = items; + }); + } on PlatformException { + setState(() { + _error = 'Platform exception'; + }); + } + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + const textStyleTitle = TextStyle(fontSize: 20, color: Colors.black); + const textStylePath = TextStyle(fontSize: 18, color: Colors.black54); + + const spaceMedium = SizedBox(height: 20); + const spaceSmall = SizedBox(height: 10); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example shared_preferences'), + ), + body: Stack( + children: [ + // Error message + Visibility( + visible: _error != null, + child: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.redAccent, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: Text( + _error ?? '', + style: textStyleWhite, + ), + ), + ), + ), + ), + // List directories path + Visibility( + visible: _error == null, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + // Info + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: + BorderRadius.all(Radius.circular(10.0)), + ), + child: const Text( + 'Demo application demonstration implementation of shared_preferences', + style: textStyleWhite, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + + const Text( + 'Counter / int', + style: textStyleTitle, + ), + spaceSmall, + Text( + _counter.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Repeat / bool', + style: textStyleTitle, + ), + spaceSmall, + Text( + _repeat.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Decimal / double', + style: textStyleTitle, + ), + spaceSmall, + Text( + _decimal.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Action / String', + style: textStyleTitle, + ), + spaceSmall, + Text( + _action.toString(), + style: textStylePath, + ), + + spaceMedium, + const Text( + 'Items / String List', + style: textStyleTitle, + ), + spaceSmall, + Text( + _items.toString(), + style: textStylePath, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/shared_preferences/shared_preferences_aurora/example/pubspec.lock b/packages/shared_preferences/shared_preferences_aurora/example/pubspec.lock new file mode 100644 index 0000000..5dc00eb --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/pubspec.lock @@ -0,0 +1,299 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.6" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + shared_preferences_aurora: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.12" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.4" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" +sdks: + dart: ">=2.18.6 <3.0.0" + flutter: ">=3.0.0" diff --git a/packages/shared_preferences/shared_preferences_aurora/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_aurora/example/pubspec.yaml new file mode 100644 index 0000000..b3e56b3 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/example/pubspec.yaml @@ -0,0 +1,84 @@ +name: shared_preferences_aurora_example +description: Demonstrates how to use the shared_preferences_aurora 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: '>=2.18.6 <3.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 + shared_preferences: ^2.1.1 + shared_preferences_aurora: + # When depending on this package from a real application you should use: + # shared_preferences_aurora: ^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: + 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/packages/shared_preferences/shared_preferences_aurora/example/test/widget_test.dart b/packages/shared_preferences/shared_preferences_aurora/example/test/widget_test.dart new file mode 100644 index 0000000..2fc5cb7 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/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:shared_preferences_aurora_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/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora.dart b/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora.dart new file mode 100644 index 0000000..6d1de90 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora.dart @@ -0,0 +1,64 @@ +import 'shared_preferences_aurora_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; + +class SharedPreferencesAurora extends SharedPreferencesStorePlatform { + SharedPreferencesAurora({ + @visibleForTesting SharedPreferencesAuroraPlatform? api, + }) : _api = api ?? SharedPreferencesAuroraPlatform.instance; + + late final SharedPreferencesAuroraPlatform _api; + + /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. + static void registerWith() { + SharedPreferencesStorePlatform.instance = SharedPreferencesAurora(); + } + + static const String _defaultPrefix = 'flutter.'; + + @override + Future remove(String key) async { + return _api.remove(key); + } + + @override + Future setValue(String valueType, String key, Object value) async { + switch (valueType) { + case 'String': + return _api.setString(key, value as String); + case 'Bool': + return _api.setBool(key, value as bool); + case 'Int': + return _api.setInt(key, value as int); + case 'Double': + return _api.setDouble(key, value as double); + case 'StringList': + return _api.setStringList(key, value as List); + } + throw PlatformException( + code: 'InvalidOperation', + message: '"$valueType" is not a supported type.'); + } + + @override + Future clear() { + return clearWithPrefix(_defaultPrefix); + } + + @override + Future clearWithPrefix(String prefix) async { + return _api.clearWithPrefix(prefix); + } + + @override + Future> getAll() { + return getAllWithPrefix(_defaultPrefix); + } + + @override + Future> getAllWithPrefix(String prefix) async { + final Map data = await _api.getAllWithPrefix(prefix); + return data.cast(); + } +} diff --git a/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora_method_channel.dart b/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora_method_channel.dart new file mode 100644 index 0000000..4533262 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora_method_channel.dart @@ -0,0 +1,82 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'shared_preferences_aurora_platform_interface.dart'; + +/// An implementation of [SharedPreferencesAuroraPlatform] that uses method channels. +class MethodChannelSharedPreferencesAurora + extends SharedPreferencesAuroraPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('shared_preferences_aurora'); + + @override + Future setInt(String key, int value) async { + return await methodChannel.invokeMethod('setInt', { + 'key': key, + 'value': value, + }) ?? + false; + } + + @override + Future setBool(String key, bool value) async { + return await methodChannel.invokeMethod('setBool', { + 'key': key, + 'value': value, + }) ?? + false; + } + + @override + Future setDouble(String key, double value) async { + return await methodChannel.invokeMethod('setDouble', { + 'key': key, + 'value': value, + }) ?? + false; + } + + @override + Future setString(String key, String value) async { + return await methodChannel.invokeMethod('setString', { + 'key': key, + 'value': value, + }) ?? + false; + } + + @override + Future setStringList(String key, List value) async { + return await methodChannel.invokeMethod('setStringList', { + 'key': key, + 'value': value, + }) ?? + false; + } + + @override + Future clearWithPrefix(String prefix) async { + return await methodChannel.invokeMethod('clearWithPrefix', { + 'prefix': prefix, + }) ?? + false; + } + + @override + Future remove(String key) async { + return await methodChannel.invokeMethod('remove', { + 'key': key, + }) ?? + false; + } + + @override + Future> getAllWithPrefix(String prefix) async { + return await methodChannel + .invokeMethod?>('getAllWithPrefix', { + 'prefix': prefix, + }) ?? + {}; + } +} diff --git a/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora_platform_interface.dart b/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora_platform_interface.dart new file mode 100644 index 0000000..180ffd0 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/lib/shared_preferences_aurora_platform_interface.dart @@ -0,0 +1,58 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'shared_preferences_aurora_method_channel.dart'; + +abstract class SharedPreferencesAuroraPlatform extends PlatformInterface { + /// Constructs a SharedPreferencesAuroraPlatform. + SharedPreferencesAuroraPlatform() : super(token: _token); + + static final Object _token = Object(); + + static SharedPreferencesAuroraPlatform _instance = + MethodChannelSharedPreferencesAurora(); + + /// The default instance of [SharedPreferencesAuroraPlatform] to use. + /// + /// Defaults to [MethodChannelSharedPreferencesAurora]. + static SharedPreferencesAuroraPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [SharedPreferencesAuroraPlatform] when + /// they register themselves. + static set instance(SharedPreferencesAuroraPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future setInt(String key, int value) { + throw UnimplementedError('setInt() has not been implemented.'); + } + + Future setBool(String key, bool value) { + throw UnimplementedError('setBool() has not been implemented.'); + } + + Future setDouble(String key, double value) { + throw UnimplementedError('setDouble() has not been implemented.'); + } + + Future setString(String key, String value) { + throw UnimplementedError('setString() has not been implemented.'); + } + + Future setStringList(String key, List value) { + throw UnimplementedError('setStringList() has not been implemented.'); + } + + Future clearWithPrefix(String prefix) { + throw UnimplementedError('onClearWithPrefix() has not been implemented.'); + } + + Future remove(String key) { + throw UnimplementedError('remove() has not been implemented.'); + } + + Future> getAllWithPrefix(String prefix) { + throw UnimplementedError('getAllWithPrefix() has not been implemented.'); + } +} diff --git a/packages/shared_preferences/shared_preferences_aurora/pubspec.yaml b/packages/shared_preferences/shared_preferences_aurora/pubspec.yaml new file mode 100644 index 0000000..3fea4a7 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/pubspec.yaml @@ -0,0 +1,26 @@ +name: shared_preferences_aurora +description: The Aurora OS implementation of shared_preferences. +version: 0.0.1 +homepage: + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + shared_preferences_platform_interface: ^2.2.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + aurora: + pluginClass: SharedPreferencesAuroraPlugin + dartPluginClass: SharedPreferencesAurora \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_aurora/test/shared_preferences_aurora_method_channel_test.dart b/packages/shared_preferences/shared_preferences_aurora/test/shared_preferences_aurora_method_channel_test.dart new file mode 100644 index 0000000..1649c56 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_aurora/test/shared_preferences_aurora_method_channel_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences_aurora/shared_preferences_aurora_method_channel.dart'; + +void main() { + MethodChannelSharedPreferencesAurora platform = + MethodChannelSharedPreferencesAurora(); + const MethodChannel channel = MethodChannel('shared_preferences_aurora'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + switch (methodCall.method) { + case 'setInt': + return true; + case 'setBool': + return true; + case 'setDouble': + return true; + case 'setString': + return true; + case 'setStringList': + return true; + case 'clearWithPrefix': + return true; + case 'remove': + return true; + case 'getAllWithPrefix': + return {}; + } + return null; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('onSetInt', () async { + expect(await platform.setInt('key', 42), true); + }); + + test('onSetBool', () async { + expect(await platform.setBool('key', true), true); + }); + + test('onSetDouble', () async { + expect(await platform.setDouble('key', 0.0), true); + }); + + test('onSetString', () async { + expect(await platform.setString('key', "Start"), true); + }); + + test('onSetStringList', () async { + expect(await platform.setStringList('key', []), true); + }); + + test('onClearWithPrefix', () async { + expect(await platform.clearWithPrefix('prefix'), true); + }); + + test('onRemove', () async { + expect(await platform.remove('key'), true); + }); + + test('onGetAllWithPrefix', () async { + expect(await platform.getAllWithPrefix('prefix'), {}); + }); +} diff --git a/packages/sqflite/sqflite_aurora/.gitignore b/packages/sqflite/sqflite_aurora/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/packages/sqflite/sqflite_aurora/.gitignore @@ -0,0 +1,30 @@ +# 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/ +.packages +build/ diff --git a/packages/sqflite/sqflite_aurora/README.md b/packages/sqflite/sqflite_aurora/README.md new file mode 100644 index 0000000..c962e29 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/README.md @@ -0,0 +1,26 @@ +# sqflite_aurora + +The Aurora OS implementation of [`sqflite`](https://pub.dev/packages/sqflite). + +## Usage + +This package is not an endorsed implementation of `sqflite`. +Therefore, you have to include `sqflite_aurora` alongside `sqflite` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + sqflite: ^2.2.6 + sqflite_aurora: + git: git@os-git.omprussia.ru:non-oss/flutter/flutter-plugins.git + path: packages/sqflite/sqflite_aurora +``` + +Then you can import and use `sqflite` in your Dart code: + +```dart +import 'package:sqflite/sqflite.dart'; +``` + +### Preview example + +![preview.png](data%2Fpreview.png) \ No newline at end of file diff --git a/packages/sqflite/sqflite_aurora/aurora/.clang-format b/packages/sqflite/sqflite_aurora/aurora/.clang-format new file mode 100644 index 0000000..dcf3f76 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/.clang-format @@ -0,0 +1,118 @@ +# .clang-format for Qt Creator +# +# This is for clang-format >= 5.0. +# +# The configuration below follows the Qt Creator Coding Rules [1] as closely as +# possible. For documentation of the options, see [2]. +# +# Use ../../tests/manual/clang-format-for-qtc/test.cpp for documenting problems +# or testing changes. +# +# In case you update this configuration please also update the qtcStyle() in src\plugins\clangformat\clangformatutils.cpp +# +# [1] https://doc-snapshots.qt.io/qtcreator-extending/coding-style.html +# [2] https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - forever # avoids { wrapped to next line + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeCategories: + - Regex: '^ +#include +#include +#include +#include + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +class PLUGIN_EXPORT AsyncQueue final +{ +public: + typedef std::function Task; + +public: + AsyncQueue(); + ~AsyncQueue(); + + void Push(const Task &task); + +private: + void run(); + +private: + bool m_running; + std::thread m_thread; + std::queue m_tasks; + std::mutex m_mutex; + std::condition_variable m_condition; +}; + +#endif /* FLUTTER_PLUGIN_SQFLITE_ASYNC_QUEUE_H */ diff --git a/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/constants.h b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/constants.h new file mode 100644 index 0000000..1dcb1a5 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/constants.h @@ -0,0 +1,57 @@ +#ifndef FLUTTER_PLUGIN_SQFLITE_CONSTANTS_H +#define FLUTTER_PLUGIN_SQFLITE_CONSTANTS_H + +#include + +const std::string METHOD_GET_PLATFORM_VERSION = "getPlatformVersion"; +const std::string METHOD_GET_DATABASES_PATH = "getDatabasesPath"; +const std::string METHOD_DEBUG = "debug"; +const std::string METHOD_OPTIONS = "options"; +const std::string METHOD_OPEN_DATABASE = "openDatabase"; +const std::string METHOD_CLOSE_DATABASE = "closeDatabase"; +const std::string METHOD_INSERT = "insert"; +const std::string METHOD_EXECUTE = "execute"; +const std::string METHOD_QUERY = "query"; +const std::string METHOD_QUERY_CURSOR_NEXT = "queryCursorNext"; +const std::string METHOD_UPDATE = "update"; +const std::string METHOD_BATCH = "batch"; +const std::string METHOD_DELETE_DATABASE = "deleteDatabase"; +const std::string METHOD_DATABASE_EXISTS = "databaseExists"; + +const std::string ARG_ID = "id"; +const std::string ARG_PATH = "path"; +const std::string ARG_READ_ONLY = "readOnly"; +const std::string ARG_SINGLE_INSTANCE = "singleInstance"; +const std::string ARG_LOG_LEVEL = "logLevel"; +const std::string ARG_TRANSACTION_ID = "transactionId"; +const std::string ARG_IN_TRANSACTION = "inTransaction"; +const std::string ARG_RECOVERED = "recovered"; +const std::string ARG_RECOVERED_IN_TRANSACTION = "recoveredInTransaction"; +const std::string ARG_SQL = "sql"; +const std::string ARG_SQL_ARGUMENTS = "arguments"; +const std::string ARG_NO_RESULT = "noResult"; +const std::string ARG_CONTINUE_ON_ERROR = "continueOnError"; +const std::string ARG_COLUMNS = "columns"; +const std::string ARG_ROWS = "rows"; +const std::string ARG_DATABASES = "databases"; +const std::string ARG_COMMAND = "cmd"; +const std::string ARG_OPERATIONS = "operations"; +const std::string ARG_METHOD = "method"; +const std::string ARG_RESULT = "result"; +const std::string ARG_ERROR = "error"; +const std::string ARG_ERROR_CODE = "code"; +const std::string ARG_ERROR_MESSAGE = "message"; +const std::string ARG_ERROR_DATA = "data"; +const std::string ARG_CURSOR_PAGE_SIZE = "cursorPageSize"; +const std::string ARG_CURSOR_ID = "cursorId"; +const std::string ARG_CANCEL = "cancel"; + +const std::string ERROR_SQFLITE = "sqlite_error"; +const std::string ERROR_OPEN = "open_failed"; +const std::string ERROR_CLOSE = "close_failed"; +const std::string ERROR_CLOSED = "database_closed"; +const std::string ERROR_BAD_PARAM = "bad_param"; +const std::string ERROR_BAD_ARGS = "bad_arguments"; +const std::string ERROR_INTERNAL = "internal"; + +#endif /* FLUTTER_PLUGIN_SQFLITE_CONSTANTS_H */ diff --git a/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/database.h b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/database.h new file mode 100644 index 0000000..559fecc --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/database.h @@ -0,0 +1,111 @@ +#ifndef FLUTTER_PLUGIN_SQFLITE_DATABASE_H +#define FLUTTER_PLUGIN_SQFLITE_DATABASE_H + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +class PLUGIN_EXPORT Database final +{ +public: + enum TransactionID { + None = -2, + Force = -1, + }; + + struct Cursor + { + int64_t id; + sqlite3_stmt *stmt; + int64_t pageSize; + }; + + struct Operation + { + std::string method; + std::string sql; + Encodable::List arguments; + }; + +public: + Database(int id, const std::string &path, bool singleton, const Logger &logger); + ~Database(); + +public: + std::optional Open(); + std::optional OpenReadOnly(); + std::optional Close(); + std::optional Execute(const std::string &sql, const Encodable::List &args); + std::optional QueryCursorNext(int cursorID, bool cancel, Encodable::Map &result); + std::optional Query(const std::string &sql, + const Encodable::List &args, + Encodable::Map &result); + std::optional QueryWithPageSize(const std::string &sql, + const Encodable::List &args, + int64_t pageSize, + Encodable::Map &result); + std::optional Insert(const std::string &sql, + const Encodable::List &args, + int &insertID); + std::optional Update(const std::string &sql, + const Encodable::List &args, + int &updated); + std::optional Batch(const std::vector &operations, + bool continueOnError, + Encodable::List &results); + + void EnterInTransaction(); + void LeaveTransaction(); + int CurrentTransactionID(); + + typedef std::function SqlCommandCallback; + void ProcessSqlCommand(int transactionID, const SqlCommandCallback &callback); + +public: + bool IsOpen() const; + bool IsSingleInstance() const; + bool IsInTransaction() const; + bool IsInMemory() const; + bool IsReadOnly() const; + + const std::string &Path() const; + int64_t ID() const; + Logger::Level LogLevel() const; + +private: + std::string currentErrorMessage(); + std::optional createParentDir(); + std::optional bindStmtArgs(sqlite3_stmt *stmt, const Encodable::List &args); + + void closeCursor(const Cursor &cursor); + std::optional resultFromCursor(const Cursor &cursor, Encodable::Map &result); + +private: + int64_t id; + std::string path; + bool isSingleInstance; + bool isReadOnly; + Logger logger; + int transactionID; + int currentTransactionID; + std::queue pendingSqlCallbacks; + int64_t cursorID; + std::unordered_map cursors; + sqlite3 *db; +}; + +#endif /* FLUTTER_PLUGIN_SQFLITE_DATABASE_H */ diff --git a/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/logger.h b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/logger.h new file mode 100644 index 0000000..0b2afb8 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/logger.h @@ -0,0 +1,40 @@ +#ifndef FLUTTER_PLUGIN_SQFLITE_LOGGER_H +#define FLUTTER_PLUGIN_SQFLITE_LOGGER_H + +#include +#include + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +class PLUGIN_EXPORT Logger final +{ +public: + enum Level { + None = 0, + Sql, + Verbose, + }; + +public: + Logger(Level level, const std::string &tag); + + Level LogLevel() const; + void SetLogLevel(Level level); + + std::string Tag() const; + void SetTag(const std::string &tag); + + std::ostream &verb(); + std::ostream &sql(); + +private: + Level level; + std::string tag; + std::ostream devnull; +}; + +#endif /* FLUTTER_PLUGIN_SQFLITE_LOGGER_H */ diff --git a/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/sqflite_aurora_plugin.h b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/sqflite_aurora_plugin.h new file mode 100644 index 0000000..3d39b49 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/include/sqflite_aurora/sqflite_aurora_plugin.h @@ -0,0 +1,67 @@ +#ifndef FLUTTER_PLUGIN_SQFLITE_H +#define FLUTTER_PLUGIN_SQFLITE_H + +#include + +#include +#include + +#include +#include +#include + +#ifdef PLUGIN_IMPL +#define PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PLUGIN_EXPORT +#endif + +class PLUGIN_EXPORT SqfliteAuroraPlugin final : public PluginInterface +{ +public: + SqfliteAuroraPlugin(); + void RegisterWithRegistrar(PluginRegistrar ®istrar) override; + +private: + void onMethodCall(const MethodCall &call); + void onPlatformVersionCall(const MethodCall &call); + void onOpenDatabaseCall(const MethodCall &call); + void onCloseDatabaseCall(const MethodCall &call); + void onDeleteDatabaseCall(const MethodCall &call); + void onDatabaseExistsCall(const MethodCall &call); + void onGetDatabasesPathCall(const MethodCall &call); + void onOptionsCall(const MethodCall &call); + void onDebugCall(const MethodCall &call); + void onExecuteCall(const MethodCall &call); + void onQueryCall(const MethodCall &call); + void onQueryCursorNextCall(const MethodCall &call); + void onUpdateCall(const MethodCall &call); + void onInsertCall(const MethodCall &call); + void onBatchCall(const MethodCall &call); + + std::shared_ptr databaseByPath(const std::string &path); + std::shared_ptr databaseByID(int64_t id); + + void databaseRemove(std::shared_ptr db); + void databaseAdd(std::shared_ptr db); + + void success(const MethodCall &call, const Encodable &result = nullptr); + void error(const MethodCall &call, + const std::string &error, + const std::string &message, + const std::string &desc = "", + const Encodable &details = nullptr); + + Encodable::Map makeOpenResult(int64_t dbID, bool recovered, bool recoveredInTransaction); + +private: + std::mutex mutex; + std::unordered_map> singletonDatabases; + std::unordered_map> databases; + int64_t dbID = 0; + Logger logger; + bool queryAsMapList = false; + AsyncQueue asynq; +}; + +#endif /* FLUTTER_PLUGIN_SQFLITE_H */ diff --git a/packages/sqflite/sqflite_aurora/aurora/lib/asynq.cpp b/packages/sqflite/sqflite_aurora/aurora/lib/asynq.cpp new file mode 100644 index 0000000..9f0f4c6 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/lib/asynq.cpp @@ -0,0 +1,41 @@ +#include + +AsyncQueue::AsyncQueue() + : m_running(false) + , m_thread() +{} + +AsyncQueue::~AsyncQueue() +{ + m_running = false; + m_thread.join(); +} + +void AsyncQueue::Push(const Task &task) +{ + if (!m_running) { + m_running = true; + m_tasks.push(task); + m_thread = std::thread(&AsyncQueue::run, this); + return; + } + + std::lock_guard lock(m_mutex); + + m_tasks.push(task); + m_condition.notify_one(); +} + +void AsyncQueue::run() +{ + while (m_running) { + std::unique_lock lock(m_mutex); + m_condition.wait(lock, [this] { return !m_tasks.empty(); }); + + const auto task = m_tasks.front(); + m_tasks.pop(); + lock.unlock(); + + task(); + } +} diff --git a/packages/sqflite/sqflite_aurora/aurora/lib/database.cpp b/packages/sqflite/sqflite_aurora/aurora/lib/database.cpp new file mode 100644 index 0000000..be35507 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/lib/database.cpp @@ -0,0 +1,631 @@ +#include +#include + +#include +#include + +namespace { + +void addError(Encodable::List &results, const std::string &error) +{ + results.emplace_back(Encodable::Map{ + {"error", Encodable::Map{{"message", error}}}, + }); +} + +template +void addResult(Encodable::List &results, const T &result) +{ + results.emplace_back(Encodable::Map{{"result", result}}); +} + +} /* namespace */ + +Database::Database(int id, const std::string &path, bool singleton, const Logger &logger) + : id(id) + , path(path) + , isSingleInstance(singleton) + , logger(logger.LogLevel(), logger.Tag() + "-db-" + std::to_string(id)) + , transactionID(0) + , currentTransactionID(TransactionID::None) + , cursorID(0) + , db(nullptr) +{} + +Database::~Database() +{ + Close(); +} + +std::optional Database::Open() +{ + const auto error = this->createParentDir(); + if (error) + return error; + + int result_code = sqlite3_open_v2(this->path.c_str(), + &this->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nullptr); + if (result_code != SQLITE_OK) + return this->currentErrorMessage(); + + this->isReadOnly = false; + return std::nullopt; +} + +std::optional Database::OpenReadOnly() +{ + const auto error = this->createParentDir(); + if (error) + return error; + + int result_code = sqlite3_open_v2(this->path.c_str(), &this->db, SQLITE_OPEN_READONLY, nullptr); + if (result_code != SQLITE_OK) + return this->currentErrorMessage(); + + this->isReadOnly = true; + return std::nullopt; +} + +std::optional Database::Close() +{ + if (sqlite3_close_v2(this->db) != SQLITE_OK) + return this->currentErrorMessage(); + + this->db = nullptr; + this->pendingSqlCallbacks = {}; + + return std::nullopt; +} + +std::optional Database::Execute(const std::string &sql, const Encodable::List &args) +{ + sqlite3_stmt *stmt = nullptr; + + if (sqlite3_prepare_v2(this->db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) + return this->currentErrorMessage(); + + const auto error = this->bindStmtArgs(stmt, args); + + if (error) { + sqlite3_finalize(stmt); + return error; + } + + while (true) { + int status = sqlite3_step(stmt); + + if (status == SQLITE_ROW) + continue; + + if (status == SQLITE_DONE) + break; + + sqlite3_finalize(stmt); + return this->currentErrorMessage(); + } + + sqlite3_finalize(stmt); + return std::nullopt; +} + +std::optional Database::Query(const std::string &sql, + const Encodable::List &args, + Encodable::Map &result) +{ + sqlite3_stmt *stmt = nullptr; + + if (sqlite3_prepare_v2(this->db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) + return this->currentErrorMessage(); + + const auto error = this->bindStmtArgs(stmt, args); + + if (error) { + sqlite3_finalize(stmt); + return error; + } + + Encodable::List columns; + + for (int idx = 0; idx < sqlite3_column_count(stmt); idx++) + columns.emplace_back(sqlite3_column_name(stmt, idx)); + + Encodable::List rows; + + while (true) { + int status = sqlite3_step(stmt); + + if (status == SQLITE_ROW) { + Encodable::List row; + + for (size_t idx = 0; idx < columns.size(); idx++) { + switch (sqlite3_column_type(stmt, idx)) { + case SQLITE_INTEGER: + row.emplace_back(sqlite3_column_int64(stmt, idx)); + break; + case SQLITE_FLOAT: + row.emplace_back(sqlite3_column_double(stmt, idx)); + break; + case SQLITE_TEXT: + row.emplace_back(reinterpret_cast(sqlite3_column_text(stmt, idx))); + break; + case SQLITE_BLOB: { + const uint8_t *blob = reinterpret_cast( + sqlite3_column_blob(stmt, idx)); + std::vector v(blob, blob + sqlite3_column_bytes(stmt, idx)); + row.emplace_back(v); + break; + } + case SQLITE_NULL: { + const char *columnDecltype = sqlite3_column_decltype(stmt, idx); + + if (columnDecltype != NULL && std::string("BLOB") == columnDecltype) + row.emplace_back(Encodable::Uint8List{}); + else + row.emplace_back(nullptr); + + break; + } + default: + break; + } + } + + rows.emplace_back(row); + continue; + } + + if (status == SQLITE_DONE) + break; + + sqlite3_finalize(stmt); + return this->currentErrorMessage(); + } + + result = Encodable::Map{{"columns", columns}, {"rows", rows}}; + + sqlite3_finalize(stmt); + return std::nullopt; +} + +std::optional Database::QueryWithPageSize(const std::string &sql, + const Encodable::List &args, + int64_t pageSize, + Encodable::Map &result) +{ + sqlite3_stmt *stmt = nullptr; + + if (sqlite3_prepare_v2(this->db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) + return this->currentErrorMessage(); + + const auto error = this->bindStmtArgs(stmt, args); + + if (error) + return error; + + this->cursorID += 1; + this->cursors[cursorID] = Cursor{this->cursorID, stmt, pageSize}; + + return resultFromCursor(this->cursors[cursorID], result); +} + +std::optional Database::QueryCursorNext(int cursorID, + bool cancel, + Encodable::Map &result) +{ + this->logger.verb() << "querying cursor next (ID=" << cursorID << "; CANCEL=" << cancel << ")" + << std::endl; + + if (!this->cursors.count(cursorID)) + return "cursor not found"; + + const Cursor &cursor = this->cursors[cursorID]; + + if (cancel) { + this->closeCursor(cursor); + return std::nullopt; + } + + return this->resultFromCursor(cursor, result); +} + +std::optional Database::Insert(const std::string &sql, + const Encodable::List &args, + int &insertID) +{ + if (this->isReadOnly) + return "database is readonly"; + + const auto error = this->Execute(sql, args); + + if (error) + return error; + + const int updated = sqlite3_changes(this->db); + + this->logger.sql() << "rows updated: " << updated << std::endl; + + if (updated == 0) { + insertID = 0; + return std::nullopt; + } + + insertID = sqlite3_last_insert_rowid(this->db); + this->logger.sql() << "last inserted row id: " << insertID << std::endl; + + return std::nullopt; +} + +std::optional Database::Update(const std::string &sql, + const Encodable::List &args, + int &updated) +{ + if (this->isReadOnly) + return "database is readonly"; + + const auto error = this->Execute(sql, args); + + if (error) + return error; + + updated = sqlite3_changes(this->db); + this->logger.sql() << "rows updated: " << updated << std::endl; + + return std::nullopt; +} + +void Database::ProcessSqlCommand(int transactionID, const SqlCommandCallback &callback) +{ + if (this->currentTransactionID == TransactionID::None) { + callback(); + return; + } + + if (transactionID == this->currentTransactionID || transactionID == TransactionID::Force) { + callback(); + + if (this->currentTransactionID == TransactionID::None) { + while (!this->pendingSqlCallbacks.empty() && this->IsOpen()) { + const auto &pendingCallback = this->pendingSqlCallbacks.front(); + pendingCallback(); + this->pendingSqlCallbacks.pop(); + } + } + + return; + } + + this->pendingSqlCallbacks.push(callback); +} + +void Database::EnterInTransaction() +{ + this->transactionID += 1; + this->currentTransactionID = this->transactionID; +} + +void Database::LeaveTransaction() +{ + this->currentTransactionID = TransactionID::None; +} + +int Database::CurrentTransactionID() +{ + return this->currentTransactionID; +} + +bool Database::IsOpen() const +{ + return this->db != nullptr; +} + +const std::string &Database::Path() const +{ + return this->path; +} + +bool Database::IsSingleInstance() const +{ + return this->isSingleInstance; +} + +bool Database::IsReadOnly() const +{ + return this->isReadOnly; +} + +bool Database::IsInTransaction() const +{ + return this->currentTransactionID != TransactionID::None; +} + +int64_t Database::ID() const +{ + return this->id; +} + +Logger::Level Database::LogLevel() const +{ + return this->logger.LogLevel(); +} + +bool Database::IsInMemory() const +{ + return this->path.empty() || this->path == ":memory:"; +} + +std::string Database::currentErrorMessage() +{ + const auto message = sqlite3_errmsg(this->db); + const auto code = sqlite3_extended_errcode(this->db); + + return std::string(message) + " (" + std::to_string(code) + ")"; +} + +std::optional Database::createParentDir() +{ + if (this->IsInMemory()) + return std::nullopt; + + const auto parentDir = std::filesystem::path(this->path).parent_path(); + + if (std::filesystem::exists(parentDir)) + return std::nullopt; + + if (std::filesystem::create_directories(parentDir)) + return std::nullopt; + + return "couldn't create parent directory"; +} + +std::optional Database::bindStmtArgs(sqlite3_stmt *stmt, const Encodable::List &args) +{ + int result = SQLITE_OK; + + for (size_t i = 0; i < args.size(); i++) { + auto idx = i + 1; + auto &arg = args[i]; + + if (arg.IsNull()) { + result = sqlite3_bind_null(stmt, idx); + } else if (arg.IsBoolean()) { + result = sqlite3_bind_int(stmt, idx, static_cast(arg.GetBoolean())); + } else if (arg.IsInt()) { + const int64_t value = arg.GetInt(); + + const int32_t i32min = std::numeric_limits::min(); + const int32_t i32max = std::numeric_limits::max(); + + if (value < i32min || value > i32max) + result = sqlite3_bind_int64(stmt, idx, arg.GetInt()); + else + result = sqlite3_bind_int64(stmt, idx, static_cast(arg.GetInt())); + } else if (arg.IsFloat()) { + result = sqlite3_bind_double(stmt, idx, arg.GetFloat()); + } else if (arg.IsString()) { + const auto &string = arg.GetString(); + result = sqlite3_bind_text(stmt, idx, string.c_str(), string.size(), SQLITE_TRANSIENT); + } else if (arg.IsUint8List()) { + const auto &container = arg.GetUint8List(); + result = sqlite3_bind_blob(stmt, + idx, + container.data(), + container.size(), + SQLITE_TRANSIENT); + } else if (arg.IsInt32List()) { + const auto &container = arg.GetInt32List(); + result = sqlite3_bind_blob(stmt, + idx, + container.data(), + container.size() * sizeof(int32_t), + SQLITE_TRANSIENT); + } else if (arg.IsInt64List()) { + const auto &container = arg.GetInt64List(); + result = sqlite3_bind_blob(stmt, + idx, + container.data(), + container.size() * sizeof(int64_t), + SQLITE_TRANSIENT); + } else if (arg.IsFloat32List()) { + const auto &container = arg.GetFloat32List(); + result = sqlite3_bind_blob(stmt, + idx, + container.data(), + container.size() * sizeof(float), + SQLITE_TRANSIENT); + } else if (arg.IsFloat64List()) { + const auto &container = arg.GetFloat64List(); + result = sqlite3_bind_blob(stmt, + idx, + container.data(), + container.size() * sizeof(double), + SQLITE_TRANSIENT); + } else if (arg.IsList()) { + /* only bytes list is supported */ + std::vector container; + + for (const auto &entry : arg.GetList()) { + if (!entry.IsInt()) + return "only list of bytes is supported for statement parameter"; + + const auto value = entry.GetInt(); + + if (value < 0 || value > 255) + return "only list of bytes is supported for statement parameter"; + + container.push_back(static_cast(value)); + } + + result = sqlite3_bind_blob(stmt, + idx, + container.data(), + container.size(), + SQLITE_TRANSIENT); + } else { + return "statement parameter has invalid type"; + } + + if (result != SQLITE_OK) + return this->currentErrorMessage(); + } + + this->logger.sql() << sqlite3_expanded_sql(stmt) << std::endl; + return std::nullopt; +} + +void Database::closeCursor(const Cursor &cursor) +{ + logger.verb() << "closing cursor (ID=" << cursor.id << ")" << std::endl; + sqlite3_finalize(cursor.stmt); + this->cursors.erase(cursor.id); +} + +std::optional Database::resultFromCursor(const Cursor &cursor, Encodable::Map &result) +{ + Encodable::List columns; + + for (int idx = 0; idx < sqlite3_column_count(cursor.stmt); idx++) + columns.emplace_back(sqlite3_column_name(cursor.stmt, idx)); + + Encodable::List rows; + int status = SQLITE_ROW; + + while (static_cast(rows.size()) < cursor.pageSize) { + status = sqlite3_step(cursor.stmt); + + if (status == SQLITE_ROW) { + Encodable::List row; + + for (size_t idx = 0; idx < columns.size(); idx++) { + switch (sqlite3_column_type(cursor.stmt, idx)) { + case SQLITE_INTEGER: + row.emplace_back(sqlite3_column_int64(cursor.stmt, idx)); + break; + case SQLITE_FLOAT: + row.emplace_back(sqlite3_column_double(cursor.stmt, idx)); + break; + case SQLITE_TEXT: + row.emplace_back( + reinterpret_cast(sqlite3_column_text(cursor.stmt, idx))); + break; + case SQLITE_BLOB: { + const uint8_t *blob = reinterpret_cast( + sqlite3_column_blob(cursor.stmt, idx)); + std::vector v(blob, blob + sqlite3_column_bytes(cursor.stmt, idx)); + row.emplace_back(v); + break; + } + case SQLITE_NULL: { + const char *columnDecltype = sqlite3_column_decltype(cursor.stmt, idx); + + if (columnDecltype != NULL && std::string("BLOB") == columnDecltype) + row.emplace_back(Encodable::Uint8List{}); + else + row.emplace_back(nullptr); + + break; + } + default: + break; + } + } + + rows.emplace_back(row); + continue; + } + + if (status == SQLITE_DONE) { + this->closeCursor(cursor); + break; + } + + this->closeCursor(cursor); + return this->currentErrorMessage(); + } + + result = Encodable::Map{{"columns", columns}, {"rows", rows}}; + + if (status != SQLITE_DONE) + result.insert({ARG_CURSOR_ID, cursor.id}); + + return std::nullopt; +} + +std::optional Database::Batch(const std::vector &operations, + bool continueOnError, + Encodable::List &results) +{ + for (const auto &operation : operations) { + const auto &method = operation.method; + + if (method == METHOD_INSERT) { + int insertID = 0; + const auto error = this->Insert(operation.sql, operation.arguments, insertID); + + if (error) { + if (!continueOnError) + return error; + + addError(results, *error); + continue; + } + + if (insertID == 0) + addResult(results, nullptr); + else + addResult(results, insertID); + + continue; + } + + if (method == METHOD_EXECUTE) { + const auto error = this->Execute(operation.sql, operation.arguments); + + if (error) { + if (!continueOnError) + return error; + + addError(results, *error); + continue; + } + + addResult(results, nullptr); + continue; + } + + if (method == METHOD_QUERY) { + Encodable::Map result; + const auto error = this->Query(operation.sql, operation.arguments, result); + + if (error) { + if (!continueOnError) + return error; + + addError(results, *error); + continue; + } + + addResult(results, result); + continue; + } + + if (method == METHOD_UPDATE) { + int updated = 0; + const auto error = this->Update(operation.sql, operation.arguments, updated); + + if (error) { + if (!continueOnError) + return error; + + addError(results, *error); + continue; + } + + addResult(results, updated); + continue; + } + } + + return std::nullopt; +} diff --git a/packages/sqflite/sqflite_aurora/aurora/lib/logger.cpp b/packages/sqflite/sqflite_aurora/aurora/lib/logger.cpp new file mode 100644 index 0000000..4f47b0b --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/lib/logger.cpp @@ -0,0 +1,44 @@ +#include +#include + +Logger::Logger(Level level, const std::string &tag) + : level(level) + , tag(tag) + , devnull(0) +{} + +Logger::Level Logger::LogLevel() const +{ + return this->level; +} + +void Logger::SetLogLevel(Level level) +{ + this->level = level; +} + +std::string Logger::Tag() const +{ + return this->tag; +} + +void Logger::SetTag(const std::string &tag) +{ + this->tag = tag; +} + +std::ostream &Logger::verb() +{ + if (this->level >= Level::Verbose) + return loginfo << (this->tag.empty() ? "" : "[" + this->tag + "] "); + + return this->devnull; +} + +std::ostream &Logger::sql() +{ + if (this->level >= Level::Sql) + return loginfo << (this->tag.empty() ? "" : "[" + this->tag + "] "); + + return this->devnull; +} diff --git a/packages/sqflite/sqflite_aurora/aurora/lib/sqflite_aurora_plugin.cpp b/packages/sqflite/sqflite_aurora/aurora/lib/sqflite_aurora_plugin.cpp new file mode 100644 index 0000000..8e3bf17 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/aurora/lib/sqflite_aurora_plugin.cpp @@ -0,0 +1,619 @@ +#include +#include + +#include +#include + +#include +#include + +namespace { + +int64_t GetTransactionID(const Encodable &args) +{ + if (!args.HasKey(ARG_TRANSACTION_ID)) + return static_cast(Database::TransactionID::None); + + if (args[ARG_TRANSACTION_ID].IsNull()) + return static_cast(Database::TransactionID::None); + + if (!args[ARG_TRANSACTION_ID].IsInt()) + return static_cast(Database::TransactionID::None); + + return args[ARG_TRANSACTION_ID].GetInt(); +} + +Encodable::List GetSqlArguments(const Encodable &args) +{ + if (!args.HasKey(ARG_SQL_ARGUMENTS)) + return Encodable::List{}; + + if (args[ARG_SQL_ARGUMENTS].IsNull()) + return Encodable::List{}; + + if (!args[ARG_SQL_ARGUMENTS].IsList()) + return Encodable::List{}; + + return args[ARG_SQL_ARGUMENTS].GetList(); +} + +} /* namespace */ + +SqfliteAuroraPlugin::SqfliteAuroraPlugin() + : dbID(0) + , logger(Logger::Level::None, "sqflite") +{} + +void SqfliteAuroraPlugin::RegisterWithRegistrar(PluginRegistrar ®istrar) +{ + registrar.RegisterMethodChannel("com.tekartik.sqflite", + MethodCodecType::Standard, + [this](const MethodCall &call) { this->onMethodCall(call); }); +} + +void SqfliteAuroraPlugin::onMethodCall(const MethodCall &call) +{ + const auto &method = call.GetMethod(); + + if (method == METHOD_GET_PLATFORM_VERSION) { + this->onPlatformVersionCall(call); + return; + } + + if (method == METHOD_OPEN_DATABASE) { + this->onOpenDatabaseCall(call); + return; + } + + if (method == METHOD_CLOSE_DATABASE) { + this->onCloseDatabaseCall(call); + return; + } + + if (method == METHOD_DELETE_DATABASE) { + this->onDeleteDatabaseCall(call); + return; + } + + if (method == METHOD_DATABASE_EXISTS) { + this->onDatabaseExistsCall(call); + return; + } + + if (method == METHOD_GET_DATABASES_PATH) { + this->onGetDatabasesPathCall(call); + return; + } + + if (method == METHOD_OPTIONS) { + this->onOptionsCall(call); + return; + } + + if (method == METHOD_DEBUG) { + this->onDebugCall(call); + return; + } + + if (method == METHOD_EXECUTE) { + this->onExecuteCall(call); + return; + } + + if (method == METHOD_QUERY) { + this->onQueryCall(call); + return; + } + + if (method == METHOD_QUERY_CURSOR_NEXT) { + this->onQueryCursorNextCall(call); + return; + } + + if (method == METHOD_UPDATE) { + this->onUpdateCall(call); + return; + } + + if (method == METHOD_INSERT) { + this->onInsertCall(call); + return; + } + + if (method == METHOD_BATCH) { + this->onBatchCall(call); + return; + } + + this->success(call); +} + +void SqfliteAuroraPlugin::onPlatformVersionCall(const MethodCall &call) +{ + std::ifstream in("/etc/os-release"); + std::string line; + + while (in.is_open() && std::getline(in, line)) { + if (line.rfind("VERSION_ID=") != 0) + continue; + + this->success(call, "Aurora " + line.substr(11)); + return; + } + + this->success(call, "Aurora"); +} + +void SqfliteAuroraPlugin::onOpenDatabaseCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + + const auto dbPath = args[ARG_PATH].GetString(); + const auto readOnly = args.HasKey(ARG_READ_ONLY) ? args[ARG_READ_ONLY].GetBoolean() : false; + + const auto inMemory = dbPath.empty() || dbPath == ":memory:"; + const auto singleton = args[ARG_SINGLE_INSTANCE].GetBoolean() && !inMemory; + + if (singleton) { + const auto db = this->databaseByPath(dbPath); + + if (db) { + if (db->IsOpen()) { + this->logger.verb() << "re-opened single instance database" + << (db->IsInTransaction() ? "(in transaction) " : "") + << db->ID() << " " << db->Path() << std::endl; + + this->success(call, makeOpenResult(db->ID(), true, db->IsInTransaction())); + return; + } + + this->logger.verb() << "single instance database " << db->Path() << " not opened" + << std::endl; + } + } + + const auto db = std::make_shared(++dbID, dbPath, singleton, logger); + + this->asynq.Push([this, db, readOnly, call] { + this->logger.sql() << "open database " + db->Path() + " (ID=" << db->ID() << ")" + << std::endl; + + const auto error = readOnly ? db->OpenReadOnly() : db->Open(); + if (error) { + this->error(call, ERROR_OPEN, db->Path(), *error); + return; + } + + this->databaseAdd(db); + this->success(call, makeOpenResult(db->ID(), false, false)); + }); +} + +void SqfliteAuroraPlugin::onCloseDatabaseCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + const auto dbID = args[ARG_ID].GetInt(); + + const auto db = databaseByID(dbID); + + this->asynq.Push([this, db, dbID, call] { + if (!db) { + this->error(call, ERROR_CLOSED, "database closed", "ID=" + std::to_string(dbID) + ")"); + return; + } + + this->logger.sql() << "closing database with ID=" << db->ID() << std::endl; + + const auto error = db->Close(); + if (error) { + this->error(call, ERROR_CLOSE, db->Path(), *error); + return; + } + + this->databaseRemove(db); + this->success(call); + }); +} + +void SqfliteAuroraPlugin::onDeleteDatabaseCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + const auto dbPath = args[ARG_PATH].GetString(); + + const auto db = databaseByPath(dbPath); + + this->asynq.Push([this, db, dbPath, call] { + if (db) { + if (db->IsOpen()) { + this->logger.verb() + << "close database " << db->Path() << " (ID=" << db->ID() << ")" << std::endl; + + const auto error = db->Close(); + if (error) { + this->error(call, ERROR_CLOSE, db->Path(), *error); + return; + } + } + + this->databaseRemove(db); + } + + if (std::filesystem::exists(dbPath)) { + this->logger.verb() << "delete not opened database " << dbPath << std::endl; + std::filesystem::remove(dbPath); + } + + this->success(call); + }); +} + +void SqfliteAuroraPlugin::onDatabaseExistsCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + const auto dbPath = args[ARG_PATH].GetString(); + + this->success(call, std::filesystem::exists(dbPath)); +} + +void SqfliteAuroraPlugin::onGetDatabasesPathCall(const MethodCall &call) +{ + const auto home = std::getenv("HOME"); + + if (home == nullptr) { + this->error(call, ERROR_INTERNAL, "environment variable $HOME not found"); + return; + } + + const auto [orgname, appname] = Application::GetID(); + const auto directory = std::filesystem::path(home) / ".local/share" / orgname / appname; + + this->success(call, directory.generic_string()); +} + +void SqfliteAuroraPlugin::onOptionsCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + const auto level = args[ARG_LOG_LEVEL].GetInt(); + + this->logger.SetLogLevel(static_cast(level)); + this->success(call); +} + +void SqfliteAuroraPlugin::onDebugCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + const auto cmd = args[ARG_COMMAND].GetString(); + + std::lock_guard lock(this->mutex); + Encodable::Map result; + + if (cmd == "get") { + if (this->logger.LogLevel() > Logger::Level::None) + result.emplace(ARG_LOG_LEVEL, static_cast(this->logger.LogLevel())); + + if (!this->databases.empty()) { + Encodable::Map databases; + + for (const auto &[id, db] : this->databases) { + Encodable::Map info; + + info.emplace(ARG_PATH, db->Path()); + info.emplace(ARG_SINGLE_INSTANCE, db->IsSingleInstance()); + + if (db->LogLevel() > Logger::Level::None) + info.emplace(ARG_LOG_LEVEL, static_cast(db->LogLevel())); + + databases.emplace(std::to_string(id), info); + } + + result.emplace(ARG_DATABASES, databases); + } + } + + this->success(call, result); +} + +void SqfliteAuroraPlugin::onExecuteCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + + const auto dbID = args[ARG_ID].GetInt(); + const auto sql = args[ARG_SQL].GetString(); + const auto sqlArgs = GetSqlArguments(args); + const auto inTransactionChange = args.HasKey(ARG_IN_TRANSACTION) + ? args[ARG_IN_TRANSACTION].GetBoolean() + : false; + const auto transactionID = GetTransactionID(args); + const auto enteringTransaction = inTransactionChange == true + && transactionID == Database::TransactionID::None; + + const auto db = databaseByID(dbID); + + if (!db) { + this->error(call, ERROR_CLOSED, "database closed", "ID=" + std::to_string(dbID) + ")"); + return; + } + + this->asynq.Push( + [this, db, sql, sqlArgs, inTransactionChange, enteringTransaction, transactionID, call] { + db->ProcessSqlCommand( + transactionID, + [this, db, sql, sqlArgs, inTransactionChange, enteringTransaction, call] { + if (enteringTransaction) + db->EnterInTransaction(); + + const auto error = db->Execute(sql, sqlArgs); + + if (error) { + db->LeaveTransaction(); + this->error(call, ERROR_INTERNAL, *error); + return; + } + + if (enteringTransaction) { + this->success(call, + Encodable::Map{ + {ARG_TRANSACTION_ID, db->CurrentTransactionID()}, + }); + return; + } + + if (inTransactionChange == false) + db->LeaveTransaction(); + + this->success(call); + }); + }); +} + +void SqfliteAuroraPlugin::onQueryCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + + const auto dbID = args[ARG_ID].GetInt(); + const auto sql = args[ARG_SQL].GetString(); + const auto sqlArgs = GetSqlArguments(args); + const auto transactionID = GetTransactionID(args); + const auto pageSize = args.HasKey(ARG_CURSOR_PAGE_SIZE) ? args[ARG_CURSOR_PAGE_SIZE].GetInt() + : -1; + const auto db = databaseByID(dbID); + + if (!db) { + this->error(call, ERROR_CLOSED, "database closed", "ID=" + std::to_string(dbID) + ")"); + return; + } + + this->asynq.Push([this, db, sql, sqlArgs, transactionID, pageSize, call] { + db->ProcessSqlCommand(transactionID, [this, db, sql, sqlArgs, pageSize, call] { + Encodable::Map result; + std::optional error; + + if (pageSize <= 0) + error = db->Query(sql, sqlArgs, result); + else + error = db->QueryWithPageSize(sql, sqlArgs, pageSize, result); + + if (error) { + this->error(call, ERROR_INTERNAL, *error); + return; + } + + this->success(call, result); + }); + }); +} + +void SqfliteAuroraPlugin::onQueryCursorNextCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + + const auto dbID = args[ARG_ID].GetInt(); + const auto cursorID = args[ARG_CURSOR_ID].GetInt(); + const auto closeCursor = args.HasKey(ARG_CANCEL) ? args[ARG_CANCEL].GetBoolean() : false; + const auto transactionID = GetTransactionID(args); + + const auto db = databaseByID(dbID); + + if (!db) { + this->error(call, ERROR_CLOSED, "database closed", "ID=" + std::to_string(dbID) + ")"); + return; + } + + this->asynq.Push([this, db, cursorID, closeCursor, transactionID, call] { + db->ProcessSqlCommand(transactionID, [this, db, cursorID, closeCursor, call] { + Encodable::Map result; + const auto error = db->QueryCursorNext(cursorID, closeCursor, result); + + if (error) { + this->error(call, ERROR_INTERNAL, *error); + return; + } + + this->success(call, result); + }); + }); +} + +void SqfliteAuroraPlugin::onUpdateCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + + const auto dbID = args[ARG_ID].GetInt(); + const auto sql = args[ARG_SQL].GetString(); + const auto sqlArgs = GetSqlArguments(args); + const auto transactionID = GetTransactionID(args); + + const auto db = databaseByID(dbID); + + if (!db) { + this->error(call, ERROR_CLOSED, "database closed", "ID=" + std::to_string(dbID) + ")"); + return; + } + + this->asynq.Push([this, db, sql, sqlArgs, transactionID, call] { + db->ProcessSqlCommand(transactionID, [this, db, sql, sqlArgs, call] { + int updated = 0; + const auto error = db->Update(sql, sqlArgs, updated); + + if (error) { + this->error(call, ERROR_INTERNAL, *error); + return; + } + + this->success(call, updated); + }); + }); +} + +void SqfliteAuroraPlugin::onInsertCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + + const auto dbID = args[ARG_ID].GetInt(); + const auto sql = args[ARG_SQL].GetString(); + const auto sqlArgs = GetSqlArguments(args); + const auto transactionID = GetTransactionID(args); + + const auto db = databaseByID(dbID); + + if (!db) { + this->error(call, ERROR_CLOSED, "database closed", "ID=" + std::to_string(dbID) + ")"); + return; + } + + this->asynq.Push([this, db, sql, sqlArgs, transactionID, call] { + db->ProcessSqlCommand(transactionID, [this, db, sql, sqlArgs, call] { + int insertID = 0; + const auto error = db->Insert(sql, sqlArgs, insertID); + + if (error) { + this->error(call, ERROR_INTERNAL, *error); + return; + } + + if (insertID == 0) + this->success(call); + else + this->success(call, insertID); + }); + }); +} + +void SqfliteAuroraPlugin::onBatchCall(const MethodCall &call) +{ + const auto &args = call.GetArguments(); + + const auto dbID = args[ARG_ID].GetInt(); + const auto &operations = args[ARG_OPERATIONS].GetList(); + const auto noResult = args.HasKey(ARG_NO_RESULT) ? args[ARG_NO_RESULT].GetBoolean() : false; + const auto continueOnError = args.HasKey(ARG_CONTINUE_ON_ERROR) + ? args[ARG_CONTINUE_ON_ERROR].GetBoolean() + : false; + const auto transactionID = GetTransactionID(args); + + const auto db = databaseByID(dbID); + + if (!db) { + this->error(call, ERROR_CLOSED, "database closed", "ID=" + std::to_string(dbID) + ")"); + return; + } + + std::vector dbOperations; + + for (const auto &operation : operations) { + const auto method = operation[ARG_METHOD].GetString(); + const auto sql = operation[ARG_SQL].GetString(); + const auto sqlArgs = GetSqlArguments(operation); + + dbOperations.emplace_back(Database::Operation{method, sql, sqlArgs}); + } + + this->asynq.Push([this, db, dbOperations, transactionID, continueOnError, noResult, call] { + const auto command = [this, db, dbOperations, continueOnError, noResult, call] { + Encodable::List results; + const auto error = db->Batch(dbOperations, continueOnError, results); + if (error) { + this->error(call, ERROR_INTERNAL, *error); + return; + } + + if (noResult) + this->success(call); + else + this->success(call, results); + }; + + db->ProcessSqlCommand(transactionID, command); + }); +} + +std::shared_ptr SqfliteAuroraPlugin::databaseByPath(const std::string &path) +{ + std::lock_guard lock(this->mutex); + + if (!this->singletonDatabases.count(path)) + return nullptr; + + return this->singletonDatabases.at(path); +} + +std::shared_ptr SqfliteAuroraPlugin::databaseByID(int64_t id) +{ + std::lock_guard lock(this->mutex); + + if (!this->databases.count(id)) + return nullptr; + + return this->databases.at(id); +} + +void SqfliteAuroraPlugin::databaseRemove(std::shared_ptr db) +{ + std::lock_guard lock(this->mutex); + + if (db->IsSingleInstance()) + this->singletonDatabases.erase(db->Path()); + + this->databases.erase(db->ID()); +} + +void SqfliteAuroraPlugin::databaseAdd(std::shared_ptr db) +{ + std::lock_guard lock(this->mutex); + + if (db->IsSingleInstance()) + this->singletonDatabases.emplace(db->Path(), db); + + this->databases.emplace(db->ID(), db); +} + +void SqfliteAuroraPlugin::success(const MethodCall &call, const Encodable &result) +{ + call.SendSuccessResponse(result); +} + +void SqfliteAuroraPlugin::error(const MethodCall &call, + const std::string &error, + const std::string &message, + const std::string &desc, + const Encodable &details) +{ + call.SendErrorResponse(ERROR_SQFLITE, + error + ": " + message + (desc.empty() ? "" : " (" + desc + ")"), + details); +} + +Encodable::Map SqfliteAuroraPlugin::makeOpenResult(int64_t dbID, + bool recovered, + bool recoveredInTransaction) +{ + Encodable::Map result = {{ARG_ID, dbID}}; + + if (recovered) + result.emplace(ARG_RECOVERED, true); + + if (recoveredInTransaction) + result.emplace(ARG_RECOVERED_IN_TRANSACTION, true); + + return result; +} diff --git a/packages/sqflite/sqflite_aurora/data/preview.png b/packages/sqflite/sqflite_aurora/data/preview.png new file mode 100644 index 0000000..a0e0f33 Binary files /dev/null and b/packages/sqflite/sqflite_aurora/data/preview.png differ diff --git a/packages/sqflite/sqflite_aurora/example/.gitignore b/packages/sqflite/sqflite_aurora/example/.gitignore new file mode 100644 index 0000000..41f3a61 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/.gitignore @@ -0,0 +1,45 @@ +# 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 +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/pubspec.lock +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/sqflite/sqflite_aurora/example/LICENSE b/packages/sqflite/sqflite_aurora/example/LICENSE new file mode 100644 index 0000000..5385068 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2019, Alexandre Roux Tekartik +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/sqflite/sqflite_aurora/example/README.md b/packages/sqflite/sqflite_aurora/example/README.md new file mode 100644 index 0000000..92ee53c --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/README.md @@ -0,0 +1,9 @@ +# Sqflite example for Aurora OS + +Demonstrates how to use the `sqflite_aurora` plugin. + +## Building + +```shell +$ flutter build aurora [--release|--debug|--profile] +``` diff --git a/packages/sqflite/sqflite_aurora/example/analysis_options.yaml b/packages/sqflite/sqflite_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..fe99e5f --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/analysis_options.yaml @@ -0,0 +1,87 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + language: + strict-casts: true + strict-inference: true + + errors: + missing_required_param: warning + missing_return: warning + todo: ignore + included_file_warning: ignore + +linter: + rules: + - always_declare_return_types + - avoid_dynamic_calls + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_shadowing_type_parameters + - avoid_slow_async_io + - avoid_types_as_parameter_names + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - curly_braces_in_flow_control_structures + - directives_ordering + - empty_catches + - hash_and_equals + - iterable_contains_unrelated_type + - list_remove_unrelated_type + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + - omit_local_variable_types + - package_api_docs + - package_prefixed_library_names + - prefer_generic_function_type_aliases + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - sort_child_properties_last + - test_types_in_equals + - throw_in_finally + - unawaited_futures + - unnecessary_null_aware_assignments + - unnecessary_statements + - unrelated_type_equality_checks + - unsafe_html + - valid_regexps + + - constant_identifier_names + - control_flow_in_finally + - empty_statements + - implementation_imports + - overridden_fields + - package_names + - prefer_const_constructors + - prefer_initializing_formals + - prefer_void_to_null + + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_return_types_on_setters + - empty_constructor_bodies + - library_names + - library_prefixes + - prefer_adjacent_string_concatenation + - prefer_collection_literals + - prefer_contains + - prefer_equal_for_default_values + - slash_for_doc_comments + - type_init_formals + - unnecessary_const + - unnecessary_new + - unnecessary_null_in_if_null_operators + - use_rethrow_when_possible + + - public_member_api_docs + + - sort_constructors_first + - sort_unnamed_constructors_first diff --git a/packages/sqflite/sqflite_aurora/example/assets/example.db b/packages/sqflite/sqflite_aurora/example/assets/example.db new file mode 100644 index 0000000..e3db112 Binary files /dev/null and b/packages/sqflite/sqflite_aurora/example/assets/example.db differ diff --git a/packages/sqflite/sqflite_aurora/example/assets/issue_64.db b/packages/sqflite/sqflite_aurora/example/assets/issue_64.db new file mode 100644 index 0000000..d26f958 Binary files /dev/null and b/packages/sqflite/sqflite_aurora/example/assets/issue_64.db differ diff --git a/packages/sqflite/sqflite_aurora/example/aurora/.gitignore b/packages/sqflite/sqflite_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/sqflite/sqflite_aurora/example/aurora/CMakeLists.txt b/packages/sqflite/sqflite_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..4add9c6 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.sqflite_aurora_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/packages/sqflite/sqflite_aurora/example/aurora/desktop/com.example.sqflite_aurora_example.desktop b/packages/sqflite/sqflite_aurora/example/aurora/desktop/com.example.sqflite_aurora_example.desktop new file mode 100644 index 0000000..dbc40db --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/desktop/com.example.sqflite_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=Sqflite Example +Comment=Demonstrates how to use the sqflite_aurora plugin. +Icon=com.example.sqflite_aurora_example +Exec=/usr/bin/com.example.sqflite_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=sqflite_aurora_example diff --git a/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..4548767 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + std::make_shared(), + }); +} diff --git a/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..d5a7098 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,31 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST + sqflite_aurora +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/sqflite/sqflite_aurora/example/aurora/icons/108x108.png b/packages/sqflite/sqflite_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/sqflite/sqflite_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/sqflite/sqflite_aurora/example/aurora/icons/128x128.png b/packages/sqflite/sqflite_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/sqflite/sqflite_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/sqflite/sqflite_aurora/example/aurora/icons/172x172.png b/packages/sqflite/sqflite_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/sqflite/sqflite_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/sqflite/sqflite_aurora/example/aurora/icons/86x86.png b/packages/sqflite/sqflite_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/sqflite/sqflite_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/sqflite/sqflite_aurora/example/aurora/main.cpp b/packages/sqflite/sqflite_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/sqflite/sqflite_aurora/example/aurora/rpm/com.example.sqflite_aurora_example.spec b/packages/sqflite/sqflite_aurora/example/aurora/rpm/com.example.sqflite_aurora_example.spec new file mode 100644 index 0000000..5c0b8ae --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/aurora/rpm/com.example.sqflite_aurora_example.spec @@ -0,0 +1,32 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.sqflite_aurora_example +Summary: Demonstrates how to use the sqflite_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(sqlite3) +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/sqflite/sqflite_aurora/example/lib/batch_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/batch_test_page.dart new file mode 100644 index 0000000..66cb011 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/batch_test_page.dart @@ -0,0 +1,215 @@ +import 'package:flutter/foundation.dart'; +import 'package:sqflite/sqflite.dart'; + +import 'test_page.dart'; + +/// Batch test page. +class BatchTestPage extends TestPage { + /// Batch test page. + BatchTestPage({Key? key}) : super('Batch tests', key: key) { + test('BatchQuery', () async { + // await Sqflite.devSetDebugModeOn(); + final path = await initDeleteDb('batch_query.db'); + final db = await openDatabase(path); + + // empty batch + var batch = db.batch(); + batch.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item1']); + var results = await batch.commit(); + expect(results, [null, 1]); + + final dbResult = await db.rawQuery('SELECT id, name FROM Test'); + // devPrint('dbResult $dbResult'); + expect(dbResult, [ + {'id': 1, 'name': 'item1'} + ]); + + // one query + batch = db.batch(); + batch.rawQuery('SELECT id, name FROM Test'); + batch.query('Test', columns: ['id', 'name']); + results = await batch.commit(); + // devPrint('select $results ${results?.first}'); + expect(results, [ + [ + {'id': 1, 'name': 'item1'} + ], + [ + {'id': 1, 'name': 'item1'} + ] + ]); + await db.close(); + }); + test('Batch', () async { + // await databaseFactory.devSetDebugModeOn(); + final path = await initDeleteDb('batch.db'); + final db = await openDatabase(path); + + // empty batch + var batch = db.batch(); + var results = await batch.commit(); + expect(results.length, 0); + expect(results, isEmpty); + + // one create table + batch = db.batch(); + batch.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + results = await batch.commit(); + // devPrint('1 $results ${results?.first}'); + expect(results, [null]); + expect(results[0], null); + + // one insert + batch = db.batch(); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item1']); + results = await batch.commit(); + expect(results, [1]); + + // one query + batch = db.batch(); + batch.rawQuery('SELECT id, name FROM Test'); + batch.query('Test', columns: ['id', 'name']); + results = await batch.commit(); + // devPrint('select $results ${results?.first}'); + expect(results, [ + [ + {'id': 1, 'name': 'item1'} + ], + [ + {'id': 1, 'name': 'item1'} + ] + ]); + + // two insert + batch = db.batch(); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item2']); + batch.insert('Test', {'name': 'item3'}); + results = await batch.commit(); + expect(results, [2, 3]); + + // update + batch = db.batch(); + batch.rawUpdate( + 'UPDATE Test SET name = ? WHERE name = ?', ['new_item', 'item1']); + batch.update('Test', {'name': 'new_other_item'}, + where: 'name != ?', whereArgs: ['new_item']); + results = await batch.commit(); + expect(results, [1, 2]); + + // delete + batch = db.batch(); + batch.rawDelete('DELETE FROM Test WHERE name = ?', ['new_item']); + batch.delete('Test', + where: 'name = ?', whereArgs: ['new_other_item']); + results = await batch.commit(); + expect(results, [1, 2]); + + // No result + batch = db.batch(); + batch.insert('Test', {'name': 'item'}); + batch.update('Test', {'name': 'new_item'}, + where: 'name = ?', whereArgs: ['item']); + batch.delete('Test', where: 'name = ?', whereArgs: ['item']); + results = await batch.commit(noResult: true); + expect(results, isEmpty); + + await db.close(); + }); + + test('Batch in transaction', () async { + // await Sqflite.devSetDebugModeOn(); + final path = await initDeleteDb('batch_in_transaction.db'); + final db = await openDatabase(path); + + late List results; + + await db.transaction((txn) async { + final batch1 = txn.batch(); + batch1.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + final batch2 = txn.batch(); + batch2.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item1']); + results = await batch1.commit(); + expect(results, [null]); + + results = await batch2.commit(); + expect(results, [1]); + }); + + await db.close(); + }); + + test('Apply in database', () async { + // await Sqflite.devSetDebugModeOn(); + final path = await initDeleteDb('apply_in_database.db'); + final db = await openDatabase(path); + + late List results; + + final batch1 = db.batch(); + batch1.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + final batch2 = db.batch(); + batch2.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item1']); + results = await batch1.apply(); + expect(results, [null]); + + results = await batch2.apply(); + expect(results, [1]); + await db.close(); + }); + + test('Apply in transaction', () async { + // await Sqflite.devSetDebugModeOn(); + final path = await initDeleteDb('apply_in_transaction.db'); + final db = await openDatabase(path); + + late List results; + + await db.transaction((txn) async { + final batch1 = txn.batch(); + batch1.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + final batch2 = txn.batch(); + batch2.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item1']); + results = await batch1.apply(); + expect(results, [null]); + + results = await batch2.apply(); + expect(results, [1]); + }); + + await db.close(); + }); + + test('Batch continue on error', () async { + // await Sqflite.devSetDebugModeOn(); + final path = await initDeleteDb('batch_continue_on_error.db'); + final db = await openDatabase(path); + try { + final batch = db.batch(); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item1']); + batch.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + batch.execute('DUMMY'); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item1']); + batch.rawQuery('SELECT * FROM Test'); + final results = await batch.commit(continueOnError: true); + // devPrint(results); + // First result is an exception + var exception = results[0] as DatabaseException; + expect(exception.isNoSuchTableError(), true); + // Second result is null (create table) + expect(results[1], null); + // Third result is an exception + exception = results[2] as DatabaseException; + expect(exception.isSyntaxError(), true); + // Fourth result is an insert + expect(results[3], 1); + // Fifth is a select + expect(results[4], [ + {'id': 1, 'name': 'item1'} + ]); + } finally { + await db.close(); + } + }); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/database/database.dart b/packages/sqflite/sqflite_aurora/example/lib/database/database.dart new file mode 100644 index 0000000..0fb2b9b --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/database/database.dart @@ -0,0 +1,2 @@ +export 'database_impl.dart'; +export 'database_io.dart' if (dart.library.html) 'database_web.dart'; diff --git a/packages/sqflite/sqflite_aurora/example/lib/database/database_impl.dart b/packages/sqflite/sqflite_aurora/example/lib/database/database_impl.dart new file mode 100644 index 0000000..ab9c3a0 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/database/database_impl.dart @@ -0,0 +1,90 @@ +import 'dart:typed_data'; + +import 'package:sqflite/sqflite.dart'; + +import 'database.dart'; + +/// Custom platform Handler, need to handle Web or IO differently or from a +/// custom app +abstract class PlatformHandler { + /// delete the db, create the folder and returns its path + Future initDeleteDb(String dbName) async { + if (await databaseExists(dbName)) { + await deleteDatabase(dbName); + } + return dbName; + } + + /// Write the db file directly to the file system + Future writeFileAsBytes(String path, List bytes, + {bool flush = false}); + + /// Read a file as bytes + Future readFileAsBytes(String path); + + /// Write a file as a string + Future writeFileAsString(String path, String text, + {bool flush = false}); + + /// Read a file as a string + Future readFileAsString(String path); + + /// Check if a path exists. + Future pathExists(String path); + + /// Recursively create a directory + Future createDirectory(String path); + + /// Recursively delete a directory + Future deleteDirectory(String path); + + /// Check if a directory exists + Future existsDirectory(String path); +} + +// --- +// Compat, to keep the example page as is +// --- + +/// delete the db, create the folder and returnes its path +Future initDeleteDb(String dbName) => + platformHandler.initDeleteDb(dbName); + +/// Write the db file directly to the file system +Future writeFileAsBytes(String path, List bytes, + {bool flush = false}) => + platformHandler.writeFileAsBytes(path, bytes, flush: flush); + +/// Read a file as bytes +Future readFileAsBytes(String path) => + platformHandler.readFileAsBytes(path); + +/// Write a file as a string +Future writeFileAsString(String path, String text, + {bool flush = false}) => + platformHandler.writeFileAsString(path, text, flush: flush); + +/// Read a file as a string +Future readFileAsString(String path) => + platformHandler.readFileAsString(path); + +/// Check if a path exists. +Future pathExists(String path) => platformHandler.pathExists(path); + +/// Recursively create a directory +Future createDirectory(String path) => + platformHandler.createDirectory(path); + +/// Recursively delete a directory +Future deleteDirectory(String path) => + platformHandler.deleteDirectory(path); + +/// Check if a directory exists +Future existsDirectory(String path) => + platformHandler.existsDirectory(path); + +PlatformHandler? _platformHandler; + +/// Platform handler (can be overriden, needed for the web test app) +PlatformHandler get platformHandler => _platformHandler ??= platformHandlerIo; +set platformHandler(PlatformHandler handler) => _platformHandler = handler; diff --git a/packages/sqflite/sqflite_aurora/example/lib/database/database_io.dart b/packages/sqflite/sqflite_aurora/example/lib/database/database_io.dart new file mode 100644 index 0000000..58592dd --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/database/database_io.dart @@ -0,0 +1,86 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_example/database/database.dart'; + +class _PlatformHandlerIo extends PlatformHandler { + /// delete the db, create the folder and returns its path + @override + Future initDeleteDb(String dbName) async { + final databasePath = await getDatabasesPath(); + // print(databasePath); + final path = join(databasePath, dbName); + + // make sure the folder exists + // ignore: avoid_slow_async_io + if (await Directory(dirname(path)).exists()) { + await deleteDatabase(path); + } else { + try { + await Directory(dirname(path)).create(recursive: true); + } catch (e) { + // ignore: avoid_print + print(e); + } + } + return path; + } + + /// Write the db file directly to the file system + @override + Future writeFileAsBytes(String path, List bytes, + {bool flush = false}) async { + await File(path).writeAsBytes(bytes, flush: flush); + } + + /// Read a file as bytes + @override + Future readFileAsBytes(String path) async { + return File(path).readAsBytes(); + } + + /// Write a file as a string + @override + Future writeFileAsString(String path, String text, + {bool flush = false}) async { + await File(path).writeAsString(text, flush: true); + } + + /// Read a file as a string + @override + Future readFileAsString(String path) async { + return File(path).readAsString(); + } + + /// Check if a path exists. + @override + Future pathExists(String path) async { + // ignore: avoid_slow_async_io + return File(path).exists(); + } + + /// Recursively create a directory + @override + Future createDirectory(String path) async { + await Directory(dirname(path)).create(recursive: true); + } + + /// Recursively delete a directory + @override + Future deleteDirectory(String path) async { + await Directory(path).delete(recursive: true); + } + + /// Check if a directory exists + @override + Future existsDirectory(String path) async { + // ignore: avoid_slow_async_io + return Directory(path).exists(); + } +} + +/// Io platform handler +PlatformHandler platformHandlerIo = _PlatformHandlerIo(); diff --git a/packages/sqflite/sqflite_aurora/example/lib/database/database_web.dart b/packages/sqflite/sqflite_aurora/example/lib/database/database_web.dart new file mode 100644 index 0000000..a0047aa --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/database/database_web.dart @@ -0,0 +1,5 @@ +import 'package:sqflite_example/database/database.dart'; + +/// platform handler io not supported on the web. +PlatformHandler get platformHandlerIo => + throw UnsupportedError('platform handler io not supported on the web'); diff --git a/packages/sqflite/sqflite_aurora/example/lib/deprecated_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/deprecated_test_page.dart new file mode 100644 index 0000000..a03d30b --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/deprecated_test_page.dart @@ -0,0 +1,11 @@ +import 'package:flutter/foundation.dart'; + +import 'test_page.dart'; + +/// Deprecated test page. +class DeprecatedTestPage extends TestPage { + /// Deprecated test page. + DeprecatedTestPage({Key? key}) : super('Deprecated tests', key: key) { + test('None', () async {}); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/exception_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/exception_test_page.dart new file mode 100644 index 0000000..e360990 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/exception_test_page.dart @@ -0,0 +1,723 @@ +import 'package:flutter/foundation.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite/sql.dart'; + +import 'src/common_import.dart'; +import 'test_page.dart'; + +// ignore_for_file: avoid_print +/// Exception test page. +class ExceptionTestPage extends TestPage { + /// Exception test page. + ExceptionTestPage({Key? key}) : super('Exception tests', key: key) { + test('Transaction failed', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('transaction_failed.db'); + final db = await openDatabase(path); + + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + // insert then fails to make sure the transaction is cancelled + var hasFailed = false; + try { + await db.transaction((txn) async { + await txn.rawInsert( + 'INSERT INTO Test (name) VALUES (?)', ['item']); + final afterCount = Sqflite.firstIntValue( + await txn.rawQuery('SELECT COUNT(*) FROM Test')); + expect(afterCount, 1); + + hasFailed = true; + // this failure should cancel the insertion before + await txn.execute('DUMMY CALL'); + hasFailed = false; + }); + } on DatabaseException catch (e) { + // iOS: native_error: PlatformException(sqlite_error, Error Domain=FMDatabase Code=1 'near 'DUMMY': syntax error' UserInfo={NSLocalizedDescription=near 'DUMMY': syntax error}, null) + print('native_error: $e'); + } + verify(hasFailed); + + final afterCount = + Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM Test')); + expect(afterCount, 0); + + await db.close(); + }); + + test('Batch failed', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('batch_failed.db'); + final db = await openDatabase(path); + + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + final batch = db.batch(); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item']); + batch.execute('DUMMY CALL'); + + var hasFailed = true; + try { + await batch.commit(); + hasFailed = false; + } on DatabaseException catch (e) { + print('native_error: $e'); + } + + verify(hasFailed); + + final afterCount = + Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM Test')); + expect(afterCount, 0); + + await db.close(); + }); + + test('Sqlite Exception', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('exception.db'); + final db = await openDatabase(path); + + // Query + try { + await db.rawQuery('SELECT COUNT(*) FROM Test'); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isNoSuchTableError('Test')); + // Error Domain=FMDatabase Code=1 'no such table: Test' UserInfo={NSLocalizedDescription=no such table: Test}) + } + + // Catch without using on DatabaseException + try { + await db.rawQuery('malformed query'); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + //verify(e.toString().contains('sql 'malformed query' args')); + // devPrint(e); + } + + try { + await db.rawQuery('malformed query with args ?', [1]); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + print(e); + verify(e + .toString() + .contains("sql 'malformed query with args ?' args [1]")); + } + + try { + await db.execute('DUMMY'); + fail(); // should fail before + } on Exception catch (e) { + //verify(e.isSyntaxError()); + print(e); + verify(e.toString().contains('DUMMY')); + } + + try { + await db.rawInsert('DUMMY'); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + verify(e.toString().contains('DUMMY')); + } + + try { + await db.rawUpdate('DUMMY'); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + verify(e.toString().contains('DUMMY')); + } + + await db.close(); + }); + + test('Sqlite constraint Exception', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('constraint_exception.db'); + final db = await openDatabase(path, version: 1, onCreate: (db, version) { + db.execute('CREATE TABLE Test (name TEXT UNIQUE)'); + }); + await db.insert('Test', {'name': 'test1'}); + + try { + await db.insert('Test', {'name': 'test1'}); + } on DatabaseException catch (e) { + // iOS: Error Domain=FMDatabase Code=19 'UNIQUE constraint failed: Test.name' UserInfo={NSLocalizedDescription=UNIQUE constraint failed: Test.name}) s + // Android: UNIQUE constraint failed: Test.name (code 2067)) + print(e); + + verify(e.isUniqueConstraintError()); + expect(e.getResultCode(), 2067); + verify(e.isUniqueConstraintError('Test.name')); + } + + await db.close(); + }); + + test('Sqlite constraint primary key', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('constraint_primary_key_exception.db'); + final db = await openDatabase(path, version: 1, onCreate: (db, version) { + db.execute('CREATE TABLE Test (name TEXT PRIMARY KEY)'); + }); + await db.insert('Test', {'name': 'test1'}); + + try { + await db.insert('Test', {'name': 'test1'}); + } on DatabaseException catch (e) { + // iOS: Error Domain=FMDatabase Code=19 'UNIQUE constraint failed: Test.name' UserInfo={NSLocalizedDescription=UNIQUE constraint failed: Test.name}) s + // Android: UNIQUE constraint failed: Test.name (code 1555)) + print(e); + verify(e.isUniqueConstraintError()); + verify(e.isUniqueConstraintError('Test.name')); + + expect(e.getResultCode(), 1555); + } + + await db.close(); + }); + + test('Sqlite batch Exception', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('batch_exception.db'); + final db = await openDatabase(path); + + // Query + try { + final batch = db.batch(); + batch.rawQuery('SELECT COUNT(*) FROM Test'); + await batch.commit(); + fail(); // should fail before + } on DatabaseException catch (e) { + print(e); + verify(e.isNoSuchTableError('Test')); + } + + // Catch without using on DatabaseException + try { + final batch = db.batch(); + batch.rawQuery('malformed query'); + await batch.commit(); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + print(e); + verify(e.toString().contains("sql 'malformed query'")); + } + + try { + final batch = db.batch(); + batch.rawQuery('malformed query with args ?', [1]); + await batch.commit(); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + print(e); + verify(e + .toString() + .contains("sql 'malformed query with args ?' args [1]")); + } + + try { + final batch = db.batch(); + batch.execute('DUMMY'); + await batch.commit(); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + // devPrint(e); + // iOS Error Domain=FMDatabase Code=1 "near "DUMMY": syntax error" UserInfo={NSLocalizedDescription=near "DUMMY": syntax error}) + verify(e.toString().contains("sql 'DUMMY'")); + } + + try { + final batch = db.batch(); + batch.rawInsert('DUMMY'); + await batch.commit(); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + verify(e.toString().contains("sql 'DUMMY'")); + } + + try { + final batch = db.batch(); + batch.rawUpdate('DUMMY'); + await batch.commit(); + fail(); // should fail before + } on DatabaseException catch (e) { + verify(e.isSyntaxError()); + verify(e.toString().contains("sql 'DUMMY'")); + } + + await db.close(); + }); + + test('Open onDowngrade fail', () async { + final path = await initDeleteDb('open_on_downgrade_fail.db'); + var database = await openDatabase(path, version: 2, + onCreate: (Database db, int version) async { + await db.execute('CREATE TABLE Test(id INTEGER PRIMARY KEY)'); + }); + await database.close(); + + // currently this is crashing... + // should fail going back in versions + try { + database = await openDatabase(path, + version: 1, onDowngrade: onDatabaseVersionChangeError); + verify(false); + } catch (e) { + print(e); + } + + // should work + database = await openDatabase(path, + version: 2, onDowngrade: onDatabaseVersionChangeError); + print(database); + await database.close(); + }); + + test('Access after close', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('access_after_close.db'); + final database = await openDatabase(path, version: 3, + onCreate: (Database db, int version) async { + await db.execute('CREATE TABLE Test(id INTEGER PRIMARY KEY)'); + }); + await database.close(); + try { + await database.getVersion(); + verify(false); + } on DatabaseException catch (e) { + print(e); + verify(e.isDatabaseClosedError()); + } + + try { + await database.setVersion(1); + fail(); + } on DatabaseException catch (e) { + print(e); + verify(e.isDatabaseClosedError()); + } + }); + + test('Non escaping fields', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('non_escaping_fields.db'); + final db = await openDatabase(path); + + const table = 'table'; + try { + await db.execute('CREATE TABLE $table (group INTEGER)'); + fail('should fail'); + } on DatabaseException catch (e) { + print(e); + verify(e.isSyntaxError()); + } + try { + await db.execute('INSERT INTO $table (group) VALUES (1)'); + fail('should fail'); + } on DatabaseException catch (e) { + print(e); + verify(e.isSyntaxError()); + } + try { + await db.rawQuery('SELECT * FROM $table ORDER BY group DESC'); + } on DatabaseException catch (e) { + print(e); + verify(e.isSyntaxError()); + } + + try { + await db.rawQuery('DELETE FROM $table'); + } on DatabaseException catch (e) { + print(e); + verify(e.isSyntaxError()); + } + + // Build our escape list from all the sqlite keywords + final toExclude = []; + for (var name in allEscapeNames) { + try { + await db.execute('CREATE TABLE $name (value INTEGER)'); + } on DatabaseException catch (e) { + await db.execute('CREATE TABLE ${escapeName(name)} (value INTEGER)'); + + verify(e.isSyntaxError()); + toExclude.add(name); + } + } + + print(json.encode(toExclude)); + + await db.close(); + }); + + test('Bind no argument (no iOS)', () async { + if (!platform.isIOS) { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('bind_no_arg_failed.db'); + final db = await openDatabase(path); + + await db.execute('CREATE TABLE Test (name TEXT)'); + + await db.rawInsert('INSERT INTO Test (name) VALUES ("?")', []); + + await db.rawQuery('SELECT * FROM Test WHERE name = ?', []); + + await db.rawDelete('DELETE FROM Test WHERE name = ?', []); + + await db.close(); + } + }); + + test('crash ios (no iOS)', () async { + // This crashes natively on iOS...can't catch it yet + if (!platform.isIOS) { + //if (true) { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('bind_no_arg_failed.db'); + final db = await openDatabase(path); + + await db.execute('CREATE TABLE Test (name TEXT)'); + + await db.rawInsert('INSERT INTO Test (name) VALUES ("?")', []); + + await db.rawQuery('SELECT * FROM Test WHERE name = ?', []); + + await db.close(); + } + }); + + test('Bind null argument', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('bind_null_failed.db'); + final db = await openDatabase(path); + + await db.execute('CREATE TABLE Test (name TEXT)'); + + //await db.rawInsert("INSERT INTO Test (name) VALUES (\"?\")", [null]); + /* + nnbd this can no longer be tested! + + try { + await db.rawInsert('INSERT INTO Test (name) VALUES (?)', [null]); + } on DatabaseException catch (e) { + print('ERR: $e'); + expect(e.toString().contains("sql 'INSERT"), true); + } + + try { + await db.rawQuery('SELECT * FROM Test WHERE name = ?', [null]); + } on DatabaseException catch (e) { + print('ERR: $e'); + expect(e.toString().contains("sql 'SELECT * FROM Test"), true); + } + + try { + await db.rawDelete('DELETE FROM Test WHERE name = ?', [null]); + } on DatabaseException catch (e) { + print('ERR: $e'); + expect(e.toString().contains("sql 'DELETE FROM Test"), true); + } + + */ + + await db.close(); + }); + + test('Bind no parameter', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('bind_no_parameter_failed.db'); + final db = await openDatabase(path); + + await db.execute('CREATE TABLE Test (name TEXT)'); + + try { + await db + .rawInsert('INSERT INTO Test (name) VALUES ("value")', ['value2']); + } on DatabaseException catch (e) { + print('ERR: $e'); + expect(e.toString().contains("sql 'INSERT INTO Test"), true); + } + + try { + await db + .rawQuery('SELECT * FROM Test WHERE name = "value"', ['value2']); + } on DatabaseException catch (e) { + print('ERR: $e'); + expect(e.toString().contains("sql 'SELECT * FROM Test"), true); + } + + try { + await db.rawDelete('DELETE FROM Test WHERE name = "value"', ['value2']); + } on DatabaseException catch (e) { + print('ERR: $e'); + expect(e.toString().contains("sql 'DELETE FROM Test"), true); + } + + await db.close(); + }); + + // Using the db object in a transaction lead to a deadlock... + test('Dead lock', () async { + final path = await initDeleteDb('dead_lock.db'); + final db = await openDatabase(path); + try { + var hasTimedOut = false; + var callbackCount = 0; + Sqflite.setLockWarningInfo( + duration: const Duration(milliseconds: 200), + callback: () { + callbackCount++; + }); + + await db.transaction((txn) async { + try { + await db.getVersion().timeout(const Duration(milliseconds: 1500)); + fail('should fail'); + } on TimeoutException catch (_) { + hasTimedOut = true; + } + }); + + expect(hasTimedOut, true); + expect(callbackCount, 1); + } finally { + await db.close(); + } + }); + + test('Thread dead lock', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('thread_dead_lock.db'); + final db1 = await openDatabase(path, singleInstance: false); + final db2 = await openDatabase(path, singleInstance: false); + try { + await db1.execute('BEGIN IMMEDIATE TRANSACTION'); + + try { + // this should block the main thread + await db2 + .execute('BEGIN IMMEDIATE TRANSACTION') + .timeout(const Duration(milliseconds: 500)); + fail('should timeout'); + } on TimeoutException catch (e) { + print('caught $e'); + } + + // Try to open another db to check that the main thread is free + final db = await openDatabase(inMemoryDatabasePath); + await db.close(); + + try { + // clean up + await db1.execute('ROLLBACK'); + } catch (_) {} + + try { + await db2.execute('ROLLBACK'); + } catch (_) {} + } finally { + await db1.close(); + await db2.close(); + } + }); + } +} + +/// Name that should be escaped. +var escapeNames = [ + 'add', + 'all', + 'alter', + 'and', + 'as', + 'autoincrement', + 'between', + 'case', + 'check', + 'collate', + 'commit', + 'constraint', + 'create', + 'default', + 'deferrable', + 'delete', + 'distinct', + 'drop', + 'else', + 'escape', + 'except', + 'exists', + 'foreign', + 'from', + 'group', + 'having', + 'if', + 'in', + 'index', + 'insert', + 'intersect', + 'into', + 'is', + 'isnull', + 'join', + 'limit', + 'not', + 'notnull', + 'null', + 'on', + 'or', + 'order', + 'primary', + 'references', + 'select', + 'set', + 'table', + 'then', + 'to', + 'transaction', + 'union', + 'unique', + 'update', + 'using', + 'values', + 'when', + 'where' +]; + +/// all SQLite keywords to escape. +var allEscapeNames = [ + 'abort', + 'action', + 'add', + 'after', + 'all', + 'alter', + 'analyze', + 'and', + 'as', + 'asc', + 'attach', + 'autoincrement', + 'before', + 'begin', + 'between', + 'by', + 'cascade', + 'case', + 'cast', + 'check', + 'collate', + 'column', + 'commit', + 'conflict', + 'constraint', + 'create', + 'cross', + 'current_date', + 'current_time', + 'current_timestamp', + 'database', + 'default', + 'deferrable', + 'deferred', + 'delete', + 'desc', + 'detach', + 'distinct', + 'drop', + 'each', + 'else', + 'end', + 'escape', + 'except', + 'exclusive', + 'exists', + 'explain', + 'fail', + 'for', + 'foreign', + 'from', + 'full', + 'glob', + 'group', + 'having', + 'if', + 'ignore', + 'immediate', + 'in', + 'index', + 'indexed', + 'initially', + 'inner', + 'insert', + 'instead', + 'intersect', + 'into', + 'is', + 'isnull', + 'join', + 'key', + 'left', + 'like', + 'limit', + 'match', + 'natural', + 'no', + 'not', + 'notnull', + 'null', + 'of', + 'offset', + 'on', + 'or', + 'order', + 'outer', + 'plan', + 'pragma', + 'primary', + 'query', + 'raise', + 'recursive', + 'references', + 'regexp', + 'reindex', + 'release', + 'rename', + 'replace', + 'restrict', + 'right', + 'rollback', + 'row', + 'savepoint', + 'select', + 'set', + 'table', + 'temp', + 'temporary', + 'then', + 'to', + 'transaction', + 'trigger', + 'union', + 'unique', + 'update', + 'using', + 'vacuum', + 'values', + 'view', + 'virtual', + 'when', + 'where', + 'with', + 'without' +]; diff --git a/packages/sqflite/sqflite_aurora/example/lib/exp_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/exp_test_page.dart new file mode 100644 index 0000000..d67ce66 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/exp_test_page.dart @@ -0,0 +1,777 @@ +import 'dart:isolate'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_common/sqflite_dev.dart'; +import 'package:sqflite_example/src/common_import.dart'; +import 'package:sqflite_example/utils.dart'; + +import 'test_page.dart'; + +// ignore_for_file: avoid_print + +/// `todo` table name +const String tableTodo = 'todo'; + +/// id column name +const String columnId = '_id'; + +/// title column name +const String columnTitle = 'title'; + +/// done column name +const String columnDone = 'done'; + +/// Experiment test page. +class ExpTestPage extends TestPage { + /// Experiment test page. + ExpTestPage({Key? key}) : super('Exp Tests', key: key) { + test('order_by', () async { + //await Sqflite.setDebugModeOn(true); + final path = await initDeleteDb('order_by_exp.db'); + final db = await openDatabase(path); + + const table = 'test'; + await db + .execute('CREATE TABLE $table (column_1 INTEGER, column_2 INTEGER)'); + // inserted in a wrong order to check ASC/DESC + await db + .execute('INSERT INTO $table (column_1, column_2) VALUES (11, 180)'); + await db + .execute('INSERT INTO $table (column_1, column_2) VALUES (10, 180)'); + await db + .execute('INSERT INTO $table (column_1, column_2) VALUES (10, 2000)'); + + final expectedResult = [ + {'column_1': 10, 'column_2': 2000}, + {'column_1': 10, 'column_2': 180}, + {'column_1': 11, 'column_2': 180} + ]; + + var result = await db.rawQuery( + 'SELECT * FROM $table ORDER BY column_1 ASC, column_2 DESC'); + //print(JSON.encode(result)); + expect(result, expectedResult); + result = await db.query(table, orderBy: 'column_1 ASC, column_2 DESC'); + expect(result, expectedResult); + + await db.close(); + }); + + test('in', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('simple_exp.db'); + final db = await openDatabase(path); + + const table = 'test'; + await db + .execute('CREATE TABLE $table (column_1 INTEGER, column_2 INTEGER)'); + await db + .execute('INSERT INTO $table (column_1, column_2) VALUES (1, 1001)'); + await db + .execute('INSERT INTO $table (column_1, column_2) VALUES (2, 1002)'); + await db + .execute('INSERT INTO $table (column_1, column_2) VALUES (2, 1012)'); + await db + .execute('INSERT INTO $table (column_1, column_2) VALUES (3, 1003)'); + + final expectedResult = [ + {'column_1': 1, 'column_2': 1001}, + {'column_1': 2, 'column_2': 1002}, + {'column_1': 2, 'column_2': 1012} + ]; + + // testing with value in the In clause + var result = await db.query(table, + where: 'column_1 IN (1, 2)', orderBy: 'column_1 ASC, column_2 ASC'); + //print(JSON.encode(result)); + expect(result, expectedResult); + + // testing with value as arguments + result = await db.query(table, + where: 'column_1 IN (?, ?)', + whereArgs: ['1', '2'], + orderBy: 'column_1 ASC, column_2 ASC'); + expect(result, expectedResult); + + await db.close(); + }); + + test('Raw escaping', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('raw_escaping_fields.db'); + final db = await openDatabase(path); + + const table = 'table'; + await db.execute('CREATE TABLE "$table" ("group" INTEGER)'); + // inserted in a wrong order to check ASC/DESC + await db.execute('INSERT INTO "$table" ("group") VALUES (1)'); + + final expectedResult = [ + {'group': 1} + ]; + + var result = await db + .rawQuery('SELECT "group" FROM "$table" ORDER BY "group" DESC'); + + print(result); + expect(result, expectedResult); + result = + await db.rawQuery("SELECT * FROM '$table' ORDER BY `group` DESC"); + //print(JSON.encode(result)); + expect(result, expectedResult); + + await db.rawDelete("DELETE FROM '$table'"); + + await db.close(); + }); + + test('Escaping fields', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('escaping_fields.db'); + final db = await openDatabase(path); + + const table = 'group'; + await db.execute('CREATE TABLE "$table" ("group" TEXT)'); + // inserted in a wrong order to check ASC/DESC + + await db.insert(table, {'group': 'group_value'}); + await db.update(table, {'group': 'group_new_value'}, + where: "\"group\" = 'group_value'"); + + final expectedResult = [ + {'group': 'group_new_value'} + ]; + + final result = + await db.query(table, columns: ['group'], orderBy: '"group" DESC'); + //print(JSON.encode(result)); + expect(result, expectedResult); + + await db.delete(table); + + await db.close(); + }); + + test('Functions', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('exp_functions.db'); + final db = await openDatabase(path); + + const table = 'functions'; + await db.execute('CREATE TABLE "$table" (one TEXT, another TEXT)'); + await db.insert(table, {'one': '1', 'another': '2'}); + await db.insert(table, {'one': '1', 'another': '3'}); + await db.insert(table, {'one': '2', 'another': '2'}); + + var result = await db.rawQuery(''' + select one, GROUP_CONCAT(another) as my_col + from $table + GROUP BY one'''); + //print('result :$result'); + expect(result, [ + {'one': '1', 'my_col': '2,3'}, + {'one': '2', 'my_col': '2'} + ]); + + result = await db.rawQuery(''' + select one, GROUP_CONCAT(another) + from $table + GROUP BY one'''); + // print('result :$result'); + expect(result, [ + {'one': '1', 'GROUP_CONCAT(another)': '2,3'}, + {'one': '2', 'GROUP_CONCAT(another)': '2'} + ]); + + // user alias + result = await db.rawQuery(''' + select t.one, GROUP_CONCAT(t.another) + from $table as t + GROUP BY t.one'''); + //print('result :$result'); + expect(result, [ + {'one': '1', 'GROUP_CONCAT(t.another)': '2,3'}, + {'one': '2', 'GROUP_CONCAT(t.another)': '2'} + ]); + + await db.close(); + }); + + test('Alias', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('exp_alias.db'); + final db = await openDatabase(path); + + try { + const table = 'alias'; + await db.execute( + 'CREATE TABLE $table (column_1 INTEGER, column_2 INTEGER)'); + await db.insert(table, {'column_1': 1, 'column_2': 2}); + + final result = await db.rawQuery(''' + select t.column_1, t.column_1 as "t.column1", column_1 as column_alias_1, column_2 + from $table as t'''); + print('result :$result'); + expect(result, [ + {'t.column1': 1, 'column_1': 1, 'column_alias_1': 1, 'column_2': 2} + ]); + } finally { + await db.close(); + } + }); + + test('Dart2 query', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('exp_dart2_query.db'); + final db = await openDatabase(path); + + try { + const table = 'test'; + await db.execute( + 'CREATE TABLE $table (column_1 INTEGER, column_2 INTEGER)'); + await db.insert(table, {'column_1': 1, 'column_2': 2}); + + final result = await db.rawQuery(''' + select column_1, column_2 + from $table as t + '''); + print('result: $result'); + // test output types + print('result.first: ${result.first}'); + final first = result.first; + print('result.first.keys: ${first.keys}'); + var keys = result.first.keys; + var values = result.first.values; + verify(keys.first == 'column_1' || keys.first == 'column_2'); + verify(values.first == 1 || values.first == 2); + print('result.last.keys: ${result.last.keys}'); + keys = result.last.keys; + values = result.last.values; + verify(keys.last == 'column_1' || keys.last == 'column_2'); + verify(values.last == 1 || values.last == 2); + } finally { + await db.close(); + } + }); + /* + + Save code that modify a map from a result - unused + var rawResult = await rawQuery(builder.sql, builder.arguments); + + // Super slow if we escape a name, please avoid it + // This won't be called if no keywords were used + if (builder.hasEscape) { + for (Map map in rawResult) { + var keys = new Set(); + + for (String key in map.keys) { + if (isEscapedName(key)) { + keys.add(key); + } + } + if (keys.isNotEmpty) { + for (var key in keys) { + var value = map[key]; + map.remove(key); + map[unescapeName(key)] = value; + } + } + } + } + return rawResult; + */ + test('Issue#48', () async { + // Sqflite.devSetDebugModeOn(true); + // devPrint('issue #48'); + // Try to query on a non-indexed field + final path = await initDeleteDb('exp_issue_48.db'); + final db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE npa (id INT, title TEXT, identifier TEXT)'); + await db.insert( + 'npa', {'id': 128, 'title': 'title 1', 'identifier': '0001'}); + await db.insert('npa', + {'id': 215, 'title': 'title 1', 'identifier': '0008120150514'}); + }); + var resultSet = await db.query('npa', + columns: ['id', 'title', 'identifier'], + where: '"identifier" = ?', + whereArgs: ['0008120150514']); + // print(resultSet); + expect(resultSet.length, 1); + // but the results is always - empty QueryResultSet[]. + // If i'm trying to do the same with the id field and integer value like + resultSet = await db.query('npa', + columns: ['id', 'title', 'identifier'], + where: '"id" = ?', + whereArgs: [215]); + // print(resultSet); + expect(resultSet.length, 1); + await db.close(); + }); + + test('Issue#52', () async { + // Sqflite.devSetDebugModeOn(true); + // Try to insert string with quote + final path = await initDeleteDb('exp_issue_52.db'); + final db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db.execute('CREATE TABLE test (id INT, value TEXT)'); + await db.insert('test', {'id': 1, 'value': 'without quote'}); + await db.insert('test', {'id': 2, 'value': 'with " quote'}); + }); + var resultSet = await db + .query('test', where: 'value = ?', whereArgs: ['with " quote']); + expect(resultSet.length, 1); + expect(resultSet.first['id'], 2); + + resultSet = await db + .rawQuery('SELECT * FROM test WHERE value = ?', ['with " quote']); + expect(resultSet.length, 1); + expect(resultSet.first['id'], 2); + await db.close(); + }); + + test('Issue#64', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('issue_64.db'); + + // delete existing if any + await deleteDatabase(path); + + // Copy from asset + final data = await rootBundle.load(join('assets', 'issue_64.db')); + final bytes = + data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + await writeFileAsBytes(path, bytes); + + // open the database + final db = await openDatabase(path); + + var result = await db.query('recordings', + columns: ['id', 'content', 'file', 'speaker', 'reference']); + print('result1: $result'); + expect(result.length, 2); + + // This one does not work + // to investigate + result = await db.query('recordings', + columns: ['id', 'content', 'file', 'speaker', 'reference'], + where: 'speaker = ?', + whereArgs: [1]); + + print('result2: $result'); + expect(result.length, 2); + + result = await db.query( + 'recordings', + columns: ['id', 'content', 'file', 'speaker', 'reference'], + where: 'speaker = 1', + ); + print('result3: $result'); + expect(result.length, 2); + + await db.close(); + }); + + test('sql dump file', () async { + // await Sqflite.devSetDebugModeOn(true); + + // try to import an sql dump file (not working) + final path = await initDeleteDb('sql_file.db'); + final db = await openDatabase(path); + try { + const table = 'test'; + const sql = ''' +CREATE TABLE test (value INTEGER); +INSERT INTO test (value) VALUES (1); +INSERT INTO test (value) VALUES (10); +'''; + await db.execute(sql); + // that should be the expected result + // var expectedResult = [ + // {'value': 1}, + // {'value': 10} + // ]; + final result = await db.rawQuery('SELECT * FROM $table'); + print(json.encode(result)); + + // However (at least on Android) + // result is empty, only the first statement is executed + // Ok when using ffi... + if (platform.isLinux) { + // Ok when using ffi linux implementation + // TODO check windows and mac. + // that should be the expected result + var expectedResult = [ + {'value': 1}, + {'value': 10} + ]; + expect(result, expectedResult); + } else { + expect(result, isEmpty); + } + } finally { + await db.close(); + } + }); + + test('Issue#164', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('issue_164.db'); + + final db = await openDatabase(path); + try { + await db.execute(''' +CREATE TABLE test ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + label TEXT NOT NULL, + UNIQUE (label) ON CONFLICT IGNORE +); +'''); + // inserted in a wrong order to check ASC/DESC + var id = await db.rawInsert(''' + INSERT INTO test (label) VALUES(?) + ''', ['label-1']); + expect(id, 1); + + id = await db.rawInsert(''' + INSERT INTO test (label) VALUES(?) + ''', ['label-2']); + expect(id, 2); + + id = await db.rawInsert(''' + INSERT INTO test (label) VALUES(?) + ''', ['label-1']); + expect(id, 0); + } finally { + await db.close(); + } + }); + + test('Defensive mode', () async { + // This shold succeed even on on iOS 14 + final db = await openDatabase(inMemoryDatabasePath); + try { + await db.execute('CREATE TABLE Test(value TEXT)'); + // Workaround for iOS 14 + await db.execute('PRAGMA sqflite -- db_config_defensive_off'); + await db.execute('PRAGMA writable_schema = ON'); + expect( + await db.update( + 'sqlite_master', {'sql': 'CREATE TABLE Test(value BLOB)'}, + where: 'name = \'Test\' and type = \'table\''), + 1); + } finally { + await db.close(); + } + }); + + test('Defensive mode (should fail on iOS 14)', () async { + // This shold fail on iOS 14 + final db = await openDatabase(inMemoryDatabasePath); + try { + await db.execute('CREATE TABLE Test(value TEXT)'); + await db.execute('PRAGMA writable_schema = ON'); + expect( + await db.update( + 'sqlite_master', {'sql': 'CREATE TABLE Test(value BLOB)'}, + where: 'name = \'Test\' and type = \'table\''), + 1); + } finally { + await db.close(); + } + }); + + test('ATTACH database', () async { + final db1Path = await initDeleteDb('attach1.db'); + final db2Path = await initDeleteDb('attach2.db'); + + // Create some data on db1 and close it + var db1 = await databaseFactory.openDatabase(db1Path); + try { + var batch = db1.batch(); + batch.execute('CREATE TABLE table1 (col1 INTEGER)'); + batch.insert('table1', {'col1': 1234}); + await batch.commit(); + } finally { + await db1.close(); + } + + // Open a new db2 database, attach db1 and query it + + var db2 = await databaseFactory.openDatabase(db2Path); + try { + await db2.execute('ATTACH DATABASE \'$db1Path\' AS db1'); + var rows = await db2.query('db1.table1'); + expect(rows, [ + {'col1': 1234} + ]); + } finally { + await db2.close(); + } + }); + + /// fts4 + var fts4Supports = supportsCompatMode; + if (fts4Supports) { + test('Issue#206', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('issue_206.db'); + + final db = await openDatabase(path); + try { + final sqls = LineSplitter.split( + '''CREATE VIRTUAL TABLE Food using fts4(description TEXT) + INSERT Into Food (description) VALUES ('banana') + INSERT Into Food (description) VALUES ('apple')'''); + final batch = db.batch(); + for (var sql in sqls) { + batch.execute(sql); + } + await batch.commit(); + + final results = await db.rawQuery( + 'SELECT description, matchinfo(Food) as matchinfo FROM Food WHERE Food MATCH ?', + ['ban*']); + print(results); + // matchinfo is currently returned as binary bloc + expect(results.length, 1); + final map = results.first; + final matchInfo = map['matchinfo'] as Uint8List; + + // Convert to Uint32List + final uint32ListLength = matchInfo.length ~/ 4; + final uint32List = Uint32List(uint32ListLength); + final data = ByteData.view( + matchInfo.buffer, matchInfo.offsetInBytes, matchInfo.length); + for (var i = 0; i < uint32ListLength; i++) { + uint32List[i] = data.getUint32(i * 4, Endian.host); + } + // print(uint32List); + expect(uint32List, [1, 1, 1, 1, 1]); + expect(map['matchinfo'], const TypeMatcher()); + } finally { + await db.close(); + } + }); + } + + test('Log level', () async { + // test setting log level + Database? db; + try { + // ignore: deprecated_member_use + await databaseFactory.setLogLevel(sqfliteLogLevelVerbose); + //await databaseFactory.setLogLevel(sqfliteLogLevelSql); + db = await openDatabase(inMemoryDatabasePath); + await db.execute('CREATE TABLE test (value TEXT UNIQUE)'); + const table = 'test'; + final map = {'value': 'test'}; + await db.insert(table, map, + conflictAlgorithm: ConflictAlgorithm.replace); + expect( + Sqflite.firstIntValue(await db.query(table, columns: ['COUNT(*)'])), + 1); + } finally { + // ignore: deprecated_member_use + await databaseFactory.setLogLevel(sqfliteLogLevelNone); + await db?.close(); + } + }); + + Future testBigBlog(int size) async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('big_blob.db'); + var db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value BLOB)'); + }); + try { + var blob = + Uint8List.fromList(List.generate(size, (index) => index % 256)); + var id = await db.insert('Test', {'value': blob}); + + /// Get the value field from a given id + Future getValue(int id) async { + return ((await db.query('Test', where: 'id = $id')).first)['value'] + as Uint8List; + } + + expect((await getValue(id)).length, blob.length); + } finally { + await db.close(); + } + } + + // We don't test automatically above as it crashes seriously on Android + test('big blob 800 Ko', () async { + await testBigBlog(800000); + }); + + Future testBigText(int size) async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('big_text.db'); + var db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + try { + var text = List.generate(size, (index) => 'A').join(); + var id = await db.insert('Test', {'value': text}); + + /// Get the value field from a given id + Future getValue(int id) async { + return ((await db.query('Test', where: 'id = $id')).first)['value'] + as String; + } + + expect((await getValue(id)).length, text.length); + } finally { + await db.close(); + } + } + + // We don't test automatically above as it crashes seriously on Android + test('big text 800 Ko', () async { + await testBigText(800000); + }); + /* + test('big blob 1500 Ko (fails on Android sqlite)', () async { + await testBigBlog(1500000); + }); + test('big blob 2 Mo (fails on Android sqlite)', () async { + await testBigBlog(2000000); + }); + test('big blob 15 Mo (fails on Android sqlite)', () async { + await testBigBlog(15000000); + }); + */ + /* + test('Isolate', () async { + // This test does not work yet + // Need background registration. I Kept the code for future reference + await Future.sync(() async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('isolate.db'); + + // Open the db in the main isolate + Database db = + await openDatabase(path, version: 1, onCreate: (db, version) { + db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + }); + try { + await insert(db, 1); + expect(await db.rawQuery('SELECT id, name FROM Test'), [ + {'id': 1, 'name': 'item 1'} + ]); + + // Keep it open and run the isolate + final receivePort = ReceivePort(); + await Isolate.spawn(simpleInsertQueryIsolate, receivePort.sendPort); + + int index = 0; + SendPort sendPort; + List> results; + var completer = Completer(); + var subscription = receivePort.listen((data) { + switch (index++) { + case 0: + // first is the port to send + sendPort = data as SendPort; + // Send path + sendPort.send(path); + break; + case 1: + // second is result + results = data as List>; + completer.complete(); + break; + } + }); + await completer.future; + await subscription?.cancel(); + + print(results); + expect(results, {}); + + // Query again in main isolate + expect(await db.rawQuery('SELECT id, name FROM Test'), {}); + } finally { + await db.close(); + } + }).timeout(Duration(seconds: 3)); + }); + */ + test('missing parameter', () async { + var db = await openDatabase(inMemoryDatabasePath); + await db.execute( + 'CREATE TABLE IF NOT EXISTS foo (id int primary key, name text)'); + var missingParameterShouldFail = !supportsCompatMode; + try { + await db.rawQuery('SELECT * FROM foo WHERE id=?'); + } catch (e) { + expect(missingParameterShouldFail, isTrue); + } + await db.close(); + }); + // Issue https://github.com/tekartik/sqflite/issues/929 + // Pragma has to use rawQuery...why, on sqflite Android + test('wal', () async { + // await Sqflite.devSetDebugModeOn(true); + var db = await openDatabase(inMemoryDatabasePath); + try { + await db.execute('PRAGMA journal_mode=WAL'); + } catch (e) { + print(e); + await db.rawQuery('PRAGMA journal_mode=WAL'); + } + await db.execute('CREATE TABLE test (id INTEGER)'); + await db.insert('test', {'id': 1}); + try { + var resultSet = await db.rawQuery('SELECT id FROM test'); + expect(resultSet, [ + {'id': 1}, + ]); + } finally { + await db.close(); + } + }); + } +} + +/// Insert a record with a given id. +Future insert(Database db, int id) async { + await db.insert('Test', {'id': id, 'name': 'item $id'}); +} + +/// Open, insert and query for isolate testing. +Future simpleInsertQueryIsolate(SendPort sendPort) async { + final receivePort = ReceivePort(); + // First share our receive port + sendPort.send(receivePort.sendPort); + + // Get the path + final path = await receivePort.first as String; + final db = await openDatabase(path, version: 1, onCreate: (db, version) { + db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + }); + List> results; + try { + await insert(db, 2); + results = await db.rawQuery('SELECT id, name FROM Test'); + print(results); + } finally { + await db.close(); + } + + // Done send the result + sendPort.send(results); +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/main.dart b/packages/sqflite/sqflite_aurora/example/lib/main.dart new file mode 100644 index 0000000..01783fe --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/main.dart @@ -0,0 +1,204 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:sqflite_example/batch_test_page.dart'; +import 'package:sqflite_example/deprecated_test_page.dart'; +import 'package:sqflite_example/exception_test_page.dart'; +import 'package:sqflite_example/exp_test_page.dart'; +import 'package:sqflite_example/manual_test_page.dart'; +import 'package:sqflite_example/src/dev_utils.dart'; + +import 'model/main_item.dart'; +import 'open_test_page.dart'; +import 'raw_test_page.dart'; +import 'slow_test_page.dart'; +import 'src/main_item_widget.dart'; +import 'todo_test_page.dart'; +import 'type_test_page.dart'; + +void main() { + mainExampleApp(); +} + +/// Example app main entry point, exported for external application +/// +/// might move to a different shared package. +void mainExampleApp() { + WidgetsFlutterBinding.ensureInitialized(); + // debugAutoStartRouteName = testOpenRoute; + runApp(const SqfliteExampleApp()); +} + +/// Sqflite test app +class SqfliteExampleApp extends StatefulWidget { + /// test app. + const SqfliteExampleApp({Key? key}) : super(key: key); + // This widget is the root of your application. + + @override + // ignore: library_private_types_in_public_api + _SqfliteExampleAppState createState() => _SqfliteExampleAppState(); +} + +/// Simple test page. +const String testRawRoute = '/test/simple'; + +/// Open test page. +const String testOpenRoute = '/test/open'; + +/// Slow test page. +const String testSlowRoute = '/test/slow'; + +/// Type test page. +const String testTypeRoute = '/test/type'; + +/// Batch test page. +const String testBatchRoute = '/test/batch'; + +/// `todo` example test page. +const String testTodoRoute = '/test/todo'; + +/// Exception test page. +const String testExceptionRoute = '/test/exception'; + +/// Manual test page. +const String testManualRoute = '/test/manual'; + +/// Experiment test page. +const String testExpRoute = '/test/exp'; + +/// Deprecated test page. +const String testDeprecatedRoute = '/test/deprecated'; + +class _SqfliteExampleAppState extends State { + var routes = { + '/test': (BuildContext context) => MyHomePage(), + testRawRoute: (BuildContext context) => RawTestPage(), + testOpenRoute: (BuildContext context) => OpenTestPage(), + testSlowRoute: (BuildContext context) => SlowTestPage(), + testTodoRoute: (BuildContext context) => TodoTestPage(), + testTypeRoute: (BuildContext context) => TypeTestPage(), + testManualRoute: (BuildContext context) => const ManualTestPage(), + testBatchRoute: (BuildContext context) => BatchTestPage(), + testExceptionRoute: (BuildContext context) => ExceptionTestPage(), + testExpRoute: (BuildContext context) => ExpTestPage(), + testDeprecatedRoute: (BuildContext context) => DeprecatedTestPage(), + }; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Sqflite 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 press Run > Hot Reload App in IntelliJ). + // Notice that the counter didn't reset back to zero -- the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'Sqflite Demo Home Page'), + routes: routes); + } +} + +/// App home menu page. +class MyHomePage extends StatefulWidget { + /// App home menu page. + MyHomePage({Key? key, this.title}) : super(key: key) { + _items.add( + MainItem('Raw tests', 'Raw SQLite operations', route: testRawRoute)); + _items.add(MainItem('Open tests', 'Open onCreate/onUpgrade/onDowngrade', + route: testOpenRoute)); + _items + .add(MainItem('Type tests', 'Test value types', route: testTypeRoute)); + _items.add(MainItem('Batch tests', 'Test batch operations', + route: testBatchRoute)); + _items.add( + MainItem('Slow tests', 'Lengthy operations', route: testSlowRoute)); + _items.add(MainItem( + 'Todo database example', 'Simple Todo-like database usage example', + route: testTodoRoute)); + _items.add(MainItem('Exp tests', 'Experimental and various tests', + route: testExpRoute)); + _items.add(MainItem('Exception tests', 'Tests that trigger exceptions', + route: testExceptionRoute)); + _items.add(MainItem('Manual tests', 'Tests that requires manual execution', + route: testManualRoute)); + _items.add(MainItem('Deprecated test', + 'Keeping some old tests for deprecated functionalities', + route: testDeprecatedRoute)); + + // Uncomment to view all logs + //Sqflite.devSetDebugModeOn(true); + } + + final List _items = []; + + /// Page title. + final String? title; + + @override + // ignore: library_private_types_in_public_api + _MyHomePageState createState() => _MyHomePageState(); +} + +String? _debugAutoStartRouteName; + +/// (debug) set the route to start with. +String? get debugAutoStartRouteName => _debugAutoStartRouteName; + +/// Deprecated to avoid calls +@Deprecated('Deb only') +set debugAutoStartRouteName(String? routeName) => + _debugAutoStartRouteName = routeName; + +class _MyHomePageState extends State { + int get _itemCount => widget._items.length; + + @override + void initState() { + super.initState(); + + Future.delayed(Duration.zero).then((_) async { + if (mounted) { + // Use it to auto start a test page + if (debugAutoStartRouteName != null) { + // only once + + // await Navigator.of(context).pushNamed(testExpRoute); + // await Navigator.of(context).pushNamed(testRawRoute); + final future = + Navigator.of(context).pushNamed(debugAutoStartRouteName!); + // ignore: deprecated_member_use_from_same_package + debugAutoStartRouteName = null; + await future; + // await Navigator.of(context).pushNamed(testExceptionRoute); + } + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Center( + child: Text('Sqflite demo', textAlign: TextAlign.center)), + ), + body: + ListView.builder(itemBuilder: _itemBuilder, itemCount: _itemCount)); + } + + //new Center(child: new Text('Running on: $_platformVersion\n')), + + Widget _itemBuilder(BuildContext context, int index) { + return MainItemWidget(widget._items[index], (MainItem item) { + Navigator.of(context).pushNamed(item.route!); + }); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/manual_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/manual_test_page.dart new file mode 100644 index 0000000..c602122 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/manual_test_page.dart @@ -0,0 +1,364 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:sqflite/sqflite.dart'; +// ignore: implementation_imports +import 'package:sqflite/src/factory_mixin.dart' as impl; +import 'package:sqflite/utils/utils.dart'; +import 'package:sqflite_example/src/item_widget.dart'; +import 'package:sqflite_example/utils.dart'; + +// ignore_for_file: avoid_print + +import 'model/item.dart'; +import 'src/common_import.dart'; + +/// Manual test page. +class ManualTestPage extends StatefulWidget { + /// Test page. + const ManualTestPage({Key? key}) : super(key: key); + + @override + // ignore: library_private_types_in_public_api + _ManualTestPageState createState() => _ManualTestPageState(); +} + +class _ManualTestPageState extends State { + Database? database; + static const String dbName = 'manual_test.db'; + + Future showToast(String message) async { + ScaffoldMessenger.of(context) + ..clearSnackBars() + ..showSnackBar(SnackBar( + content: Text(message), duration: const Duration(milliseconds: 300))); + } + + Future _openDatabase() async { + return database ??= await databaseFactory.openDatabase(dbName); + } + + Future _closeDatabase() async { + await database?.close(); + database = null; + } + + Future _deleteDatabase() async { + await databaseFactory.deleteDatabase(dbName); + } + + Future _incrementVersion() async { + final version = await database!.getVersion(); + print('version $version'); + await database!.setVersion(version + 1); + } + + late List items; + late List itemWidgets; + + Future pop() async { + return true; + } + + Future _addAndQuery({int? msDelay, bool? noSynchronized}) async { + // await databaseFactory.debugSetLogLevel(sqfliteLogLevelVerbose); + var db = await _openDatabase(); + + // ignore: invalid_use_of_visible_for_testing_member + db.internalsDoNotUseSynchronized = noSynchronized ?? false; + await db.transaction((txn) async { + await txn.execute( + 'CREATE TABLE IF NOT EXISTS Task(id INTEGER PRIMARY KEY, name TEXT)'); + await txn.execute('INSERT INTO Task(name) VALUES (?)', + ['task ${DateTime.now().toIso8601String()}']); + var count = + firstIntValue(await txn.query('Task', columns: [sqlCountColumn])); + unawaited(showToast('$count task(s)')); + if (msDelay != null) { + await Future.delayed(Duration(milliseconds: msDelay)); + } + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + items = [ + SqfMenuItem('SQLite version', () async { + final db = await openDatabase(inMemoryDatabasePath); + + final results = await db.rawQuery('select sqlite_version()'); + print('select sqlite_version(): $results'); + var version = results.first.values.first; + print('sqlite version: $version'); + await db.close(); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('select sqlite_version(): $version'), + )); + } + }, summary: 'select sqlite_version()'), + SqfMenuItem('Factory information', () async { + var info = databaseFactory.toString(); + print('sqlite database factory: $info'); + unawaited(showToast(info)); + }, summary: 'toString()'), + SqfMenuItem('openDatabase', () async { + await _openDatabase(); + }, summary: 'Open the database'), + SqfMenuItem('transaction add and query and pause', () async { + await _addAndQuery(msDelay: 5000); + }, summary: 'open/create table/add/query/pause'), + SqfMenuItem('transaction add and query and pause no synchronized', + () async { + await _addAndQuery(msDelay: 5000, noSynchronized: true); + }, summary: 'open/create table/add/query/pause'), + SqfMenuItem('BEGIN EXCLUSIVE', () async { + final db = await _openDatabase(); + await db.execute('BEGIN EXCLUSIVE'); + }, + summary: + 'Execute than exit or hot-restart the application. Open the database if needed'), + SqfMenuItem('close', () async { + await _closeDatabase(); + }, + summary: + 'Execute after starting then exit the app using the back button on Android and restart from the launcher.'), + SqfMenuItem('delete', () async { + await _deleteDatabase(); + }, + summary: + 'Try open (then optionally) delete, exit or hot-restart then delete then open'), + SqfMenuItem('log level: none', () async { + // ignore: deprecated_member_use + await Sqflite.devSetOptions( + // ignore: deprecated_member_use + SqfliteOptions(logLevel: sqfliteLogLevelNone)); + }, summary: 'No logs'), + SqfMenuItem('log level: sql', () async { + // ignore: deprecated_member_use + await Sqflite.devSetOptions( + // ignore: deprecated_member_use + SqfliteOptions(logLevel: sqfliteLogLevelSql)); + }, summary: 'Log sql command and basic database operation'), + SqfMenuItem('log level: verbose', () async { + // ignore: deprecated_member_use + await Sqflite.devSetOptions( + // ignore: deprecated_member_use + SqfliteOptions(logLevel: sqfliteLogLevelVerbose)); + }, summary: 'Verbose logs, for debugging'), + SqfMenuItem('Get info', () async { + final factory = databaseFactory as impl.SqfliteDatabaseFactoryMixin; + final info = await factory.getDebugInfo(); + print(info.toString()); + }, summary: 'Implementation info (dev only)'), + SqfMenuItem('Increment version', () async { + print(await _incrementVersion()); + }, summary: 'Implementation info (dev only)'), + SqfMenuItem('Multiple db', () async { + await Navigator.of(context).push(MaterialPageRoute(builder: (_) { + return const MultipleDbTestPage(); + })); + }, summary: 'Open multiple databases'), + ...[800000, 1500000, 15000000, 150000000] + .map((size) => SqfMenuItem('Big blob $size', () async { + await testBigBlog(size); + })) + ]; + } + + Future testBigBlog(int size) async { + // await Sqflite.devSetDebugModeOn(true); + var db = await openDatabase(inMemoryDatabasePath, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value BLOB)'); + }); + try { + var blob = + Uint8List.fromList(List.generate(size, (index) => index % 256)); + var id = await db.insert('Test', {'value': blob}); + + /// Get the value field from a given id + Future getValue(int id) async { + return ((await db.query('Test', where: 'id = $id')).first)['value'] + as Uint8List; + } + + var ok = (await getValue(id)).length == blob.length; + if (mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text('$size: $ok'))); + } + } finally { + await db.close(); + } + } + + @override + Widget build(BuildContext context) { + itemWidgets = items + .map((item) => ItemWidget( + item, + (item) async { + final stopwatch = Stopwatch()..start(); + final future = (item as SqfMenuItem).run(); + setState(() {}); + await future; + // always add a small delay + final elapsed = stopwatch.elapsedMilliseconds; + if (elapsed < 300) { + await sleep(300 - elapsed); + } + setState(() {}); + }, + summary: item.summary, + )) + .toList(growable: false); + return Scaffold( + appBar: AppBar( + title: const Text('Manual tests'), + ), + body: WillPopScope( + onWillPop: pop, + child: ListView( + children: itemWidgets, + ), + ), + ); + } +} + +/// Multiple db test page. +class MultipleDbTestPage extends StatelessWidget { + /// Test page. + const MultipleDbTestPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Widget dbTile(String name) { + return ListTile( + title: Text(name), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (_) { + return SimpleDbTestPage( + dbName: name, + ); + })); + }, + ); + } + + return Scaffold( + appBar: AppBar( + title: const Text('Multiple databases'), + ), + body: ListView( + children: [ + dbTile('data1.db'), + dbTile('data2.db'), + dbTile('data3.db') + ], + ), + ); + } +} + +/// Simple db test page. +class SimpleDbTestPage extends StatefulWidget { + /// Simple db test page. + const SimpleDbTestPage({Key? key, required this.dbName}) : super(key: key); + + /// db name. + final String dbName; + + @override + // ignore: library_private_types_in_public_api + _SimpleDbTestPageState createState() => _SimpleDbTestPageState(); +} + +class _SimpleDbTestPageState extends State { + Database? database; + + Future _openDatabase() async { + // await Sqflite.devSetOptions(SqfliteOptions(logLevel: sqfliteLogLevelVerbose)); + return database ??= await databaseFactory.openDatabase(widget.dbName, + options: OpenDatabaseOptions( + version: 1, + onCreate: (db, version) async { + await db.execute('CREATE TABLE Test (value TEXT)'); + })); + } + + Future _closeDatabase() async { + await database?.close(); + database = null; + } + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Simple db ${widget.dbName}'), + ), + body: Builder( + builder: (context) { + Widget menuItem(String title, void Function() onTap, + {String? summary}) { + return ListTile( + title: Text(title), + subtitle: summary == null ? null : Text(summary), + onTap: onTap, + ); + } + + Future countRecord() async { + final db = await _openDatabase(); + final result = + firstIntValue(await db.query('test', columns: ['COUNT(*)'])); + // Temp for nnbd successfull lint + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('$result records'), + duration: const Duration(milliseconds: 700), + )); + } + } + + final items = [ + menuItem('open Database', () async { + await _openDatabase(); + }, summary: 'Open the database'), + menuItem('Add record', () async { + final db = await _openDatabase(); + await db.insert('test', {'value': 'some_value'}); + await countRecord(); + }, summary: 'Add one record. Open the database if needed'), + menuItem('Count record', () async { + await countRecord(); + }, summary: 'Count records. Open the database if needed'), + menuItem( + 'Close Database', + () async { + await _closeDatabase(); + }, + ), + menuItem( + 'Delete database', + () async { + await databaseFactory.deleteDatabase(widget.dbName); + }, + ), + ]; + return ListView( + children: items, + ); + }, + )); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/model/item.dart b/packages/sqflite/sqflite_aurora/example/lib/model/item.dart new file mode 100644 index 0000000..33569db --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/model/item.dart @@ -0,0 +1,56 @@ +import 'package:sqflite_example/src/common_import.dart'; + +/// Item states. +enum ItemState { + /// test not run yet. + none, + + /// test is running. + running, + + /// test succeeded. + success, + + /// test fails. + failure +} + +/// Menu item. +class Item { + /// Menu item. + Item(this.name); + + /// Menu item state. + ItemState state = ItemState.running; + + /// Menu item name/ + String name; +} + +/// Menu item implementation. +class SqfMenuItem extends Item { + /// Menu item implementation. + SqfMenuItem(String name, this.body, {this.summary}) : super(name) { + state = ItemState.none; + } + + /// Summary. + String? summary; + + /// Run the item. + Future run() { + state = ItemState.running; + return Future.delayed(const Duration()).then((_) async { + try { + await body(); + state = ItemState.success; + } catch (e) { + state = ItemState.failure; + rethrow; + } + }); + } + + /// Menu item body. + final FutureOr Function() body; +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/model/main_item.dart b/packages/sqflite/sqflite_aurora/example/lib/model/main_item.dart new file mode 100644 index 0000000..63fee3e --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/model/main_item.dart @@ -0,0 +1,29 @@ +/// Main item. +class MainItem { + /// Main item. + MainItem(this.title, this.description, {this.route}); + + /// Title. + String title; + + /// Description. + String description; + + /// Page route. + String? route; +} + +/// Main route item. +class MainRouteItem { + /// Main route item. + MainRouteItem(this.title, this.description, {this.route}); + + /// Title. + String title; + + /// Description. + String description; + + /// Page route. + MainRouteItem? route; +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/model/test.dart b/packages/sqflite/sqflite_aurora/example/lib/model/test.dart new file mode 100644 index 0000000..fad2fd7 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/model/test.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +/// Test definition. +class Test { + /// Test definition. + Test(this.name, this.fn, {bool? solo, bool? skip}) + : solo = solo == true, + skip = skip == true; + + /// Only run this test. + final bool solo; + + /// Skip this test. + final bool skip; + + /// Test name. + String name; + + /// Test body. + FutureOr Function() fn; +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/open_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/open_test_page.dart new file mode 100644 index 0000000..ed353da --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/open_test_page.dart @@ -0,0 +1,955 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:flutter/services.dart'; +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite/src/database_mixin.dart' // ignore: implementation_imports + show + SqfliteDatabaseMixin; +import 'package:sqflite/src/factory_mixin.dart' // ignore: implementation_imports + show + SqfliteDatabaseFactoryMixin; +import 'package:sqflite_example/utils.dart'; +import 'package:synchronized/synchronized.dart'; + +import 'src/common_import.dart'; +import 'test_page.dart'; +// ignore_for_file: avoid_slow_async_io +// ignore_for_file: avoid_print + +/// Open callbacks. +class OpenCallbacks { + /// Open callbacks. + OpenCallbacks() { + onConfigure = (Database db) { + // devPrint('onConfigure'); + //verify(!onConfigureCalled, 'onConfigure must be called once'); + expect(onConfigureCalled, false, + reason: + 'onConfigure already called'); // onConfigure must be called once + onConfigureCalled = true; + }; + + onCreate = (Database db, int version) { + //print('onCreate'); + expect(onConfigureCalled, true, reason: 'onConfigure not called'); + expect(onCreateCalled, false, reason: 'onCreate already called'); + onCreateCalled = true; + }; + + onOpen = (Database db) { + //print('onOpen'); + verify(onConfigureCalled, 'onConfigure must be called before onOpen'); + verify(!onOpenCalled!, 'onOpen already called'); + onOpenCalled = true; + }; + + onUpgrade = (Database db, int oldVersion, int newVersion) { + verify(onConfigureCalled, 'onConfigure not called in onUpgrade'); + verify(!onUpgradeCalled!, 'onUpgradeCalled already called'); + onUpgradeCalled = true; + }; + + onDowngrade = (Database db, int oldVersion, int newVersion) { + verify(onConfigureCalled, 'onConfigure not called'); + verify(!onDowngradeCalled!, 'onDowngrade already called'); + onDowngradeCalled = true; + }; + + reset(); + } + + /// true when onConfigure is called. + bool? onConfigureCalled; + + /// true when onOpen is called. + bool? onOpenCalled; + + /// true when onCreate is called. + bool? onCreateCalled; + + /// true when onDowngrade is called. + bool? onDowngradeCalled; + + /// true when onUpgrade is called. + bool? onUpgradeCalled; + + /// onCreate callback. + late OnDatabaseCreateFn onCreate; + + /// onConfigure callback. + OnDatabaseConfigureFn? onConfigure; + + /// onDowngrade callback. + late OnDatabaseVersionChangeFn onDowngrade; + + /// onUpgrade callback. + late OnDatabaseVersionChangeFn onUpgrade; + + /// onOpen callback. + late OnDatabaseOpenFn onOpen; + + /// reset callbacks called information. + void reset() { + onConfigureCalled = false; + onOpenCalled = false; + onCreateCalled = false; + onDowngradeCalled = false; + onUpgradeCalled = false; + } + + /// open the database. + Future open(String path, {required int version}) async { + reset(); + return await databaseFactory.openDatabase(path, + options: OpenDatabaseOptions( + version: version, + onCreate: onCreate, + onConfigure: onConfigure!, + onDowngrade: onDowngrade, + onUpgrade: onUpgrade, + onOpen: onOpen)); + } +} + +/// Check if a file is a valid database file +/// +/// An empty file is a valid empty sqlite file +Future isDatabase(String path) async { + Database? db; + var isDatabase = false; + try { + db = await openReadOnlyDatabase(path); + await db.getVersion(); + isDatabase = true; + } catch (_) { + } finally { + await db?.close(); + } + return isDatabase; +} + +/// Open test page. +class OpenTestPage extends TestPage { + /// Open test page. + OpenTestPage({Key? key}) : super('Open tests', key: key) { + final factory = databaseFactory; + + test('Databases path', () async { + // await Sqflite.devSetDebugModeOn(false); + final databasesPath = await factory.getDatabasesPath(); + // On Android we know it is current a 'databases' folder in the package folder + print('databasesPath: $databasesPath'); + if (platform.isAndroid) { + expect(basename(databasesPath), 'databases'); + } else if (platform.isIOS) { + expect(basename(databasesPath), 'Documents'); + } + final path = join(databasesPath, 'in_default_directory.db'); + await factory.deleteDatabase(path); + final db = await factory.openDatabase(path); + await db.close(); + }); + test('Delete database', () async { + // await Sqflite.devSetDebugModeOn(false); + final path = await initDeleteDb('delete_database.db'); + expect(await databaseExists(path), false); + final db = await openDatabase(path); + await db.close(); + expect((await pathExists(path)), true); + expect(await databaseExists(path), true); + print('Deleting database $path'); + await deleteDatabase(path); + expect((await pathExists(path)), false); + expect(await databaseExists(path), false); + }); + + test('Open no version', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_no_version.db'); + expect((await pathExists(path)), false); + final db = await openDatabase(path); + verify(await pathExists(path)); + expect(await db.getVersion(), 0); + await db.close(); + }); + + test('isOpen', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('is_open.db'); + expect((await pathExists(path)), false); + final db = await openDatabase(path); + expect(db.isOpen, true); + verify(await pathExists(path)); + await db.close(); + expect(db.isOpen, false); + }); + + test('Open no version onCreate', () async { + // should fail + final path = await initDeleteDb('open_no_version_on_create.db'); + verify(!(await pathExists(path))); + Database? db; + try { + db = await openDatabase(path, onCreate: (Database db, int version) { + // never called + verify(false); + }); + verify(false); + } on ArgumentError catch (_) {} + verify(!await pathExists(path)); + expect(db, null); + }); + + test('Open onCreate', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_test2.db'); + var onCreate = false; + var onCreateTransaction = false; + final db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + expect(version, 1); + onCreate = true; + + await db.transaction((txn) async { + await txn.execute('CREATE TABLE Test2 (id INTEGER PRIMARY KEY)'); + onCreateTransaction = true; + }); + }); + verify(onCreate); + expect(onCreateTransaction, true); + expect(await db.getVersion(), 1); + await db.close(); + }); + + test('Simple onCreate', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_simple_on_create.db'); + expect(await isDatabase(path), isFalse); + + final db = + await openDatabase(path, version: 1, onCreate: (db, version) async { + final batch = db.batch(); + + batch.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, text NAME)'); + await batch.commit(); + }); + try { + expect( + await db.rawInsert('INSERT INTO Test (text) VALUES (?)', ['test']), + 1); + final result = await db.query('Test'); + final expected = [ + {'id': 1, 'text': 'test'} + ]; + expect(result, expected); + + expect(await isDatabase(path), isTrue); + } finally { + await db.close(); + } + expect(await isDatabase(path), isTrue); + }); + + test('Open 2 databases', () async { + //await Sqflite.devSetDebugModeOn(true); + final path1 = await initDeleteDb('open_db_1.db'); + final path2 = await initDeleteDb('open_db_2.db'); + final db1 = await openDatabase(path1, version: 1); + final db2 = await openDatabase(path2, version: 1); + await db1.close(); + await db2.close(); + }); + + test('Open onUpgrade', () async { + // await Sqflite.devSetDebugModeOn(true); + var onUpgrade = false; + final path = await initDeleteDb('open_on_upgrade.db'); + var database = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db.execute('CREATE TABLE Test(id INTEGER PRIMARY KEY)'); + }); + try { + await database + .insert('Test', {'id': 1, 'name': 'test'}); + fail('should fail'); + } on DatabaseException catch (e) { + print(e); + } + expect(await database.getVersion(), 1); + await database.close(); + database = await openDatabase(path, version: 2, + onUpgrade: (Database db, int oldVersion, int newVersion) async { + expect(oldVersion, 1); + expect(newVersion, 2); + await db.execute('ALTER TABLE Test ADD name TEXT'); + onUpgrade = true; + }); + verify(onUpgrade); + expect(await database.getVersion(), 2); + try { + expect( + await database + .insert('Test', {'id': 1, 'name': 'test'}), + 1); + } finally { + await database.close(); + } + }); + + test('Open onDowngrade', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_on_downgrade.db'); + var database = await openDatabase(path, version: 2, + onCreate: (Database db, int version) async { + await db.execute('CREATE TABLE Test(id INTEGER PRIMARY KEY)'); + }, onDowngrade: (Database db, int oldVersion, int newVersion) async { + verify(false, 'should not be called'); + }); + expect(await database.getVersion(), 2); + await database.close(); + + var onDowngrade = false; + database = await openDatabase(path, version: 1, + onDowngrade: (Database db, int oldVersion, int newVersion) async { + expect(oldVersion, 2); + expect(newVersion, 1); + await db.execute('ALTER TABLE Test ADD name TEXT'); + onDowngrade = true; + }); + verify(onDowngrade); + expect(await database.getVersion(), 1); + + await database.close(); + }); + + test('Open bad path', () async { + try { + await openDatabase('/invalid_path'); + fail(); + } on DatabaseException catch (e) { + verify(e.isOpenFailedError()); + } + }); + + test('Open asset database', () async { + // await Sqflite.devSetDebugModeOn(false); + final databasesPath = await getDatabasesPath(); + final path = join(databasesPath, 'asset_example.db'); + + // delete existing if any + await deleteDatabase(path); + + // Make sure the parent directory exists + try { + await createDirectory(path); + } catch (_) {} + + // Copy from asset + final data = await rootBundle.load(join('assets', 'example.db')); + final List bytes = + data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + // Write and flush the bytes written + await writeFileAsBytes(path, bytes, flush: true); + + // open the database + final db = await openDatabase(path); + + // Our database as a single table with a single element + final list = await db.rawQuery('SELECT * FROM Test'); + print('list $list'); + expect(list.first['name'], 'simple value'); + + await db.close(); + }); + + test('Open on configure', () async { + final path = await initDeleteDb('open_on_configure.db'); + + var onConfigured = false; + var onConfiguredTransaction = false; + Future onConfigure(Database db) async { + onConfigured = true; + await db.execute('CREATE TABLE Test1 (id INTEGER PRIMARY KEY)'); + await db.transaction((txn) async { + await txn.execute('CREATE TABLE Test2 (id INTEGER PRIMARY KEY)'); + onConfiguredTransaction = true; + }); + } + + final db = await openDatabase(path, onConfigure: onConfigure); + expect(onConfigured, true); + expect(onConfiguredTransaction, true); + + await db.close(); + }); + + test('Open onDowngrade delete', () async { + // await Sqflite.devSetDebugModeOn(false); + + final path = await initDeleteDb('open_on_downgrade_delete.db'); + var database = await openDatabase(path, version: 3, + onCreate: (Database db, int version) async { + await db.execute('CREATE TABLE Test(id INTEGER PRIMARY KEY)'); + }); + await database.close(); + + // should fail going back in versions + var onCreated = false; + var onOpened = false; + var onConfiguredOnce = false; // onConfigure will be called twice here + // since the database is re-opened + var onConfigured = false; + database = + await openDatabase(path, version: 2, onConfigure: (Database db) { + // Must not be configured nor created yet + verify(!onConfigured); + verify(!onCreated); + if (!onConfiguredOnce) { + // first time + onConfiguredOnce = true; + } else { + onConfigured = true; + } + }, onCreate: (Database db, int version) { + verify(onConfigured); + verify(!onCreated); + verify(!onOpened); + onCreated = true; + expect(version, 2); + }, onOpen: (Database db) { + verify(onCreated); + onOpened = true; + }, onDowngrade: onDatabaseDowngradeDelete); + await database.close(); + + expect(onCreated, true); + expect(onOpened, true); + expect(onConfigured, true); + + onCreated = false; + onOpened = false; + + database = await openDatabase(path, version: 2, + onCreate: (Database db, int version) { + expect(false, 'should not be called'); + }, onOpen: (Database db) { + onOpened = true; + }, onDowngrade: onDatabaseDowngradeDelete); + expect(onOpened, true); + await database.close(); + }); + + test('All open callback', () async { + // await Sqflite.devSetDebugModeOn(false); + final path = await initDeleteDb('open_all_callbacks.db'); + + var step = 1; + final openCallbacks = OpenCallbacks(); + var db = await openCallbacks.open(path, version: 1); + try { + verify(openCallbacks.onConfigureCalled, 'onConfiguredCalled $step'); + verify(openCallbacks.onCreateCalled, 'onCreateCalled $step'); + verify(openCallbacks.onOpenCalled, 'onOpenCalled $step'); + verify(!openCallbacks.onUpgradeCalled!, 'onUpgradeCalled $step'); + verify(!openCallbacks.onDowngradeCalled!, 'onDowngradeCalled $step'); + await db.close(); + + ++step; + db = await openCallbacks.open(path, version: 3); + verify(openCallbacks.onConfigureCalled, 'onConfiguredCalled $step'); + verify(!openCallbacks.onCreateCalled!, 'onCreateCalled $step'); + verify(openCallbacks.onOpenCalled, 'onOpenCalled $step'); + verify(openCallbacks.onUpgradeCalled, 'onUpgradeCalled $step'); + verify(!openCallbacks.onDowngradeCalled!, 'onDowngradeCalled $step'); + await db.close(); + + ++step; + // devPrint('downgrading'); + db = await openCallbacks.open(path, version: 2); + verify(openCallbacks.onConfigureCalled, 'onConfiguredCalled $step'); + verify(!openCallbacks.onCreateCalled!, 'onCreateCalled $step'); + verify(openCallbacks.onOpenCalled, 'onOpenCalled $step'); + verify(!openCallbacks.onUpgradeCalled!, 'onDowngradeCalled $step'); + verify(openCallbacks.onDowngradeCalled, 'onDowngradCalled $step'); + await db.close(); + // devPrint('downgrading delete'); + openCallbacks.onDowngrade = onDatabaseDowngradeDelete; + var configureCount = 0; + final callback = openCallbacks.onConfigure; + // allow being called twice + openCallbacks.onConfigure = (Database db) { + if (configureCount == 1) { + openCallbacks.onConfigureCalled = false; + } + configureCount++; + callback!(db); + }; + ++step; + db = await openCallbacks.open(path, version: 1); + + /* + verify(openCallbacks.onConfigureCalled,'onConfiguredCalled $step'); + verify(configureCount == 2, 'onConfigure count'); + verify(openCallbacks.onCreateCalled, 'onCreateCalled $step'); + verify(openCallbacks.onOpenCalled, 'onOpenCalled $step'); + verify(!openCallbacks.onUpgradeCalled, 'onUpgradeCalled $step'); + verify(!openCallbacks.onDowngradeCalled, 'onDowngradCalled $step'); + */ + } finally { + await db.close(); + } + }); + + test('Open batch', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_batch.db'); + + Future onConfigure(Database db) async { + final batch = db.batch(); + batch.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'); + await batch.commit(); + } + + Future onCreate(Database db, int version) async { + final batch = db.batch(); + // await db.execute('INSERT INTO Test(value) VALUES("value1")'); This does not work using ffi! + batch.execute('INSERT INTO Test(value) VALUES(?)', ['value1']); + await batch.commit(); + } + + Future onOpen(Database db) async { + final batch = db.batch(); + //batch.rawInsert('INSERT INTO Test(value) VALUES("value2")'); + batch.rawInsert('INSERT INTO Test(value) VALUES(?)', ['value2']); + await batch.commit(); + } + + final db = await openDatabase(path, + version: 1, + onConfigure: onConfigure, + onCreate: onCreate, + onOpen: onOpen); + expect( + Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM Test')), + 2); + + await db.close(); + }); + + test('Open read-only', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_read_only.db'); + + Future onCreate(Database db, int version) async { + final batch = db.batch(); + batch.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'); + //batch.rawInsert('INSERT INTO Test(value) VALUES("value1")'); This does not work using ffi + batch.rawInsert('INSERT INTO Test(value) VALUES(?)', ['value1']); + await batch.commit(); + } + + var db = await openDatabase(path, version: 1, onCreate: onCreate); + expect( + Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM Test')), + 1); + + await db.close(); + + db = await openReadOnlyDatabase(path); + expect( + Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM Test')), + 1); + + try { + await db.rawInsert('INSERT INTO Test(value) VALUES(?)', ['value1']); + fail('should fail'); + } on DatabaseException catch (e) { + // Error DatabaseException(attempt to write a readonly database (code 8)) running Open read-only + expect(e.isReadOnlyError(), true); + } + + final batch = db.batch(); + batch.rawQuery('SELECT COUNT(*) FROM Test'); + await batch.commit(); + + await db.close(); + }); + + test('Open demo (doc)', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_read_only.db'); + + { + Future onConfigure(Database db) async { + // Add support for cascade delete + await db.execute('PRAGMA foreign_keys = ON'); + } + + final db = await openDatabase(path, onConfigure: onConfigure); + await db.close(); + } + + { + Future onCreate(Database db, int version) async { + // Database is created, delete the table + await db.execute( + 'CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'); + } + + Future onUpgrade(Database db, int oldVersion, int newVersion) async { + // Database version is updated, alter the table + await db.execute('ALTER TABLE Test ADD name TEXT'); + } + + // Special callback used for onDowngrade here to recreate the database + final db = await openDatabase(path, + version: 1, + onCreate: onCreate, + onUpgrade: onUpgrade, + onDowngrade: onDatabaseDowngradeDelete); + await db.close(); + } + + { + Future onOpen(Database db) async { + // Database is open, print its version + print('db version ${await db.getVersion()}'); + } + + final db = await openDatabase( + path, + onOpen: onOpen, + ); + await db.close(); + } + + // asset (use existing copy if any + { + // Check if we have an existing copy first + final databasesPath = await getDatabasesPath(); + final path = join(databasesPath, 'demo_asset_example.db'); + + // try opening (will work if it exists) + Database? db; + try { + db = await openDatabase(path, readOnly: true); + } catch (e) { + print('Error $e'); + } + + if (db == null) { + // Should happen only the first time you launch your application + print('Creating new copy from asset'); + + // Copy from asset + final data = await rootBundle.load(join('assets', 'example.db')); + final bytes = + data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + await writeFileAsBytes(path, bytes); + + // open the database + db = await openDatabase(path, readOnly: true); + } else { + print('Opening existing database'); + } + + await db.close(); + } + }); + + test('Database locked (doc)', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_locked.db'); + final helper = Helper(path); + + // without the synchronized fix, this could faild + for (var i = 0; i < 100; i++) { + // ignore: unawaited_futures + helper.getDb(); + } + final db = await helper.getDb(); + await db.close(); + }); + + test('single/multi instance (using factory)', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('instances_test.db'); + Database? db1, db2, db3; + try { + db1 = await databaseFactory.openDatabase(path, + options: OpenDatabaseOptions(singleInstance: false)); + db2 = await databaseFactory.openDatabase(path, + options: OpenDatabaseOptions(singleInstance: true)); + db3 = await databaseFactory.openDatabase(path, + options: OpenDatabaseOptions(singleInstance: true)); + expect(db1, isNot(db2)); + expect(db2, db3); + } finally { + await db1!.close(); + await db2!.close(); + await db3!.close(); // safe to close the same instance + } + }); + + test('single/multi instance', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('instances_test.db'); + final db1 = await openDatabase(path, singleInstance: false); + final db2 = await openDatabase(path, singleInstance: true); + final db3 = await openDatabase(path, singleInstance: true); + verify(db1 != db2); + verify(db2 == db3); + await db1.close(); + await db2.close(); + await db3.close(); // safe to close the same instance + }); + + test('In memory database', () async { + const inMemoryPath = + inMemoryDatabasePath; // tried null without success, as it crashes on Android + const path = inMemoryPath; + + var db = await openDatabase(path); + await db + .execute('CREATE TABLE IF NOT EXISTS Test(id INTEGER PRIMARY KEY)'); + await db.insert('Test', {'id': 1}); + expect(await db.query('Test'), [ + {'id': 1} + ]); + await db.close(); + + // reopen, content should be gone + db = await openDatabase(path); + try { + await db.query('Test'); + fail('fail'); + } on DatabaseException catch (e) { + print(e); + } + await db.close(); + }); + + test('Not in memory database', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('not_in_memory.db'); + + var db = await openDatabase(path); + await db + .execute('CREATE TABLE IF NOT EXISTS Test(id INTEGER PRIMARY KEY)'); + await db.insert('Test', {'id': 1}); + expect(await db.query('Test'), [ + {'id': 1} + ]); + await db.close(); + + // reopen, content should be done + db = await openDatabase(path); + expect(await db.query('Test'), [ + {'id': 1} + ]); + await db.close(); + }); + + test('open in sub directory', () async { + final databasesPath = await factory.getDatabasesPath(); + final path = join(databasesPath, 'sub_that_should_not_exists'); + try { + await deleteDirectory(path); + } catch (_) {} + final dbPath = join(path, 'open.db'); + final db = await factory.openDatabase(dbPath); + try {} finally { + await db.close(); + } + }); + + test('open in sub sub directory', () async { + // await Sqflite.devSetDebugModeOn(true); + final databasesPath = await factory.getDatabasesPath(); + final path = + join(databasesPath, 'sub2_that_should_not_exists', 'sub_sub'); + try { + await deleteDirectory(path); + } catch (_) {} + expect(await existsDirectory(path), false); + final dbPath = join(path, 'open.db'); + final db = await factory.openDatabase(dbPath); + try {} finally { + await db.close(); + } + }); + + test('open_close_open_no_wait', () async { + // await Sqflite.devSetDebugModeOn(true); + const path = 'open_close_open_no_wait.db'; + final factory = databaseFactory; + await factory.deleteDatabase(path); + final db = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + try { + expect(await db.getVersion(), 1); + // close no wait + unawaited(db.close()); + final db2 = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + print('$db, $db2'); + verify(db != db2); + verify((db as SqfliteDatabaseMixin).id != + (db2 as SqfliteDatabaseMixin).id); + expect(await db2.getVersion(), 1); + } finally { + await db.close(); + } + }); + test('close in transaction', () async { + // await Sqflite.devSetDebugModeOn(true); + const path = 'test_close_in_transaction.db'; + final factory = databaseFactory; + await factory.deleteDatabase(path); + var db = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + try { + //await db.getVersion(); + await db.execute('BEGIN IMMEDIATE'); + await db.close(); + + db = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + } finally { + await db.close(); + } + }); + + test('Open in transaction', () async { + // await Sqflite.devSetDebugModeOn(true); + const path = 'test_open_in_transaction.db'; + final factory = databaseFactory; + await factory.deleteDatabase(path); + var db = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + try { + //await db.getVersion(); + await db.execute('BEGIN IMMEDIATE'); + // Trick to make sure we don't reuse the same instance during open + (factory as SqfliteDatabaseFactoryMixin) + .databaseOpenHelpers + .remove(db.path); + + final db2 = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + print('after open'); + verify(db != db2); + expect( + (db as SqfliteDatabaseMixin).id, (db2 as SqfliteDatabaseMixin).id); + //await db.getVersion(); + //await db.execute('ROLLBACK'); + + db = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + expect(db, db2); + } finally { + await db.close(); + } + }); + + test('Open non sqlite file', () async { + // Kind of corruption simulation + // await Sqflite.devSetDebugModeOn(true); + final factory = databaseFactory; + final path = + join(await factory.getDatabasesPath(), 'test_non_sqlite_file.db'); + + await factory.deleteDatabase(path); + // Write dummy content + await writeFileAsString(path, 'dummy', flush: true); + // check content + expect(await readFileAsString(path), 'dummy'); + + // try read-only + { + late Database db; + try { + db = await factory.openDatabase(path, + options: OpenDatabaseOptions(readOnly: true)); + } catch (e) { + print('open error'); + } + try { + await db.getVersion(); + } catch (e) { + print('getVersion error'); + } + await db.close(); + + // check content + expect(await readFileAsString(path), 'dummy'); + } + + expect(await isDatabase(path), isFalse); + // try read-write + const minExpectedSize = 1000; + expect((await readFileAsBytes(path)).length, lessThan(minExpectedSize)); + + var db = await factory.openDatabase(path); + var versionShouldFail = + !supportsCompatMode || platform.isIOS || platform.isMacOS; + if (versionShouldFail) { + // On iOS it fails + try { + await db.getVersion(); + } catch (e) { + print('getVersion error'); + } + } else { + // On Android the database is re-created + await db.getVersion(); + } + await db.close(); + + if (versionShouldFail) { + // On iOS it fails + try { + db = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + } catch (e) { + print('getVersion error'); + } + } else { + db = await factory.openDatabase(path, + options: OpenDatabaseOptions(version: 1)); + // On Android the database is re-created + await db.getVersion(); + } + await db.close(); + + if (platform.isAndroid) { + // Content has changed, it is a big file now! + expect( + (await readFileAsBytes(path)).length, greaterThan(minExpectedSize)); + } + }); + } +} + +/// Open helper. +class Helper { + /// Open helper. + Helper(this.path); + + /// Datebase path. + final String path; + Database? _db; + final _lock = Lock(); + + /// Open the database if not done. + Future getDb() async { + if (_db == null) { + await _lock.synchronized(() async { + // Check again once entering the synchronized block + _db ??= await openDatabase(path); + }); + } + return _db!; + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/raw_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/raw_test_page.dart new file mode 100644 index 0000000..2f146b4 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/raw_test_page.dart @@ -0,0 +1,667 @@ +import 'dart:io' as io; + +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite/utils/utils.dart'; +import 'package:sqflite_example/utils.dart'; + +import 'src/common_import.dart'; +import 'test_page.dart'; + +// ignore_for_file: avoid_print + +/// Raw test page. +class RawTestPage extends TestPage { + /// Raw test page. + RawTestPage({Key? key}) : super('Raw tests', key: key) { + test('Simple', () async { + // await Sqflite.devSetDebugModeOn(true); + + final path = await initDeleteDb('raw_simple.db'); + final db = await openDatabase(path); + try { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + expect( + await db.rawInsert('INSERT INTO Test (name) VALUES (?)', ['test']), + 1); + + final result = await db.query('Test'); + final expected = [ + {'id': 1, 'name': 'test'} + ]; + expect(result, expected); + } finally { + await db.close(); + } + }); + + test('Sqlite version', () async { + final db = await openDatabase(inMemoryDatabasePath); + final results = await db.rawQuery('select sqlite_version()'); + print('sqlite version: ${results.first.values.first}'); + await db.close(); + }); + + test('Options', () async { + // Sqflite.devSetDebugModeOn(true); + + final path = await initDeleteDb('raw_query_format.db'); + final db = await openDatabase(path); + try { + final batch = db.batch(); + + batch.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item 1']); + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item 2']); + await batch.commit(); + + var sql = 'SELECT id, name FROM Test'; + // ignore: deprecated_member_use + var resultSet = await db.devInvokeSqlMethod('query', sql); + var expectedResultSetMap = { + 'columns': ['id', 'name'], + 'rows': [ + [1, 'item 1'], + [2, 'item 2'] + ] + }; + print('result as r/c $resultSet'); + expect(resultSet, expectedResultSetMap); + + // empty + sql = 'SELECT id, name FROM Test WHERE id=1234'; + // ignore: deprecated_member_use + resultSet = await db.devInvokeSqlMethod('query', sql); + expectedResultSetMap = {}; + print('result as r/c $resultSet'); + try { + // This might be just for compatibility + expect(resultSet, expectedResultSetMap); + } catch (e) { + // Allow empty result + expectedResultSetMap = { + 'columns': ['id', 'name'], + 'rows': [] + }; + expect(resultSet, expectedResultSetMap); + } + } finally { + await db.close(); + } + }); + + test('Transaction', () async { + //Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('simple_transaction.db'); + final db = await openDatabase(path); + try { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + Future testItem(int i) async { + await db.transaction((txn) async { + final count = Sqflite.firstIntValue( + await txn.rawQuery('SELECT COUNT(*) FROM Test'))!; + await Future.delayed(const Duration(milliseconds: 40)); + await txn + .rawInsert('INSERT INTO Test (name) VALUES (?)', ['item $i']); + //print(await db.query('SELECT COUNT(*) FROM Test')); + final afterCount = Sqflite.firstIntValue( + await txn.rawQuery('SELECT COUNT(*) FROM Test')); + expect(count + 1, afterCount); + }); + } + + final futures = []; + for (var i = 0; i < 4; i++) { + futures.add(testItem(i)); + } + await Future.wait(futures); + } finally { + await db.close(); + } + }); + + test('Concurrency 1', () async { + // Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('simple_concurrency_1.db'); + final db = await openDatabase(path); + try { + final step1 = Completer(); + final step2 = Completer(); + final step3 = Completer(); + + Future action1() async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + step1.complete(); + + await step2.future; + try { + await db + .rawQuery('SELECT COUNT(*) FROM Test') + .timeout(const Duration(seconds: 1)); + throw 'should fail'; + } catch (e) { + expect(e is TimeoutException, true); + } + + step3.complete(); + } + + Future action2() async { + await db.transaction((txn) async { + // Wait for table being created; + await step1.future; + await txn + .rawInsert('INSERT INTO Test (name) VALUES (?)', ['item 1']); + step2.complete(); + + await step3.future; + + final count = Sqflite.firstIntValue( + await txn.rawQuery('SELECT COUNT(*) FROM Test')); + expect(count, 1); + }); + } + + final future1 = action1(); + final future2 = action2(); + + await Future.wait([future1, future2]); + + final count = Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM Test')); + expect(count, 1); + } finally { + await db.close(); + } + }); + + test('Concurrency 2', () async { + // Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('simple_concurrency_2.db'); + final db = await openDatabase(path); + try { + final step1 = Completer(); + final step2 = Completer(); + final step3 = Completer(); + + Future action1() async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + step1.complete(); + + await step2.future; + try { + await db + .rawQuery('SELECT COUNT(*) FROM Test') + .timeout(const Duration(seconds: 1)); + throw 'should fail'; + } catch (e) { + expect(e is TimeoutException, true); + } + + step3.complete(); + } + + Future action2() async { + // This is the change from concurrency 1 + // Wait for table being created; + await step1.future; + + await db.transaction((txn) async { + // Wait for table being created; + await txn + .rawInsert('INSERT INTO Test (name) VALUES (?)', ['item 1']); + step2.complete(); + + await step3.future; + + final count = Sqflite.firstIntValue( + await txn.rawQuery('SELECT COUNT(*) FROM Test')); + expect(count, 1); + }); + } + + final future1 = action1(); + final future2 = action2(); + + await Future.wait([future1, future2]); + + final count = Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM Test')); + expect(count, 1); + } finally { + await db.close(); + } + }); + + test('Transaction recursive', () async { + final path = await initDeleteDb('transaction_recursive.db'); + final db = await openDatabase(path); + try { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + // insert then fails to make sure the transaction is cancelled + await db.transaction((txn) async { + await txn.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item 1']); + + await txn.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item 2']); + }); + final afterCount = Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM Test')); + expect(afterCount, 2); + } finally { + await db.close(); + } + }); + + test('Transaction open twice', () async { + //Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('transaction_open_twice.db'); + final db = await openDatabase(path); + Database? db2; + try { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + db2 = await openDatabase(path); + + await db.transaction((txn) async { + await txn.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item']); + final afterCount = Sqflite.firstIntValue( + await txn.rawQuery('SELECT COUNT(*) FROM Test')); + expect(afterCount, 1); + + /* + // this is not working on Android + int db2AfterCount = + Sqflite.firstIntValue(await db2.rawQuery('SELECT COUNT(*) FROM Test')); + assert(db2AfterCount == 0); + */ + }); + final db2AfterCount = Sqflite.firstIntValue( + await db2.rawQuery('SELECT COUNT(*) FROM Test')); + expect(db2AfterCount, 1); + + final afterCount = Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM Test')); + expect(afterCount, 1); + } finally { + await db.close(); + await db2?.close(); + } + }); + + if (supportsCompatMode) { + test('Debug mode (log)', () async { + //await Sqflite.devSetDebugModeOn(false); + final path = await initDeleteDb('debug_mode.db'); + final db = await openDatabase(path); + try { + final debugModeOn = await Sqflite.getDebugModeOn(); + await Sqflite.setDebugModeOn(true); + await db.setVersion(1); + await Sqflite.setDebugModeOn(false); + // this message should not appear + await db.setVersion(2); + await Sqflite.setDebugModeOn(true); + await db.setVersion(3); + // restore + await Sqflite.setDebugModeOn(debugModeOn); + } finally { + await db.close(); + } + }); + } + + test('Demo', () async { + // await Sqflite.devSetDebugModeOn(); + final path = await initDeleteDb('simple_demo.db'); + final database = await openDatabase(path); + try { + //int version = await database.update('PRAGMA user_version'); + //print('version: ${await database.update('PRAGMA user_version')}'); + print('version: ${await database.rawQuery('PRAGMA user_version')}'); + + //print('drop: ${await database.update('DROP TABLE IF EXISTS Test')}'); + await database.execute('DROP TABLE IF EXISTS Test'); + + print('dropped'); + await database.execute( + 'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)'); + print('table created'); + var id = await database.rawInsert( + // This does not work using ffi + // 'INSERT INTO Test(name, value, num) VALUES("some name",1234,?)', + // [456.789]); + 'INSERT INTO Test(name, value, num) VALUES(?,1234,?)', + ['some name', 456.789]); + print('inserted1: $id'); + id = await database.rawInsert( + 'INSERT INTO Test(name, value) VALUES(?, ?)', + ['another name', 12345678]); + print('inserted2: $id'); + var count = await database.rawUpdate( + 'UPDATE Test SET name = ?, value = ? WHERE name = ?', + ['updated name', '9876', 'some name']); + print('updated: $count'); + expect(count, 1); + var list = await database.rawQuery('SELECT * FROM Test'); + var expectedList = [ + {'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789}, + {'name': 'another name', 'id': 2, 'value': 12345678, 'num': null} + ]; + + print('list: ${json.encode(list)}'); + print('expected $expectedList'); + expect(list, expectedList); + + count = await database + .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']); + print('deleted: $count'); + expect(count, 1); + list = await database.rawQuery('SELECT * FROM Test'); + expectedList = [ + {'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789}, + ]; + + print('list: ${json.encode(list)}'); + print('expected $expectedList'); + expect(list, expectedList); + } finally { + await database.close(); + } + }); + + test('Demo clean', () async { + // Get a location + final databasesPath = await getDatabasesPath(); + + // Make sure the directory exists + try { + if (!kIsWeb) { + // ignore: avoid_slow_async_io + if (!await io.Directory(databasesPath).exists()) { + await io.Directory(databasesPath).create(recursive: true); + } + } + } catch (_) {} + + final path = join(databasesPath, 'demo.db'); + + // Delete the database + await deleteDatabase(path); + + // open the database + final database = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + // When creating the db, create the table + await db.execute( + 'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)'); + }); + + // Insert some records in a transaction + await database.transaction((txn) async { + final id1 = await txn.rawInsert( + // 'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)'); This does not work using ffi + 'INSERT INTO Test(name, value, num) VALUES(?, 1234, 456.789)', + ['some name']); + print('inserted1: $id1'); + final id2 = await txn.rawInsert( + 'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)', + ['another name', 12345678, 3.1416]); + print('inserted2: $id2'); + }); + + // Update some record + var count = await database.rawUpdate( + 'UPDATE Test SET name = ?, value = ? WHERE name = ?', + ['updated name', '9876', 'some name']); + print('updated: $count'); + + // Get the records + final list = await database.rawQuery('SELECT * FROM Test'); + final expectedList = [ + {'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789}, + {'name': 'another name', 'id': 2, 'value': 12345678, 'num': 3.1416} + ]; + print(list); + print(expectedList); + //assert(const DeepCollectionEquality().equals(list, expectedList)); + expect(list, expectedList); + + // Count the records + count = (Sqflite.firstIntValue( + await database.rawQuery('SELECT COUNT(*) FROM Test')))!; + expect(count, 2); + + // Delete a record + count = await database + .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']); + expect(count, 1); + + // Close the database + await database.close(); + }); + + test('Open twice', () async { + // Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('open_twice.db'); + final db = await openDatabase(path); + Database? db2; + try { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + db2 = await openReadOnlyDatabase(path); + + final count = Sqflite.firstIntValue( + await db2.rawQuery('SELECT COUNT(*) FROM Test')); + expect(count, 0); + } finally { + await db.close(); + await db2?.close(); + } + }); + + test('text primary key', () async { + // Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('text_primary_key.db'); + final db = await openDatabase(path); + try { + // This table has no primary key however sqlite generates an hidden row id + await db.execute('CREATE TABLE Test (name TEXT PRIMARY KEY)'); + var id = await db.insert('Test', {'name': 'test'}); + expect(id, 1); + id = await db.insert('Test', {'name': 'other'}); + expect(id, 2); + // row id is not retrieve by default + var list = await db.query('Test'); + expect(list, [ + {'name': 'test'}, + {'name': 'other'} + ]); + list = await db.query('Test', columns: ['name', 'rowid']); + expect(list, [ + {'name': 'test', 'rowid': 1}, + {'name': 'other', 'rowid': 2} + ]); + } finally { + await db.close(); + } + }); + + test('Without rowid', () async { + // Sqflite.devSetDebugModeOn(true); + // this fails on iOS + + late Database db; + try { + final path = await initDeleteDb('without_rowid.db'); + db = await openDatabase(path); + // This table has no primary key and we ask sqlite not to generate + // a rowid + await db + .execute('CREATE TABLE Test (name TEXT PRIMARY KEY) WITHOUT ROWID'); + var id = await db.insert('Test', {'name': 'test'}); + + // it seems to always return 1 on Android, 0 on iOS..., 0 using ffi + var rowIdAlways0 = + (!supportsCompatMode || (platform.isIOS || platform.isMacOS)); + + if (rowIdAlways0) { + expect(id, 0); + } else { + expect(id, 1); + } + id = await db.insert('Test', {'name': 'other'}); + // it seems to always return 1 + if (rowIdAlways0) { + expect(id, 0); + } else { + expect(id, 1); + } + + // Insert conflict + // Only tested on Android for now... + try { + id = await db.insert('Test', {'name': 'other'}); + } on DatabaseException catch (e) { + // Test.name (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)) sql 'INSERT INTO Test (name) VALUES (?)' args [other] running without rowid + expect(e.getResultCode(), 1555); + } + + // notice the order is based on the primary key + final list = await db.query('Test'); + expect(list, [ + {'name': 'other'}, + {'name': 'test'} + ]); + } finally { + await db.close(); + } + }); + + test('Reference query', () async { + final path = await initDeleteDb('reference_query.db'); + final db = await openDatabase(path); + try { + final batch = db.batch(); + + batch.execute('CREATE TABLE Other (id INTEGER PRIMARY KEY, name TEXT)'); + batch.execute( + 'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, other REFERENCES Other(id))'); + batch.rawInsert('INSERT INTO Other (name) VALUES (?)', ['other 1']); + batch.rawInsert( + 'INSERT INTO Test (other, name) VALUES (?, ?)', [1, 'item 2']); + await batch.commit(); + + var result = await db.query('Test', + columns: ['other', 'name'], where: 'other = 1'); + print(result); + expect(result, [ + {'other': 1, 'name': 'item 2'} + ]); + result = await db.query('Test', + columns: ['other', 'name'], where: 'other = ?', whereArgs: [1]); + print(result); + expect(result, [ + {'other': 1, 'name': 'item 2'} + ]); + } finally { + await db.close(); + } + }); + + test('Binding null (fails on Android)', () async { + final db = await openDatabase(inMemoryDatabasePath); + try { + for (var value in [null, 2]) { + expect( + firstIntValue(await db.rawQuery( + 'SELECT CASE WHEN 0 = 1 THEN 1 ELSE ? END', [value])), + value); + } + } finally { + await db.close(); + } + }); + + test('Query by page', () async { + // await databaseFactory.debugSetLogLevel(sqfliteLogLevelVerbose); + + //final path = await initDeleteDb('query_by_page.db'); + //final db = await openDatabase(path); + final db = await openDatabase(inMemoryDatabasePath); + try { + await db.execute(''' + CREATE TABLE test ( + id INTEGER PRIMARY KEY + )'''); + await db.insert('test', {'id': 1}); + await db.insert('test', {'id': 2}); + await db.insert('test', {'id': 3}); + var resultsList = []; + + // Use a cursor + var cursor = + await db.rawQueryCursor('SELECT * FROM test', null, bufferSize: 2); + resultsList.clear(); + var results = >[]; + while (await cursor.moveNext()) { + results.add(cursor.current); + } + expect(results, [ + {'id': 1}, + {'id': 2}, + {'id': 3} + ]); + + // Multiple cursors a cursor + var cursor1 = + await db.rawQueryCursor('SELECT * FROM test', null, bufferSize: 2); + var cursor2 = + await db.rawQueryCursor('SELECT * FROM test', null, bufferSize: 1); + await cursor1.moveNext(); + expect(cursor1.current.values, [1]); + await cursor2.moveNext(); + await cursor2.moveNext(); + expect(cursor2.current.values, [2]); + await cursor1.moveNext(); + expect(cursor1.current.values, [2]); + await cursor1.close(); + await cursor1.close(); // ok to call twice + try { + cursor1.current.values; + fail('should fail get current'); + } on StateError catch (_) {} + await cursor2.moveNext(); + expect(cursor2.current.values, [3]); + expect(await cursor2.moveNext(), isFalse); + expect(await cursor1.moveNext(), isFalse); + try { + cursor2.current.values; + fail('should fail get current'); + } on StateError catch (_) {} + + // No data + cursor = await db.rawQueryCursor('SELECT * FROM test WHERE id > ?', [3], + bufferSize: 2); + expect(await cursor.moveNext(), isFalse); + + // Matching page size + cursor = await db.rawQueryCursor('SELECT * FROM test WHERE id > ?', [1], + bufferSize: 2); + expect(await cursor.moveNext(), isTrue); + expect(await cursor.moveNext(), isTrue); + expect(await cursor.moveNext(), isFalse); + } finally { + await db.close(); + } + }); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/slow_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/slow_test_page.dart new file mode 100644 index 0000000..a1d8a0c --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/slow_test_page.dart @@ -0,0 +1,153 @@ +import 'package:flutter/foundation.dart'; +import 'package:sqflite/sqflite.dart'; + +import 'src/common_import.dart'; +import 'test_page.dart'; + +// ignore_for_file: avoid_print + +/// Slow test page. +class SlowTestPage extends TestPage { + /// Slow test page. + SlowTestPage({Key? key}) : super('Slow tests', key: key) { + test('Perf 100 insert', () async { + final path = await initDeleteDb('slow_txn_100_insert.db'); + final db = await openDatabase(path); + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + await db.transaction((txn) async { + for (var i = 0; i < 100; i++) { + await txn + .rawInsert('INSERT INTO Test (name) VALUES (?)', ['item $i']); + } + }); + await db.close(); + }); + + test('Perf 100 insert no txn', () async { + final path = await initDeleteDb('slow_100_insert.db'); + final db = await openDatabase(path); + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + for (var i = 0; i < 1000; i++) { + await db.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item $i']); + } + await db.close(); + }); + + test('Perf 1000 insert', () async { + await perfInsert(); + }); + + test('Perf 1000 insert batch', () async { + final path = await initDeleteDb('slow_txn_1000_insert_batch.db'); + final db = await openDatabase(path); + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + final sw = Stopwatch()..start(); + final batch = db.batch(); + + for (var i = 0; i < 1000; i++) { + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item $i']); + } + await batch.commit(); + print('1000 insert batch ${sw.elapsed}'); + await db.close(); + }); + + test('Perf 1000 insert batch no result', () async { + final path = + await initDeleteDb('slow_txn_1000_insert_batch_no_result.db'); + final db = await openDatabase(path); + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + final sw = Stopwatch()..start(); + final batch = db.batch(); + + for (var i = 0; i < 1000; i++) { + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item $i']); + } + await batch.commit(noResult: true); + + print('1000 insert batch no result ${sw.elapsed}'); + await db.close(); + }); + + const count = 10000; + + test('Perf $count item', () async { + //Sqflite.devSetDebugModeOn(true); + await perfDo(count); + }); + + if (platform.isAndroid) { + test('Perf android NORMAL_PRIORITY', () async { + // ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package + await Sqflite.devSetOptions( + SqfliteOptions()..androidThreadPriority = 0); + try { + await perfInsert(); + await perfDo(count); + } finally { + // Background priority + await Sqflite.devSetOptions( + SqfliteOptions()..androidThreadPriority = 10); + } + }); + } + } + + /// basic performance testing. + Future perfDo(int count) async { + final path = await initDeleteDb('pref_${count}_items.db'); + final db = await openDatabase(path); + try { + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + var sw = Stopwatch()..start(); + final batch = db.batch(); + + for (var i = 0; i < count; i++) { + batch.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item $i']); + } + await batch.commit(); + print('sw ${sw.elapsed} insert $count items batch '); + + sw = Stopwatch()..start(); + var result = await db.query('Test'); + print('sw ${sw.elapsed} SELECT * From Test : ${result.length} items'); + + sw = Stopwatch()..start(); + result = + await db.query('Test', where: 'name LIKE ?', whereArgs: ['%item%']); + print( + 'sw ${sw.elapsed} SELECT * FROM Test WHERE name LIKE %item% ${result.length} items'); + + sw = Stopwatch()..start(); + result = + await db.query('Test', where: 'name LIKE ?', whereArgs: ['%dummy%']); + print( + 'sw ${sw.elapsed} SELECT * FROM Test WHERE name LIKE %dummy% ${result.length} items'); + } finally { + await db.close(); + } + } + + /// Insert perf testing. + Future perfInsert() async { + final path = await initDeleteDb('slow_txn_1000_insert.db'); + final db = await openDatabase(path); + await db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT)'); + + final sw = Stopwatch()..start(); + await db.transaction((txn) async { + for (var i = 0; i < 1000; i++) { + await txn.rawInsert('INSERT INTO Test (name) VALUES (?)', ['item $i']); + } + }); + print('1000 insert ${sw.elapsed}'); + await db.close(); + } + +// 2019-02-26 + +// BACKGROUND +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/src/common_import.dart b/packages/sqflite/sqflite_aurora/example/lib/src/common_import.dart new file mode 100644 index 0000000..9637df3 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/src/common_import.dart @@ -0,0 +1,8 @@ +export 'dart:async'; +export 'dart:convert'; + +export 'package:collection/collection.dart'; +export 'package:sqflite_common/src/internals.dart'; +export 'package:sqflite_common/src/platform/platform.dart'; + +export 'dev_utils.dart'; diff --git a/packages/sqflite/sqflite_aurora/example/lib/src/dev_utils.dart b/packages/sqflite/sqflite_aurora/example/lib/src/dev_utils.dart new file mode 100644 index 0000000..63423cd --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/src/dev_utils.dart @@ -0,0 +1,16 @@ +export 'dart:async'; + +// ignore_for_file: avoid_print + +/// Deprecated to prevent keeping the code used. +@Deprecated('Dev only') +void devPrint(Object object) { + print(object); +} + +/// Deprecated to prevent keeping the code used. +/// +/// Can be use as a todo for weird code. int value = devWarning(myFunction()); +/// The function is always called +@Deprecated('Dev only') +T devWarning(T value) => value; diff --git a/packages/sqflite/sqflite_aurora/example/lib/src/expect.dart b/packages/sqflite/sqflite_aurora/example/lib/src/expect.dart new file mode 100644 index 0000000..46c9e8d --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/src/expect.dart @@ -0,0 +1,148 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:matcher/matcher.dart'; + +/// An exception thrown when a test assertion fails. +class TestFailure { + /// An exception thrown when a test assertion fails. + TestFailure(this.message); + + /// Exception message + final String message; + + @override + String toString() => message; +} + +/// The type used for functions that can be used to build up error reports +/// upon failures in [expect]. +@Deprecated('Will be removed in 0.13.0.') +typedef ErrorFormatter = String Function(dynamic actual, Matcher matcher, + String? reason, Map matchState, bool verbose); + +/// Assert that [actual] matches [matcher]. +/// +/// This is the main assertion function. [reason] is optional and is typically +/// not supplied, as a reason is generated from [matcher]; if [reason] +/// is included it is appended to the reason generated by the matcher. +/// +/// [matcher] can be a value in which case it will be wrapped in an +/// [equals] matcher. +/// +/// If the assertion fails a [TestFailure] is thrown. +/// +/// If [skip] is a String or `true`, the assertion is skipped. The arguments are +/// still evaluated, but [actual] is not verified to match [matcher]. If +/// [actual] is a [Future], the test won't complete until the future emits a +/// value. +/// +/// If [skip] is a string, it should explain why the assertion is skipped; this +/// reason will be printed when running the test. +/// +/// Certain matchers, like [completion] and [throwsA], either match or fail +/// asynchronously. When you use [expect] with these matchers, it ensures that +/// the test doesn't complete until the matcher has either matched or failed. If +/// you want to wait for the matcher to complete before continuing the test, you +/// can call [expectLater] instead and `await` the result. +void expect( + Object? actual, + Object? matcher, { + String? reason, + Object? skip, +}) { + _expect(actual, matcher, reason: reason, skip: skip); +} + +/// Just like [expect], but returns a [Future] that completes when the matcher +/// has finished matching. +/// +/// For the [completes] and [completion] matchers, as well as [throwsA] and +/// related matchers when they're matched against a [Future], the returned +/// future completes when the matched future completes. For the [prints] +/// matcher, it completes when the future returned by the callback completes. +/// Otherwise, it completes immediately. +/// +/// If the matcher fails asynchronously, that failure is piped to the returned +/// future where it can be handled by user code. +Future expectLater(Object? actual, Object? matcher, + {String? reason, Object? skip}) => + _expect(actual, matcher, reason: reason, skip: skip) as Future; + +String _formatFailure(Matcher expected, Object? actual, String which, + {String? reason}) { + final buffer = StringBuffer(); + buffer.writeln(indent(prettyPrint(expected), first: 'Expected: ')); + buffer.writeln(indent(prettyPrint(actual), first: ' Actual: ')); + if (which.isNotEmpty) buffer.writeln(indent(which, first: ' Which: ')); + if (reason != null) buffer.writeln(reason); + return buffer.toString(); +} + +/// The implementation of [expect] and [expectLater]. +FutureOr _expect(Object? actual, Object? matcher, + {String? reason, + Object? skip, + bool verbose = false, + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + ErrorFormatter? formatter}) { + formatter ??= (actual, matcher, reason, matchState, verbose) { + final mismatchDescription = StringDescription(); + matcher.describeMismatch(actual, mismatchDescription, matchState, verbose); + + // ignore: deprecated_member_use + return _formatFailure(matcher, actual, mismatchDescription.toString(), + reason: reason); + }; + + if (skip != null && skip is! bool && skip is! String) { + throw ArgumentError.value(skip, 'skip', 'must be a bool or a String'); + } + + matcher = wrapMatcher(matcher); + + final matchState = {}; + try { + if ((matcher as Matcher).matches(actual, matchState)) { + return Future.sync(() {}); + } + } catch (e, trace) { + reason ??= '$e at $trace'; + } + fail(formatter(actual, matcher as Matcher, reason, matchState, verbose)); +} + +/// Convenience method for throwing a new [TestFailure] with the provided +/// [message]. +Never fail([String? message]) => throw TestFailure(message ?? 'should fail'); + +/// index text helper. +String indent(String text, {String? first}) { + if (first != null) { + return '$first $text'; + } + return text; +} + +/// index text helper. +String prettyPrint(dynamic text, {String? first}) { + if (first != null) { + return '$first $text'; + } + return '$text'; +} + +/// The default error formatter. +@Deprecated('Will be removed in 0.13.0.') +String formatFailure(Matcher expected, Object? actual, String which, + {String? reason}) { + final buffer = StringBuffer(); + buffer.writeln(indent(prettyPrint(expected), first: 'Expected: ')); + buffer.writeln(indent(prettyPrint(actual), first: ' Actual: ')); + if (which.isNotEmpty) buffer.writeln(indent(which, first: ' Which: ')); + if (reason != null) buffer.writeln(reason); + return buffer.toString(); +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/src/item_widget.dart b/packages/sqflite/sqflite_aurora/example/lib/src/item_widget.dart new file mode 100644 index 0000000..1c618f4 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/src/item_widget.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:sqflite_example/model/item.dart'; + +/// Item widget. +class ItemWidget extends StatefulWidget { + /// Item widget. + const ItemWidget(this.item, this.onTap, {this.summary, Key? key}) + : super(key: key); + + /// item summary. + final String? summary; + + /// item data. + final Item item; + + /// Action when pressed (typically run). + final void Function(Item item) onTap; // = Function(MainItem item); + + @override + // ignore: library_private_types_in_public_api + _ItemWidgetState createState() => _ItemWidgetState(); +} + +class _ItemWidgetState extends State { + @override + Widget build(BuildContext context) { + IconData? icon; + Color? color; + + switch (widget.item.state) { + case ItemState.none: + icon = Icons.arrow_forward_ios; + break; + case ItemState.running: + icon = Icons.more_horiz; + break; + case ItemState.success: + icon = Icons.check; + color = Colors.green; + break; + case ItemState.failure: + icon = Icons.close; + color = Colors.red; + break; + } + return ListTile( + // isThreeLine: widget.summary != null, + leading: SizedBox( + child: IconButton( + icon: Icon(icon, color: color), + + onPressed: null, // null disables the button + )), + title: Text(widget.item.name), + subtitle: widget.summary != null ? Text(widget.summary!) : null, + onTap: _onTap); + } + + void _onTap() { + widget.onTap(widget.item); + + //print(widget.item.route); + //Navigator.pushNamed(context, widget.item.route); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/src/main_item_widget.dart b/packages/sqflite/sqflite_aurora/example/lib/src/main_item_widget.dart new file mode 100644 index 0000000..d97d226 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/src/main_item_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:sqflite_example/model/main_item.dart'; + +/// Main item widget. +class MainItemWidget extends StatefulWidget { + /// Main item widget. + const MainItemWidget(this.item, this.onTap, {Key? key}) : super(key: key); + + /// item data. + final MainItem item; + + /// onTap action (typically run or open). + final void Function(MainItem item) onTap; // = Function(MainItem item); + + @override + // ignore: library_private_types_in_public_api + _MainItemWidgetState createState() => _MainItemWidgetState(); +} + +class _MainItemWidgetState extends State { + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(widget.item.title), + subtitle: Text(widget.item.description), + onTap: _onTap); + } + + void _onTap() { + widget.onTap(widget.item); + + //print(widget.item.route); + //Navigator.pushNamed(context, widget.item.route); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/test_page.dart new file mode 100644 index 0000000..0964716 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/test_page.dart @@ -0,0 +1,217 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:sqflite_example/src/common_import.dart'; +import 'package:sqflite_example/src/expect.dart'; + +import 'model/item.dart'; +import 'model/test.dart'; +import 'src/item_widget.dart'; + +export 'package:matcher/matcher.dart'; +export 'package:sqflite_example/database/database.dart'; + +export 'src/expect.dart' show expect, fail; +// ignore_for_file: avoid_print + +/// Base test page. +class TestPage extends StatefulWidget { + /// Base test page. + TestPage(this.title, {Key? key}) : super(key: key); + + /// The title. + final String title; + + /// Test list. + final List tests = []; + + /// define a test. + void test(String name, FutureOr Function() fn) { + tests.add(Test(name, fn)); + } + + /// define a solo test. + @Deprecated('SOLO_TEST - On purpose to remove before checkin') + // ignore: non_constant_identifier_names + void solo_test(String name, FutureOr Function() fn) { + tests.add(Test(name, fn, solo: true)); + } + + /// skip a test. + @Deprecated('SKIP_TEST - On purpose to remove before checkin') + // ignore: non_constant_identifier_names + void skip_test(String name, FutureOr Function() fn) { + tests.add(Test(name, fn, skip: true)); + } + + /// Thrown an exception + void fail([String? message]) { + throw Exception(message ?? 'should fail'); + } + + @override + // ignore: library_private_types_in_public_api + _TestPageState createState() => _TestPageState(); +} + +/// Verify a condition. +bool? verify(bool? condition, [String? message]) { + message ??= 'verify failed'; + expect(condition, true, reason: message); + /* + if (condition == null) { + throw new Exception(''$message' null condition'); + } + if (!condition) { + throw new Exception(''$message''); + } + */ + return condition; +} + +/// Group. +abstract class Group { + /// List of tests. + List get tests; + + bool? _hasSolo; + final _tests = []; + + /// Add a test. + void add(Test test) { + if (!test.skip) { + if (test.solo) { + if (_hasSolo != true) { + _hasSolo = true; + _tests.clear(); + } + _tests.add(test); + } else if (_hasSolo != true) { + _tests.add(test); + } + } + } + + /// true if it has solo or contains item with solo feature + bool? get hasSolo => _hasSolo; +} + +class _TestPageState extends State with Group { + int get _itemCount => items.length; + + List items = []; + + Future _run() async { + if (!mounted) { + return null; + } + + setState(() { + items.clear(); + }); + _tests.clear(); + for (var test in widget.tests) { + add(test); + } + for (var test in _tests) { + var item = Item(test.name); + + late int position; + setState(() { + position = items.length; + items.add(item); + }); + try { + await test.fn(); + + item = Item(test.name)..state = ItemState.success; + } catch (e, st) { + print(e); + print(st); + item = Item(test.name)..state = ItemState.failure; + } + + if (!mounted) { + return null; + } + + setState(() { + items[position] = item; + }); + } + } + + Future _runTest(int index) async { + if (!mounted) { + return null; + } + + final test = _tests[index]; + + var item = items[index]; + setState(() { + item.state = ItemState.running; + }); + try { + print('TEST Running ${test.name}'); + await test.fn(); + print('TEST Done ${test.name}'); + + item = Item(test.name)..state = ItemState.success; + } catch (e, st) { + print('TEST Error $e running ${test.name}'); + try { + print(st); + } catch (_) {} + item = Item(test.name)..state = ItemState.failure; + } + + if (!mounted) { + return null; + } + + setState(() { + items[index] = item; + }); + } + + @override + void initState() { + super.initState(); + /* + setState(() { + _itemCount = 3; + }); + */ + _run(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.title), actions: [ + IconButton( + icon: const Icon(Icons.refresh), + tooltip: 'Run again', + onPressed: _run, + ), + ]), + body: + ListView.builder(itemBuilder: _itemBuilder, itemCount: _itemCount)); + } + + Widget _itemBuilder(BuildContext context, int index) { + final item = getItem(index); + return ItemWidget(item, (Item item) { + //Navigator.of(context).pushNamed(item.route); + _runTest(index); + }); + } + + Item getItem(int index) { + return items[index]; + } + + @override + List get tests => widget.tests; +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/todo_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/todo_test_page.dart new file mode 100644 index 0000000..6b30149 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/todo_test_page.dart @@ -0,0 +1,150 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:sqflite/sqflite.dart'; + +import 'test_page.dart'; + +/// `todo` table name +const String tableTodo = 'todo'; + +/// id column name +const String columnId = '_id'; + +/// title column name +const String columnTitle = 'title'; + +/// done column name +const String columnDone = 'done'; + +/// Todo model. +class Todo { + /// Todo model. + Todo(); + + /// Read from a record. + Todo.fromMap(Map map) { + id = map[columnId] as int?; + title = map[columnTitle] as String?; + done = map[columnDone] == 1; + } + + /// id. + int? id; + + /// title. + String? title; + + /// done. + bool? done; + + /// Convert to a record. + Map toMap() { + final map = { + columnTitle: title, + columnDone: done == true ? 1 : 0 + }; + if (id != null) { + map[columnId] = id; + } + return map; + } +} + +/// Todo provider. +class TodoProvider { + /// The database when opened. + late Database db; + + /// Open the database. + Future open(String path) async { + db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db.execute(''' +create table $tableTodo ( + $columnId integer primary key autoincrement, + $columnTitle text not null, + $columnDone integer not null) +'''); + }); + } + + /// Insert a todo. + Future insert(Todo todo) async { + todo.id = await db.insert(tableTodo, todo.toMap()); + return todo; + } + + /// Get a todo. + Future getTodo(int id) async { + final List maps = await db.query(tableTodo, + columns: [columnId, columnDone, columnTitle], + where: '$columnId = ?', + whereArgs: [id]); + if (maps.isNotEmpty) { + return Todo.fromMap(maps.first); + } + return null; + } + + /// Delete a todo. + Future delete(int id) async { + return await db.delete(tableTodo, where: '$columnId = ?', whereArgs: [id]); + } + + /// Update a todo. + Future update(Todo todo) async { + return await db.update(tableTodo, todo.toMap(), + where: '$columnId = ?', whereArgs: [todo.id!]); + } + + /// Close database. + Future close() async => db.close(); +} + +/// Todo test page. +class TodoTestPage extends TestPage { + /// Todo test page. + TodoTestPage({Key? key}) : super('Todo example', key: key) { + test('open', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('simple_todo_open.db'); + final todoProvider = TodoProvider(); + await todoProvider.open(path); + + await todoProvider.close(); + //await Sqflite.setDebugModeOn(false); + }); + + test('insert/query/update/delete', () async { + // await Sqflite.devSetDebugModeOn(); + final path = await initDeleteDb('simple_todo.db'); + final todoProvider = TodoProvider(); + await todoProvider.open(path); + + var todo = Todo()..title = 'test'; + await todoProvider.insert(todo); + expect(todo.id, 1); + + expect(await todoProvider.getTodo(0), null); + todo = (await todoProvider.getTodo(1))!; + expect(todo.id, 1); + expect(todo.title, 'test'); + expect(todo.done, false); + + todo.done = true; + expect(await todoProvider.update(todo), 1); + todo = (await todoProvider.getTodo(1))!; + expect(todo.id, 1); + expect(todo.title, 'test'); + expect(todo.done, true); + + expect(await todoProvider.delete(0), 0); + expect(await todoProvider.delete(1), 1); + expect(await todoProvider.getTodo(1), null); + + await todoProvider.close(); + //await Sqflite.setDebugModeOn(false); + }); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/type_test_page.dart b/packages/sqflite/sqflite_aurora/example/lib/type_test_page.dart new file mode 100644 index 0000000..6d1355a --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/type_test_page.dart @@ -0,0 +1,269 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_example/utils.dart'; + +import 'test_page.dart'; + +// ignore_for_file: avoid_print + +class _Data { + late Database db; +} + +/// Type test page. +class TypeTestPage extends TestPage { + /// Type test page. + TypeTestPage({Key? key}) : super('Type tests', key: key) { + test('int', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('type_int.db'); + data.db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db.execute( + 'CREATE TABLE Test (id INTEGER PRIMARY KEY AUTOINCREMENT, value INTEGER)'); + }); + var id = await insertValue(-1); + expect(await getValue(id), -1); + + // less than 32 bits + id = await insertValue(pow(2, 31)); + expect(await getValue(id), pow(2, 31)); + + // more than 32 bits + id = await insertValue(pow(2, 33)); + //devPrint('2^33: ${await getValue(id)}'); + expect(await getValue(id), pow(2, 33)); + + id = await insertValue(pow(2, 62)); + //devPrint('2^62: ${pow(2, 62)} ${await getValue(id)}'); + expect(await getValue(id), pow(2, 62), + reason: '2^62: ${pow(2, 62)} ${await getValue(id)}'); + + var value = pow(2, 63).round() - 1; + id = await insertValue(value); + //devPrint('${value} ${await getValue(id)}'); + expect(await getValue(id), value, reason: '$value ${await getValue(id)}'); + + value = -(pow(2, 63)).round(); + id = await insertValue(value); + //devPrint('${value} ${await getValue(id)}'); + expect(await getValue(id), value, reason: '$value ${await getValue(id)}'); + /* + id = await insertValue(pow(2, 63)); + devPrint('2^63: ${pow(2, 63)} ${await getValue(id)}'); + assert(await getValue(id) == pow(2, 63), '2^63: ${pow(2, 63)} ${await getValue(id)}'); + + // more then 64 bits + id = await insertValue(pow(2, 65)); + assert(await getValue(id) == pow(2, 65)); + + // more then 128 bits + id = await insertValue(pow(2, 129)); + assert(await getValue(id) == pow(2, 129)); + */ + await data.db.close(); + }); + + test('real', () async { + //await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('type_real.db'); + data.db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value REAL)'); + }); + var id = await insertValue(-1.1); + expect(await getValue(id), -1.1); + // big float + id = await insertValue(1 / 3); + expect(await getValue(id), 1 / 3); + id = await insertValue(pow(2, 63) + .1); + expect(await getValue(id), pow(2, 63) + 0.1); + + // integer? + id = await insertValue(pow(2, 62)); + expect(await getValue(id), pow(2, 62)); + await data.db.close(); + }); + + test('text', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('type_text.db'); + data.db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db.execute( + 'CREATE TABLE Test (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT)'); + }); + try { + var id = await insertValue('simple text'); + expect(await getValue(id), 'simple text'); + // null + id = await insertValue(null); + expect(await getValue(id), null); + + // utf-8 + id = await insertValue('àöé'); + expect(await getValue(id), 'àöé'); + } finally { + await data.db.close(); + } + }); + + test('blob', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('type_blob.db'); + data.db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value BLOB)'); + }); + int id; + try { + // insert text in blob + id = await insertValue('simple text'); + expect(await getValue(id), 'simple text'); + + // UInt8List - default + final byteData = ByteData(1); + byteData.setInt8(0, 1); + final blob = byteData.buffer.asUint8List(); + id = await insertValue(blob); + //print(await getValue(id)); + final result = (await getValue(id)) as List; + print(result.runtimeType); + expect(result is Uint8List, true); + expect(result.length, 1); + expect(result, [1]); + + // empty array not supported + //id = await insertValue([]); + //print(await getValue(id)); + //assert(eq.equals(await getValue(id), [])); + + var blob1234 = [1, 2, 3, 4]; + if (!supportsCompatMode) { + blob1234 = Uint8List.fromList(blob1234); + } + id = await insertValue(blob1234); + dynamic value = (await getValue(id)) as List; + print(value); + print('${(value as List).length}'); + expect(value, blob1234, reason: '${await getValue(id)}'); + + // test hex feature on sqlite + final hexResult = await data.db + .rawQuery('SELECT hex(value) FROM Test WHERE id = ?', [id]); + expect(hexResult[0].values.first, '01020304'); + + // try blob lookup (works on Android since 2022-09-19) + var rows = await data.db + .rawQuery('SELECT * FROM Test WHERE value = ?', [blob1234]); + expect(rows.length, 1); + + // try blob lookup using hex + rows = await data.db.rawQuery( + 'SELECT * FROM Test WHERE hex(value) = ?', [Sqflite.hex(blob1234)]); + expect(rows.length, 1); + expect(rows[0]['id'], 3); + + // Insert empty blob + final blobEmpty = Uint8List(0); + id = await insertValue(blobEmpty); + value = await getValue(id); + expect(value, const TypeMatcher()); + expect(value, isEmpty); + } finally { + await data.db.close(); + } + }); + + test('null', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('type_null.db'); + data.db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + try { + final id = await insertValue(null); + expect(await getValue(id), null); + + // Make a string + expect(await updateValue(id, 'dummy'), 1); + expect(await getValue(id), 'dummy'); + + expect(await updateValue(id, null), 1); + expect(await getValue(id), null); + } finally { + await data.db.close(); + } + }); + + test('date_time', () async { + // await Sqflite.devSetDebugModeOn(true); + final path = await initDeleteDb('type_date_time.db'); + data.db = await openDatabase(path, version: 1, + onCreate: (Database db, int version) async { + await db + .execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + try { + var failed = false; + try { + await insertValue(DateTime.fromMillisecondsSinceEpoch(1234567890)); + } on ArgumentError catch (_) { + failed = true; + } + expect(failed, true); + } finally { + await data.db.close(); + } + }); + + test('bool', () async { + //await Sqflite.devSetDebugModeOn(true); + data.db = await openDatabase(inMemoryDatabasePath, version: 1, + onCreate: (Database db, int version) async { + await db.execute( + 'CREATE TABLE Test (id INTEGER PRIMARY KEY, value BOOLEAN)'); + }); + try { + var failed = false; + try { + await insertValue(true); + } on ArgumentError catch (_) { + failed = true; + } + if (supportsCompatMode) { + print('for now bool are accepted but inconsistent on iOS/Android'); + expect(failed, isFalse); + } + } finally { + await data.db.close(); + } + }); + } + + /// Out internal data. + // ignore: library_private_types_in_public_api + final _Data data = _Data(); + + /// Get the value field from a given id + Future getValue(int id) async { + return ((await data.db.query('Test', where: 'id = $id')).first)['value']; + } + + /// insert the value field and return the id + Future insertValue(dynamic value) async { + return await data.db.insert('Test', {'value': value}); + } + + /// insert the value field and return the id + Future updateValue(int id, dynamic value) async { + return await data.db.update('Test', {'value': value}, where: 'id = $id'); + } +} diff --git a/packages/sqflite/sqflite_aurora/example/lib/utils.dart b/packages/sqflite/sqflite_aurora/example/lib/utils.dart new file mode 100644 index 0000000..cf8cfc7 --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/lib/utils.dart @@ -0,0 +1,12 @@ +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite/sqflite_dev.dart'; + +/// Usage: await sleep(500); +Future sleep([int milliseconds = 0]) => + Future.delayed(Duration(milliseconds: milliseconds)); + +/// Supports compat mode (devSetDebugModeOn, queryAsMap, fts4, some error handled - missing parameter, bad file) +bool get supportsCompatMode { + // ignore: invalid_use_of_visible_for_testing_member + return databaseFactory == sqfliteDatabaseFactoryDefault; +} diff --git a/packages/sqflite/sqflite_aurora/example/pubspec.yaml b/packages/sqflite/sqflite_aurora/example/pubspec.yaml new file mode 100644 index 0000000..320712f --- /dev/null +++ b/packages/sqflite/sqflite_aurora/example/pubspec.yaml @@ -0,0 +1,34 @@ +name: sqflite_example +description: Demonstrates how to use the sqflite_aurora plugin. + +publish_to: 'none' + +environment: + sdk: '>=2.18.6 <3.0.0' + +dependencies: + flutter: + sdk: flutter + + sqflite: ^2.2.6 + sqflite_common: ^2.4.4 + sqflite_aurora: + path: ../ + + cupertino_icons: ^1.0.2 + path: ^1.8.2 + synchronized: ^3.1.0 + collection: ^1.16.0 + matcher: ^0.12.12 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true + assets: + - assets/example.db + - assets/issue_64.db diff --git a/packages/sqflite/sqflite_aurora/lib/sqflite_aurora.dart b/packages/sqflite/sqflite_aurora/lib/sqflite_aurora.dart new file mode 100644 index 0000000..a9db93f --- /dev/null +++ b/packages/sqflite/sqflite_aurora/lib/sqflite_aurora.dart @@ -0,0 +1,8 @@ +import 'package:sqflite/sqflite.dart'; + +class SqfliteAurora { + /// Register [SqflitePlugin] for aurora platform + static void registerWith() { + SqflitePlugin.registerWith(); + } +} diff --git a/packages/sqflite/sqflite_aurora/pubspec.yaml b/packages/sqflite/sqflite_aurora/pubspec.yaml new file mode 100644 index 0000000..2ee396d --- /dev/null +++ b/packages/sqflite/sqflite_aurora/pubspec.yaml @@ -0,0 +1,20 @@ +name: sqflite_aurora +description: The Aurora OS implementation of sqflite. +version: 2.2.6+aurora1 +homepage: https://os-git.omprussia.ru/non-oss/flutter/flutter-plugins + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +flutter: + plugin: + platforms: + aurora: + dartPluginClass: SqfliteAurora + pluginClass: SqfliteAuroraPlugin + +dependencies: + flutter: + sdk: flutter + sqflite: ^2.2.6 diff --git a/packages/wakelock/wakelock_aurora/.gitignore b/packages/wakelock/wakelock_aurora/.gitignore new file mode 100644 index 0000000..9dcdec5 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/.gitignore @@ -0,0 +1,32 @@ +# 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/ +.packages +build/ +run.sh +.metadata diff --git a/packages/wakelock/wakelock_aurora/README.md b/packages/wakelock/wakelock_aurora/README.md new file mode 100644 index 0000000..98c10dd --- /dev/null +++ b/packages/wakelock/wakelock_aurora/README.md @@ -0,0 +1,29 @@ +# wakelock_aurora + +The Aurora implementation of [`wakelock`][https://pub.dev/packages/wakelock]. + +## Usage +This package is not an _endorsed_ implementation of `wakelock`. +Therefore, you have to include `wakelock_aurora` alongside `wakelock` as dependencies in your `pubspec.yaml` file. + +```yaml +dependencies: + wakelock: ^0.6.2 + wakelock_aurora: + path: # path to folder with plugin +``` + +```dart +import 'package:wakelock/wakelock.dart'; +// ... + +// The following line will enable wakelock. +Wakelock.enable(); + +// The next line disables the wakelock again. +Wakelock.disable(); +``` + +### Preview example + +![preview.png](data%2Fpreview.png) diff --git a/packages/wakelock/wakelock_aurora/analysis_options.yaml b/packages/wakelock/wakelock_aurora/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/wakelock/wakelock_aurora/data/com.nokia.mce.request.xml b/packages/wakelock/wakelock_aurora/data/com.nokia.mce.request.xml new file mode 100644 index 0000000..ee47a8a --- /dev/null +++ b/packages/wakelock/wakelock_aurora/data/com.nokia.mce.request.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/packages/wakelock/wakelock_aurora/data/preview.png b/packages/wakelock/wakelock_aurora/data/preview.png new file mode 100644 index 0000000..9f6ec78 Binary files /dev/null and b/packages/wakelock/wakelock_aurora/data/preview.png differ diff --git a/packages/wakelock/wakelock_aurora/example/.gitignore b/packages/wakelock/wakelock_aurora/example/.gitignore new file mode 100644 index 0000000..66cf84f --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/.gitignore @@ -0,0 +1,45 @@ +# 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 +.packages +.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 +pubspec.lock diff --git a/packages/wakelock/wakelock_aurora/example/README.md b/packages/wakelock/wakelock_aurora/example/README.md new file mode 100644 index 0000000..5d2eb21 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/README.md @@ -0,0 +1,17 @@ +# wakelock_aurora_example + +Demonstrates how to use the wakelock_aurora plugin. + +## Build + +```shell +# Add an alias if it doesn't already exist +alias flutter-aurora=$HOME/.local/opt/flutter-sdk/bin/flutter +# Get dependencies +flutter-aurora pub get +# Run build +flutter-aurora build aurora --release +``` + +You can collect, sign, run an example on the device with a script located in the `script/build_example.sh` +More information in `build_example.sh`. diff --git a/packages/wakelock/wakelock_aurora/example/analysis_options.yaml b/packages/wakelock/wakelock_aurora/example/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/wakelock/wakelock_aurora/example/aurora/.gitignore b/packages/wakelock/wakelock_aurora/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/wakelock/wakelock_aurora/example/aurora/CMakeLists.txt b/packages/wakelock/wakelock_aurora/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..b667964 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.wakelock_aurora_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/packages/wakelock/wakelock_aurora/example/aurora/desktop/com.example.wakelock_aurora_example.desktop b/packages/wakelock/wakelock_aurora/example/aurora/desktop/com.example.wakelock_aurora_example.desktop new file mode 100644 index 0000000..a2f7f06 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/desktop/com.example.wakelock_aurora_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=wakelock_aurora_example +Comment=Demonstrates how to use the wakelock_aurora plugin. +Icon=com.example.wakelock_aurora_example +Exec=/usr/bin/com.example.wakelock_aurora_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=wakelock_aurora_example diff --git a/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..b315972 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + }); +} diff --git a/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugin_registrant.h b/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugins.cmake b/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..ae4d0a2 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,30 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/wakelock/wakelock_aurora/example/aurora/icons/108x108.png b/packages/wakelock/wakelock_aurora/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/wakelock/wakelock_aurora/example/aurora/icons/108x108.png differ diff --git a/packages/wakelock/wakelock_aurora/example/aurora/icons/128x128.png b/packages/wakelock/wakelock_aurora/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/wakelock/wakelock_aurora/example/aurora/icons/128x128.png differ diff --git a/packages/wakelock/wakelock_aurora/example/aurora/icons/172x172.png b/packages/wakelock/wakelock_aurora/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/wakelock/wakelock_aurora/example/aurora/icons/172x172.png differ diff --git a/packages/wakelock/wakelock_aurora/example/aurora/icons/86x86.png b/packages/wakelock/wakelock_aurora/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/wakelock/wakelock_aurora/example/aurora/icons/86x86.png differ diff --git a/packages/wakelock/wakelock_aurora/example/aurora/main.cpp b/packages/wakelock/wakelock_aurora/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/wakelock/wakelock_aurora/example/aurora/rpm/com.example.wakelock_aurora_example.spec b/packages/wakelock/wakelock_aurora/example/aurora/rpm/com.example.wakelock_aurora_example.spec new file mode 100644 index 0000000..d646922 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/aurora/rpm/com.example.wakelock_aurora_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.wakelock_aurora_example +Summary: Demonstrates how to use the wakelock_aurora plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/wakelock/wakelock_aurora/example/lib/main.dart b/packages/wakelock/wakelock_aurora/example/lib/main.dart new file mode 100644 index 0000000..2d8a703 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/lib/main.dart @@ -0,0 +1,74 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:wakelock/wakelock.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + bool _enableWakelock = false; + + @override + void initState() { + super.initState(); + } + + Future _toggleWakelock() async { + final enableWakelock = !(await Wakelock.enabled); + await Wakelock.toggle(enable: enableWakelock); + setState(() { + _enableWakelock = enableWakelock; + }); + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example wakelock'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + // Info + Container( + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: const Text( + 'Demo application demonstration implementation of wakelock', + style: textStyleWhite, + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 30), + + ElevatedButton( + onPressed: _toggleWakelock, + child: Text('Toggle wakelock: $_enableWakelock'), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/wakelock/wakelock_aurora/example/pubspec.yaml b/packages/wakelock/wakelock_aurora/example/pubspec.yaml new file mode 100644 index 0000000..fb5b9da --- /dev/null +++ b/packages/wakelock/wakelock_aurora/example/pubspec.yaml @@ -0,0 +1,23 @@ +name: wakelock_aurora_example +description: Demonstrates how to use the wakelock_aurora plugin. + +publish_to: 'none' + +environment: + sdk: '>=2.18.6 <3.0.0' + +dependencies: + flutter: + sdk: flutter + wakelock: ^0.6.2 + wakelock_aurora: + path: ../ + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true diff --git a/packages/wakelock/wakelock_aurora/lib/com_nokia_mce_request.dart b/packages/wakelock/wakelock_aurora/lib/com_nokia_mce_request.dart new file mode 100644 index 0000000..52854d8 --- /dev/null +++ b/packages/wakelock/wakelock_aurora/lib/com_nokia_mce_request.dart @@ -0,0 +1,21 @@ +// This file was generated using the following command and may be overwritten. +// dart-dbus generate-remote-object data/com.nokia.mce.request.xml + +import 'package:dbus/dbus.dart'; + +class ComNokiaMceRequest extends DBusRemoteObject { + ComNokiaMceRequest(DBusClient client, String destination, + {DBusObjectPath path = + const DBusObjectPath.unchecked('/com/nokia/mce/request')}) + : super(client, name: destination, path: path); + + /// Invokes com.nokia.mce.request.req_display_blanking_pause() + Future callreq_display_blanking_pause( + {bool noAutoStart = false, + bool allowInteractiveAuthorization = false}) async { + await callMethod('com.nokia.mce.request', 'req_display_blanking_pause', [], + replySignature: DBusSignature(''), + noAutoStart: noAutoStart, + allowInteractiveAuthorization: allowInteractiveAuthorization); + } +} diff --git a/packages/wakelock/wakelock_aurora/lib/wakelock_aurora.dart b/packages/wakelock/wakelock_aurora/lib/wakelock_aurora.dart new file mode 100644 index 0000000..4f30b5e --- /dev/null +++ b/packages/wakelock/wakelock_aurora/lib/wakelock_aurora.dart @@ -0,0 +1,41 @@ +import 'dart:async'; + +import 'package:dbus/dbus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:wakelock_platform_interface/wakelock_platform_interface.dart'; +import 'com_nokia_mce_request.dart'; + +class WakelockAurora extends WakelockPlatformInterface { + bool _enable = false; + Timer? _timer; + + static void registerWith() { + if (TargetPlatform.aurora == defaultTargetPlatform) { + WakelockPlatformInterface.instance = WakelockAurora(); + } + } + + @override + Future toggle({required bool enable}) async { + if (_enable != enable) { + _enable = enable; + final client = DBusClient.system(); + final request = ComNokiaMceRequest(client, 'com.nokia.mce'); + if (_timer == null) { + request.callreq_display_blanking_pause(); + _timer = Timer.periodic(const Duration(seconds: 60), (timer) { + request.callreq_display_blanking_pause(); + }); + } else { + _timer?.cancel(); + _timer = null; + } + await client.close(); + } + } + + @override + Future get enabled async { + return _enable; + } +} diff --git a/packages/wakelock/wakelock_aurora/pubspec.yaml b/packages/wakelock/wakelock_aurora/pubspec.yaml new file mode 100644 index 0000000..012032a --- /dev/null +++ b/packages/wakelock/wakelock_aurora/pubspec.yaml @@ -0,0 +1,26 @@ +name: wakelock_aurora +description: A new Flutter plugin project. +version: 0.0.1 +homepage: + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + dbus: ^0.7.8 + plugin_platform_interface: ^2.0.2 + wakelock_platform_interface: ^0.3.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + aurora: + dartPluginClass: WakelockAurora diff --git a/packages/xdga_directories/.gitignore b/packages/xdga_directories/.gitignore new file mode 100644 index 0000000..6f08bb1 --- /dev/null +++ b/packages/xdga_directories/.gitignore @@ -0,0 +1,31 @@ +# 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/ +.packages +build/ +/.metadata diff --git a/packages/xdga_directories/README.md b/packages/xdga_directories/README.md new file mode 100644 index 0000000..ca8ca31 --- /dev/null +++ b/packages/xdga_directories/README.md @@ -0,0 +1,19 @@ +# xdga_directories + +A Dart package for reading directory path on Aurora OS. +Documentation for setting permissions can be found [here](https://developer.auroraos.ru/doc/software_development/reference/user_data). + +To use this package, the basic XDG values for the following are available via a Dart API: + +- `getAppDataLocation` - Returns a directory location where persistent application data can be stored. +- `getCacheLocation` - Returns a directory location where user-specific non-essential (cached) data should be written. +- `getDocumentsLocation` - Returns the directory containing user document files. +- `getDownloadLocation` - Returns a directory for user's downloaded files. +- `getMusicLocation` - Returns the directory containing the user's music or other audio files. +- `getPicturesLocation` - Returns the directory containing the user's pictures or photos. +- `getGenericDataLocation` - Returns a directory location where persistent data shared across applications can be stored. +- `getMoviesLocation` - Returns the directory containing the user's movies and videos. + +### Preview example + +![preview.png](data%2Fpreview.png) \ No newline at end of file diff --git a/packages/xdga_directories/analysis_options.yaml b/packages/xdga_directories/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/packages/xdga_directories/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/packages/xdga_directories/aurora/CMakeLists.txt b/packages/xdga_directories/aurora/CMakeLists.txt new file mode 100644 index 0000000..2fb2126 --- /dev/null +++ b/packages/xdga_directories/aurora/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.10) + +set(PLUGIN_NAME xdga_directories) +project(${PLUGIN_NAME} LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_FLAGS "-Wall -Wextra") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../src ${CMAKE_CURRENT_BINARY_DIR}/shared) diff --git a/packages/xdga_directories/data/preview.png b/packages/xdga_directories/data/preview.png new file mode 100644 index 0000000..1fc2f51 Binary files /dev/null and b/packages/xdga_directories/data/preview.png differ diff --git a/packages/xdga_directories/example/.gitignore b/packages/xdga_directories/example/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/packages/xdga_directories/example/.gitignore @@ -0,0 +1,44 @@ +# 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 +.packages +.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 diff --git a/packages/xdga_directories/example/README.md b/packages/xdga_directories/example/README.md new file mode 100644 index 0000000..7af8517 --- /dev/null +++ b/packages/xdga_directories/example/README.md @@ -0,0 +1,16 @@ +# xdga_directories_example + +Demonstrates how to use the xdga_directories 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/packages/xdga_directories/example/analysis_options.yaml b/packages/xdga_directories/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/packages/xdga_directories/example/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/packages/xdga_directories/example/aurora/.gitignore b/packages/xdga_directories/example/aurora/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/packages/xdga_directories/example/aurora/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/xdga_directories/example/aurora/CMakeLists.txt b/packages/xdga_directories/example/aurora/CMakeLists.txt new file mode 100644 index 0000000..48ee0cc --- /dev/null +++ b/packages/xdga_directories/example/aurora/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.10) +project(com.example.xdga_directories_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) +find_package(Qt5 COMPONENTS Core 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 Qt5::Core) +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/packages/xdga_directories/example/aurora/desktop/com.example.xdga_directories_example.desktop b/packages/xdga_directories/example/aurora/desktop/com.example.xdga_directories_example.desktop new file mode 100644 index 0000000..b025d3c --- /dev/null +++ b/packages/xdga_directories/example/aurora/desktop/com.example.xdga_directories_example.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=xdga_directories_example +Comment=Demonstrates how to use the xdga_directories plugin. +Icon=com.example.xdga_directories_example +Exec=/usr/bin/com.example.xdga_directories_example +X-Nemo-Application-Type=silica-qt5 + +[X-Application] +Permissions= +OrganizationName=com.example +ApplicationName=xdga_directories_example diff --git a/packages/xdga_directories/example/aurora/flutter/generated_plugin_registrant.cpp b/packages/xdga_directories/example/aurora/flutter/generated_plugin_registrant.cpp new file mode 100644 index 0000000..b315972 --- /dev/null +++ b/packages/xdga_directories/example/aurora/flutter/generated_plugin_registrant.cpp @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include + +#include "generated_plugin_registrant.h" + +void RegisterPlugins() { + Application::RegisterPlugins({ + }); +} diff --git a/packages/xdga_directories/example/aurora/flutter/generated_plugin_registrant.h b/packages/xdga_directories/example/aurora/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..648dcb3 --- /dev/null +++ b/packages/xdga_directories/example/aurora/flutter/generated_plugin_registrant.h @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT +#define GENERATED_PLUGIN_REGISTRANT + +void RegisterPlugins(); + +#endif /* GENERATED_PLUGIN_REGISTRANT */ diff --git a/packages/xdga_directories/example/aurora/flutter/generated_plugins.cmake b/packages/xdga_directories/example/aurora/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2d7ab7b --- /dev/null +++ b/packages/xdga_directories/example/aurora/flutter/generated_plugins.cmake @@ -0,0 +1,31 @@ +# +# Generated file, do not edit. +# +set(ROOT_PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}") + +function(add_library TARGET) + _add_library(${TARGET} ${ARGN}) + + if(NOT "${TARGET}" MATCHES "^PkgConfig::.*") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${ROOT_PROJECT_BINARY_DIR}/bundle/lib/$") + endif(NOT "${TARGET}" MATCHES "^PkgConfig::.*") +endfunction() + +list(APPEND FLUTTER_PLATFORM_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + xdga_directories +) + +foreach(PLUGIN ${FLUTTER_PLATFORM_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${PLUGIN}/aurora plugins/${PLUGIN}) + target_link_libraries(${BINARY_NAME} PRIVATE ${PLUGIN}_platform_plugin) +endforeach(PLUGIN) + +foreach(FFI_PLUGIN ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${FFI_PLUGIN}/aurora plugins/${FFI_PLUGIN}) +endforeach(FFI_PLUGIN) diff --git a/packages/xdga_directories/example/aurora/icons/108x108.png b/packages/xdga_directories/example/aurora/icons/108x108.png new file mode 100644 index 0000000..984893d Binary files /dev/null and b/packages/xdga_directories/example/aurora/icons/108x108.png differ diff --git a/packages/xdga_directories/example/aurora/icons/128x128.png b/packages/xdga_directories/example/aurora/icons/128x128.png new file mode 100644 index 0000000..2d552ef Binary files /dev/null and b/packages/xdga_directories/example/aurora/icons/128x128.png differ diff --git a/packages/xdga_directories/example/aurora/icons/172x172.png b/packages/xdga_directories/example/aurora/icons/172x172.png new file mode 100644 index 0000000..9dc271b Binary files /dev/null and b/packages/xdga_directories/example/aurora/icons/172x172.png differ diff --git a/packages/xdga_directories/example/aurora/icons/86x86.png b/packages/xdga_directories/example/aurora/icons/86x86.png new file mode 100644 index 0000000..5923bb1 Binary files /dev/null and b/packages/xdga_directories/example/aurora/icons/86x86.png differ diff --git a/packages/xdga_directories/example/aurora/main.cpp b/packages/xdga_directories/example/aurora/main.cpp new file mode 100644 index 0000000..2dd2f52 --- /dev/null +++ b/packages/xdga_directories/example/aurora/main.cpp @@ -0,0 +1,10 @@ +#include +#include "generated_plugin_registrant.h" + +int main(int argc, char *argv[]) { + Application::Initialize(argc, argv); + Application::SetPixelRatio(1.8); + RegisterPlugins(); + Application::Launch(); + return 0; +} diff --git a/packages/xdga_directories/example/aurora/rpm/com.example.xdga_directories_example.spec b/packages/xdga_directories/example/aurora/rpm/com.example.xdga_directories_example.spec new file mode 100644 index 0000000..36130d1 --- /dev/null +++ b/packages/xdga_directories/example/aurora/rpm/com.example.xdga_directories_example.spec @@ -0,0 +1,31 @@ +%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ +%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|appmanifest-.+|.+_platform_plugin)\\.so.*$ + +Name: com.example.xdga_directories_example +Summary: Demonstrates how to use the xdga_directories plugin. +Version: 0.1.0 +Release: 1 +License: Proprietary +Source0: %{name}-%{version}.tar.zst + +BuildRequires: cmake +BuildRequires: pkgconfig(flutter-embedder) + +%description +%{summary}. + +%prep +%autosetup + +%build +%cmake -DCMAKE_BUILD_TYPE=%{_flutter_build_type} +%make_build + +%install +%make_install + +%files +%{_bindir}/%{name} +%{_datadir}/%{name}/* +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/*/apps/%{name}.png diff --git a/packages/xdga_directories/example/lib/main.dart b/packages/xdga_directories/example/lib/main.dart new file mode 100644 index 0000000..1cb3a11 --- /dev/null +++ b/packages/xdga_directories/example/lib/main.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:xdga_directories/xdga_directories.dart' as xdga_directories; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + late String appDataLocation; + late String cacheLocation; + late String documentsLocation; + late String downloadLocation; + late String musicLocation; + late String picturesLocation; + late String genericDataLocation; + late String moviesLocation; + + @override + void initState() { + super.initState(); + // Get paths + appDataLocation = xdga_directories.getAppDataLocation(); + cacheLocation = xdga_directories.getCacheLocation(); + documentsLocation = xdga_directories.getDocumentsLocation(); + downloadLocation = xdga_directories.getDownloadLocation(); + musicLocation = xdga_directories.getMusicLocation(); + picturesLocation = xdga_directories.getPicturesLocation(); + genericDataLocation = xdga_directories.getGenericDataLocation(); + moviesLocation = xdga_directories.getMoviesLocation(); + } + + @override + Widget build(BuildContext context) { + const textStyleWhite = TextStyle(fontSize: 18, color: Colors.white); + const textStyleTitle = TextStyle(fontSize: 20, color: Colors.black); + const textStylePath = TextStyle(fontSize: 18, color: Colors.black54); + + const spaceMedium = SizedBox(height: 16); + const spacerSmall = SizedBox(height: 8); + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Example XDGA'), + ), + body: SingleChildScrollView( + child: Center( + child: Container( + padding: const EdgeInsets.all(10), + child: Column( + children: [ + Container( + decoration: const BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + child: const Padding( + padding: EdgeInsets.all(20), + child: Text( + 'A Dart package for reading directory path on Aurora OS.', + style: textStyleWhite, + ), + ), + ), + const SizedBox(height: 30), + + // getAppDataLocation + const Text( + 'getAppDataLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + appDataLocation, + style: textStylePath, + ), + spaceMedium, + + // getCacheLocation + const Text( + 'getCacheLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + cacheLocation, + style: textStylePath, + ), + spaceMedium, + + // getDocumentsLocation + const Text( + 'getDocumentsLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + documentsLocation, + style: textStylePath, + ), + spaceMedium, + + // getDocumentsLocation + const Text( + 'getDownloadLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + downloadLocation, + style: textStylePath, + ), + spaceMedium, + + // getDocumentsLocation + const Text( + 'getMusicLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + musicLocation, + style: textStylePath, + ), + spaceMedium, + + // getDocumentsLocation + const Text( + 'getPicturesLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + picturesLocation, + style: textStylePath, + ), + spaceMedium, + + // getDocumentsLocation + const Text( + 'getGenericDataLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + genericDataLocation, + style: textStylePath, + ), + spaceMedium, + + // getDocumentsLocation + const Text( + 'getMoviesLocation()', + style: textStyleTitle, + ), + spacerSmall, + Text( + moviesLocation, + style: textStylePath, + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/xdga_directories/example/pubspec.lock b/packages/xdga_directories/example/pubspec.lock new file mode 100644 index 0000000..2ee3257 --- /dev/null +++ b/packages/xdga_directories/example/pubspec.lock @@ -0,0 +1,182 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.12" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + xdga_directories: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" +sdks: + dart: ">=2.18.6 <3.0.0" + flutter: ">=2.11.0" diff --git a/packages/xdga_directories/example/pubspec.yaml b/packages/xdga_directories/example/pubspec.yaml new file mode 100644 index 0000000..b9c0bea --- /dev/null +++ b/packages/xdga_directories/example/pubspec.yaml @@ -0,0 +1,98 @@ +name: xdga_directories_example +description: Demonstrates how to use the xdga_directories 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 + +# 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 is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=2.18.6 <3.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 + + xdga_directories: + # When depending on this package from a real application you should use: + # xdga_directories: ^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: + 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/packages/xdga_directories/ffigen.yaml b/packages/xdga_directories/ffigen.yaml new file mode 100644 index 0000000..f04a98e --- /dev/null +++ b/packages/xdga_directories/ffigen.yaml @@ -0,0 +1,18 @@ +# Run with `flutter pub run ffigen --config ffigen.yaml`. +name: XdgaDirectoriesBindings +llvm-path: + - '/usr/lib/llvm-14/lib/libclang.so' # Ubuntu 22.04 + - '/usr/lib/llvm-15/lib/libclang.so' # Ubuntu 23.04 +description: | + Bindings for `src/xdga_directories.h`. + + Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +output: 'lib/xdga_directories_bindings_generated.dart' +headers: + entry-points: + - 'src/xdga_directories.h' + include-directives: + - 'src/xdga_directories.h' +comments: + style: any + length: full diff --git a/packages/xdga_directories/lib/xdga_directories.dart b/packages/xdga_directories/lib/xdga_directories.dart new file mode 100644 index 0000000..0b77018 --- /dev/null +++ b/packages/xdga_directories/lib/xdga_directories.dart @@ -0,0 +1,39 @@ +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +import 'xdga_directories_bindings_generated.dart'; + +const String _libName = 'xdga_directories'; + +/// The dynamic library in which the symbols for [XdgaDirectoriesBindings] can be found. +final DynamicLibrary _dylib = () { + return DynamicLibrary.open('lib$_libName.so'); +}(); + +/// The bindings to the native functions in [_dylib]. +final XdgaDirectoriesBindings _bindings = XdgaDirectoriesBindings(_dylib); + +/// QStandardPaths::CacheLocation +String getCacheLocation() => _bindings.getCacheLocation().cast().toDartString(); + +/// QStandardPaths::AppDataLocation +String getAppDataLocation() => _bindings.getAppDataLocation().cast().toDartString(); + +/// QStandardPaths::DocumentsLocation +String getDocumentsLocation() => _bindings.getDocumentsLocation().cast().toDartString(); + +/// QStandardPaths::DownloadLocation +String getDownloadLocation() => _bindings.getDownloadLocation().cast().toDartString(); + +/// QStandardPaths::MusicLocation +String getMusicLocation() => _bindings.getMusicLocation().cast().toDartString(); + +/// QStandardPaths::PicturesLocation +String getPicturesLocation() => _bindings.getPicturesLocation().cast().toDartString(); + +/// QStandardPaths::GenericDataLocation +String getGenericDataLocation() => _bindings.getGenericDataLocation().cast().toDartString(); + +/// QStandardPaths::MoviesLocation +String getMoviesLocation() => _bindings.getMoviesLocation().cast().toDartString(); diff --git a/packages/xdga_directories/lib/xdga_directories_bindings_generated.dart b/packages/xdga_directories/lib/xdga_directories_bindings_generated.dart new file mode 100644 index 0000000..c48bdb0 --- /dev/null +++ b/packages/xdga_directories/lib/xdga_directories_bindings_generated.dart @@ -0,0 +1,104 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +/// Bindings for `src/xdga_directories.h`. +/// +/// Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +/// +class XdgaDirectoriesBindings { + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + XdgaDirectoriesBindings(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + XdgaDirectoriesBindings.fromLookup( + ffi.Pointer Function(String symbolName) + lookup) + : _lookup = lookup; + + ffi.Pointer getCacheLocation() { + return _getCacheLocation(); + } + + late final _getCacheLocationPtr = + _lookup Function()>>( + 'getCacheLocation'); + late final _getCacheLocation = + _getCacheLocationPtr.asFunction Function()>(); + + ffi.Pointer getAppDataLocation() { + return _getAppDataLocation(); + } + + late final _getAppDataLocationPtr = + _lookup Function()>>( + 'getAppDataLocation'); + late final _getAppDataLocation = + _getAppDataLocationPtr.asFunction Function()>(); + + ffi.Pointer getDocumentsLocation() { + return _getDocumentsLocation(); + } + + late final _getDocumentsLocationPtr = + _lookup Function()>>( + 'getDocumentsLocation'); + late final _getDocumentsLocation = + _getDocumentsLocationPtr.asFunction Function()>(); + + ffi.Pointer getDownloadLocation() { + return _getDownloadLocation(); + } + + late final _getDownloadLocationPtr = + _lookup Function()>>( + 'getDownloadLocation'); + late final _getDownloadLocation = + _getDownloadLocationPtr.asFunction Function()>(); + + ffi.Pointer getMusicLocation() { + return _getMusicLocation(); + } + + late final _getMusicLocationPtr = + _lookup Function()>>( + 'getMusicLocation'); + late final _getMusicLocation = + _getMusicLocationPtr.asFunction Function()>(); + + ffi.Pointer getPicturesLocation() { + return _getPicturesLocation(); + } + + late final _getPicturesLocationPtr = + _lookup Function()>>( + 'getPicturesLocation'); + late final _getPicturesLocation = + _getPicturesLocationPtr.asFunction Function()>(); + + ffi.Pointer getGenericDataLocation() { + return _getGenericDataLocation(); + } + + late final _getGenericDataLocationPtr = + _lookup Function()>>( + 'getGenericDataLocation'); + late final _getGenericDataLocation = + _getGenericDataLocationPtr.asFunction Function()>(); + + ffi.Pointer getMoviesLocation() { + return _getMoviesLocation(); + } + + late final _getMoviesLocationPtr = + _lookup Function()>>( + 'getMoviesLocation'); + late final _getMoviesLocation = + _getMoviesLocationPtr.asFunction Function()>(); +} diff --git a/packages/xdga_directories/pubspec.yaml b/packages/xdga_directories/pubspec.yaml new file mode 100644 index 0000000..3414055 --- /dev/null +++ b/packages/xdga_directories/pubspec.yaml @@ -0,0 +1,26 @@ +name: xdga_directories +description: A Dart package for reading XDG directory configuration information on Aurora OS. +version: 0.0.1 +homepage: https://os-git.omprussia.ru/non-oss/flutter/flutter-plugins/packages/xdga_directories + +environment: + sdk: '>=2.18.6 <3.0.0' + flutter: ">=2.11.0" + +flutter: + plugin: + platforms: + aurora: + ffiPlugin: true + +dependencies: + flutter: + sdk: flutter + ffi: ^1.2.1 + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + ffigen: ^5.0.1 + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 diff --git a/packages/xdga_directories/src/CMakeLists.txt b/packages/xdga_directories/src/CMakeLists.txt new file mode 100644 index 0000000..6486fab --- /dev/null +++ b/packages/xdga_directories/src/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) + +project(xdga_directories VERSION 0.0.1) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules(Qt5Core REQUIRED IMPORTED_TARGET Qt5Core) + +add_library(xdga_directories SHARED + "xdga_directories.cpp" +) + +target_link_libraries(xdga_directories PRIVATE PkgConfig::Qt5Core) + +set_target_properties(xdga_directories PROPERTIES + PUBLIC_HEADER "xdga_directories.h" + OUTPUT_NAME "xdga_directories" +) + +target_compile_definitions(xdga_directories PUBLIC DART_SHARED_LIB) + diff --git a/packages/xdga_directories/src/xdga_directories.cpp b/packages/xdga_directories/src/xdga_directories.cpp new file mode 100644 index 0000000..3707be9 --- /dev/null +++ b/packages/xdga_directories/src/xdga_directories.cpp @@ -0,0 +1,43 @@ +#include + +#include "xdga_directories.h" + +char *getCacheLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation).toUtf8().data(); +} + +char *getAppDataLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toUtf8().data(); +} + +char *getDocumentsLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).toUtf8().data(); +} + +char *getDownloadLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation).toUtf8().data(); +} + +char *getMusicLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::MusicLocation).toUtf8().data(); +} + +char *getPicturesLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::PicturesLocation).toUtf8().data(); +} + +char *getGenericDataLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).toUtf8().data(); +} + +char *getMoviesLocation() +{ + return QStandardPaths::writableLocation(QStandardPaths::MoviesLocation).toUtf8().data(); +} \ No newline at end of file diff --git a/packages/xdga_directories/src/xdga_directories.h b/packages/xdga_directories/src/xdga_directories.h new file mode 100644 index 0000000..e2f3187 --- /dev/null +++ b/packages/xdga_directories/src/xdga_directories.h @@ -0,0 +1,23 @@ +#ifdef __cplusplus +extern "C" { +#endif + +char *getCacheLocation(); + +char *getAppDataLocation(); + +char *getDocumentsLocation(); + +char *getDownloadLocation(); + +char *getMusicLocation(); + +char *getPicturesLocation(); + +char *getGenericDataLocation(); + +char *getMoviesLocation(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/script/build_example.sh b/script/build_example.sh new file mode 100755 index 0000000..cbad774 --- /dev/null +++ b/script/build_example.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +## Build example, sign rpm, upload/install/run rpm to device + +## Usage +## +## chmod +x ./build_example.sh +## +## ./build_example.sh \ +## -p xdga_directories \ +## -d : \ +## -s /home/user/sign/system_keys + +## Flatter path +# FLATTER="$HOME/.local/opt/flutter-sdk/bin/flutter" + +# @todo +FLATTER="/home/keygenqt/Documents/OMP/flutter-sdk/bin/flutter" + +## https://developer.auroraos.ru/doc/software_development/psdk/setup +## Install Platform SDK path +## You may not have set the PSDK_DIR environment variable. +## export PSDK_DIR=$HOME/AuroraPlatformSDK/sdks/aurora_psdk + +while getopts p:d:s: flag; do + case "${flag}" in + p) package=${OPTARG} ;; + d) device=${OPTARG} ;; + s) sign=${OPTARG} ;; + *) + echo "usage: $0 [-p] [-d] [-s]" >&2 + exit 1 + ;; + esac +done + +if [ -z "$package" ]; then + echo "Specify a build package" + exit +else + cd "../packages/$package" 2>/dev/null || eval 'echo "Package \"$package\" not found." && exit' + ## Update dependency + $FLATTER pub get + ## Run ffigen if has + $FLATTER pub run ffigen --config ffigen.yaml 2>/dev/null + ## Open example dir + cd "example" || exit + ## Build aurora example app + { + $FLATTER build aurora --release + } || { + exit 1; + } +fi + +if [ -n "$sign" ]; then + + key=$(ls "$sign"/*key.pem) + + if [ -z "$key" ]; then + echo "Key *key.pem not found." + exit + fi + + cert=$(ls "$sign"/*cert.pem) + + if [ -z "$cert" ]; then + echo "Key *cert.pem not found." + exit + fi + + ## Sign rpm system key + "$PSDK_DIR"/sdk-chroot rpmsign-external sign \ + --key "$key" \ + --cert "$cert" \ + build/aurora/arm/release/RPMS/*.rpm +fi + +if [ -n "$device" ]; then + + IFS=':' read -ra ADDR <<< "$device" + IFS='/' read -ra ADDP <<< "$package" + + D_IP="${ADDR[0]}" + D_PASS="${ADDR[1]}" + APP_KEY="${ADDP[-1]}" + + # shellcheck disable=SC2012 + rpm=$(ls "$PWD"/build/aurora/arm/release/RPMS/*.rpm | sort -r | head -n 1) + + # upload rpm + scp "$rpm" defaultuser@"$D_IP:/home/defaultuser/Downloads" + + # install rpm + ssh -t defaultuser@$D_IP "echo $D_PASS | devel-su pkcon -y install-local /home/defaultuser/Downloads/*$APP_KEY*.rpm" + + # run application + ssh -t defaultuser@$D_IP "/usr/bin/com.example.${APP_KEY}_example" +fi diff --git a/script/vscode_properties.sh b/script/vscode_properties.sh new file mode 100755 index 0000000..8211e26 --- /dev/null +++ b/script/vscode_properties.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +## Script create c_cpp_properties.json with dependencies for flutter aurora + +## Usage +## +## chmod +x ./vscode_properties.sh +## ./vscode_properties.sh + +## https://developer.auroraos.ru/doc/software_development/psdk/setup +## Install Platform SDK path +## You may not have set the PSDK_DIR environment variable. +## export PSDK_DIR=$HOME/AuroraPlatformSDK/sdks/aurora_psdk + +cd ../ + +## check file +[ -f .vscode/c_cpp_properties.json ] && { echo "File c_cpp_properties.json already exist!"; exit; } + +## find target +TARGET=$($PSDK_DIR/sdk-chroot sdk-assistant list | grep armv | grep default | sed 's/^.*A/A/g' | sed 's/\s.*//g') + +## mkdir .vscode if not exist +[ -d .vscode ] || mkdir .vscode + +## find targets path +TARGETS_PATH=$(cd "$PSDK_DIR/../../" && pwd)/targets + +## save file +tee -a .vscode/c_cpp_properties.json << END +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "\${workspaceFolder}/**", + "$TARGETS_PATH/$TARGET/usr/include", + "$TARGETS_PATH/$TARGET/usr/include/dconf", + "$TARGETS_PATH/$TARGET/usr/include/flutter-embedder", + "$TARGETS_PATH/$TARGET/usr/include/maliit", + "$TARGETS_PATH/$TARGET/usr/include/appmanifest-cpp", + "$TARGETS_PATH/$TARGET/usr/include/glib-2.0", + "$TARGETS_PATH/$TARGET/usr/lib/glib-2.0/include", + "$TARGETS_PATH/$TARGET/usr/include/sailfishapp", + "$TARGETS_PATH/$TARGET/usr/include/qt5", + "$TARGETS_PATH/$TARGET/usr/include/qt5/QtConcurrent", + "$TARGETS_PATH/$TARGET/usr/include/qt5/QtCore", + "$TARGETS_PATH/$TARGET/usr/include/qt5/QtDBus", + "$TARGETS_PATH/$TARGET/usr/include/qt5/QtGui", + "$TARGETS_PATH/$TARGET/usr/include/qt5/QtMultimedia", + "$TARGETS_PATH/$TARGET/usr/include/qt5/QtQuick" + ], + "defines": [ + "__ARM_PCS_VFP" + ], + "compilerPath": "/usr/bin/g++", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 +} +END