From ece6456d3c2b9a6e05838444f753ae0d4965e9c7 Mon Sep 17 00:00:00 2001 From: Yauheni Khnykin Date: Sun, 5 Nov 2023 14:47:51 +0100 Subject: [PATCH] Adds simple example Signed-off-by: Yauheni Khnykin --- .github/workflows/functional-tests.yml | 11 + docs/lime_idl.md | 49 +---- examples/README.md | 31 +++ examples/calculator/CMakeLists.txt | 77 +++++++ examples/calculator/README.md | 150 +++++++++++++ examples/calculator/android/.gitignore | 15 ++ examples/calculator/android/app/.gitignore | 1 + examples/calculator/android/app/build.gradle | 77 +++++++ .../calculator/android/app/proguard-rules.pro | 21 ++ .../calculator/ExampleInstrumentedTest.java | 40 ++++ .../android/app/src/main/AndroidManifest.xml | 26 +++ .../gluecodium/calculator/MainActivity.java | 197 +++++++++++++++++ .../res/drawable/ic_launcher_background.xml | 170 +++++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 +++ .../app/src/main/res/layout/activity_main.xml | 122 +++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../app/src/main/res/values-night/themes.xml | 16 ++ .../app/src/main/res/values/colors.xml | 10 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/themes.xml | 16 ++ .../app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ .../calculator/ExampleUnitTest.java | 17 ++ examples/calculator/android/build.gradle | 4 + examples/calculator/android/gradle.properties | 21 ++ .../gradle/wrapper/gradle-wrapper.properties | 6 + examples/calculator/android/gradlew | 185 ++++++++++++++++ examples/calculator/android/gradlew.bat | 89 ++++++++ examples/calculator/android/settings.gradle | 17 ++ examples/calculator/cpp/CalculatorImpl.cpp | 108 ++++++++++ examples/calculator/ios/CMakeLists.txt | 46 ++++ examples/calculator/ios/ContentView.swift | 186 ++++++++++++++++ .../ios/gluecodium_calculator_appApp.swift | 27 +++ examples/calculator/lime/Calculator.lime | 201 ++++++++++++++++++ 45 files changed, 1967 insertions(+), 46 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/calculator/CMakeLists.txt create mode 100644 examples/calculator/README.md create mode 100644 examples/calculator/android/.gitignore create mode 100644 examples/calculator/android/app/.gitignore create mode 100644 examples/calculator/android/app/build.gradle create mode 100644 examples/calculator/android/app/proguard-rules.pro create mode 100644 examples/calculator/android/app/src/androidTest/java/com/gluecodium/calculator/ExampleInstrumentedTest.java create mode 100644 examples/calculator/android/app/src/main/AndroidManifest.xml create mode 100644 examples/calculator/android/app/src/main/java/com/gluecodium/calculator/MainActivity.java create mode 100644 examples/calculator/android/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 examples/calculator/android/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 examples/calculator/android/app/src/main/res/layout/activity_main.xml create mode 100644 examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 examples/calculator/android/app/src/main/res/values-night/themes.xml create mode 100644 examples/calculator/android/app/src/main/res/values/colors.xml create mode 100644 examples/calculator/android/app/src/main/res/values/strings.xml create mode 100644 examples/calculator/android/app/src/main/res/values/themes.xml create mode 100644 examples/calculator/android/app/src/main/res/xml/backup_rules.xml create mode 100644 examples/calculator/android/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 examples/calculator/android/app/src/test/java/com/gluecodium/calculator/ExampleUnitTest.java create mode 100644 examples/calculator/android/build.gradle create mode 100644 examples/calculator/android/gradle.properties create mode 100644 examples/calculator/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/calculator/android/gradlew create mode 100644 examples/calculator/android/gradlew.bat create mode 100644 examples/calculator/android/settings.gradle create mode 100644 examples/calculator/cpp/CalculatorImpl.cpp create mode 100644 examples/calculator/ios/CMakeLists.txt create mode 100644 examples/calculator/ios/ContentView.swift create mode 100644 examples/calculator/ios/gluecodium_calculator_appApp.swift create mode 100644 examples/calculator/lime/Calculator.lime diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index 18b0cdfd80..108d41a89e 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -115,6 +115,12 @@ jobs: export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools ./scripts/build-android-namerules --publish working-directory: functional-tests + - name: Build examples + run: | + export ANDROID_HOME=${HOME}/android-sdk-linux + pushd examples/calculator/android + ./gradlew build + popd swift: name: Swift on Linux @@ -221,6 +227,11 @@ jobs: run: | ./scripts/build-swift-namerules --publish working-directory: functional-tests + - name: Build examples + run: | + mkdir build_example_calculator + cmake -B build_example_calculator examples/calculator -GXcode -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS + cmake --build build_example_calculator dart: name: Dart diff --git a/docs/lime_idl.md b/docs/lime_idl.md index 4ea1d3bbb6..92f9d4a110 100644 --- a/docs/lime_idl.md +++ b/docs/lime_idl.md @@ -25,54 +25,11 @@ LimeIDL syntax ### Example -This example only shows how syntax looks like, it's not ready to use out of the box. -For working examples check lime files in [functional tests](https://github.com/heremaps/gluecodium/tree/master/functional-tests/functional/input/lime). -Also [cmake tests](https://github.com/heremaps/gluecodium/tree/master/cmake/tests/unit) demonstrate how -to integrate Gluecodium to CMake-based project and how to use it for more complex scenarios when you need -multiple modules which interact with each other. +Start with [examples](../examples/README.md). -``` -package com.example - -import com.example.utils.GenericResult - -class SomeImportantProcessor { - constructor create(options: Options?) throws SomethingWrongException - - fun process(mode: Mode, input: String): GenericResult - - property processingTime: ProcessorHelperTypes.Timestamp { get } - - @Internal - static property secretDelegate: ProcessorDelegate? - - enum Mode { - SLOW, - FAST, - CHEAP - } - - @Immutable - struct Options { - flagOption: Boolean - uintOption: UShort - additionalOptions: List = {} - } +For more advanced lime examples check [functional tests](https://github.com/heremaps/gluecodium/tree/master/functional-tests/functional/input/lime). - exception SomethingWrongException(String) -} - -interface ProcessorDelegate: com.example.utils.GenericDelegate { - fun onProcessorEvent(message: String) -} - -struct ProcessorHelperTypes { - typealias Timestamp = Date - - const DefaultOptions: SomeImportantProcessor.Options = {flagOption = true, uintOption = 42, {}} -} - -``` +Also [cmake tests](https://github.com/heremaps/gluecodium/tree/master/cmake/tests/unit) demonstrate how to use CMake scripts for more complex scenarios when you need multiple modules which interact with each other. ### General remarks on syntax diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000..3eb5c43c4e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,31 @@ +## Overview + +[README.md](../README.md) in the root provides basic information about Gluecodium. +It's simplier to keep C++ code in shared library when binding is necessary for Java and for Swift. +In examples simpliest configuration is used. For more advanced check `cmake/tests/unit` tests. + +## CMake Gluecodium wraper. + +This is set of cmake functions which provide the following functionality: +- Download Gluecodium from artifactory. +- Add step to generate code. +- Set options to configure generated code. +- Help to configure a target to include generated C++/Swift/Java/Flutter sources, add include directories, etc. + +While examples work with shipped CMake Gluecodium wraper in real application it's handy to clone only subtree `cmake/modules`. + +## Example `calculator`. + +What this example demonstrates: +- How to configure the project to generate C++, Java and Swift source code. +- How to use the generated code in Android and iOS applications. +- How to describe basic primitives like `class`, `interface`, `struct` and how to interact with them +- How to make platform-only comment and links. + +What this example DOESN'T demonstrate: +- How to make complex build setup with several modules. +- How to describe custom types. +- How to make Flutter plugin. +- How to use advanced Gluecodium features: tags, advanced manupulations with comments, properties, etc. + +For detailed desription check [calculator/README.md](calculator/README.md) diff --git a/examples/calculator/CMakeLists.txt b/examples/calculator/CMakeLists.txt new file mode 100644 index 0000000000..7f6ab6cefb --- /dev/null +++ b/examples/calculator/CMakeLists.txt @@ -0,0 +1,77 @@ +# Copyright (C) 2016-2023 HERE Europe B.V. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# License-Filename: LICENSE + +cmake_minimum_required(VERSION 3.12) + +project(gluecodium.calculator) + +option(ENABLE_APP "Enables iOS app which demostrates how to use the generated code" ON) + +set(CMAKE_CXX_STANDARD 17) + +if(CMAKE_GENERATOR STREQUAL "Xcode") + enable_language(Swift) +endif() + +# Path to Gluecodium CMake wrapper +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../cmake/modules") + +include(gluecodium/Gluecodium) + +# Android requires C++ code to be built as shared library. +# For iOS it's recommended (but not required) to do Framework because it's consistent +# to Android setup and simplifies CMake scripts and it allows to use framework with +# Xcode projects which are not originated from CMake (for example cocoapods or +# generated by Xcode itself). +add_library(mylibrary SHARED "${CMAKE_CURRENT_LIST_DIR}/cpp/CalculatorImpl.cpp") + +# C++ (cpp) generator usually always necessary, android and swift depend on current platform. +set (_generators cpp) +if (ANDROID) + list (APPEND _generators android) +elseif (CMAKE_GENERATOR STREQUAL "Xcode") + list (APPEND _generators swift) +endif() + +# Add step to generate sources. Lime files are specified below. +gluecodium_generate(mylibrary GENERATORS ${_generators}) + +# Gluecodium has plenty options which can be configured with target properties +# prfixed with `GLUECODIUM_`. To learn all the properties configure the project +# with `-DGLUECODIUM_PRINT_KNOWN_PROPERTIES=ON` parameter. +set_target_properties(mylibrary PROPERTIES + # Not required, but highly desired Java annotations. + GLUECODIUM_JAVA_NONNULL_ANNOTATION "androidx.annotation.NonNull" + GLUECODIUM_JAVA_NULLABLE_ANNOTATION "androidx.annotation.Nullable" + + # Set prefix for java package. + GLUECODIUM_JAVA_PACKAGE "com") + +# This is the most convenient way to add lime files from which Gluecodium generates code. +set_property(TARGET mylibrary APPEND PROPERTY GLUECODIUM_LIME_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/lime/Calculator.lime") + +if (CMAKE_GENERATOR STREQUAL "Xcode") + # Make CMake to build framework. + set_target_properties(mylibrary PROPERTIES + FRAMEWORK TRUE + XCODE_ATTRIBUTE_DEFINES_MODULE YES) + + if(ENABLE_APP AND IOS) + add_subdirectory("ios") + endif() +endif() diff --git a/examples/calculator/README.md b/examples/calculator/README.md new file mode 100644 index 0000000000..76ac578628 --- /dev/null +++ b/examples/calculator/README.md @@ -0,0 +1,150 @@ +# Calculator + +This simple application demonstrates how to use basic entities like `struct`, `class`, `interface`, `exception`, `lambda`, `optional values`, `documentation`, `platform-only documentation` and interact between Java, Swift and C++. Flutter is out of this example. + +# Requirements + +* For Apple: Xcode, CMake the newer the better. +* For Android: CMake, Android SDK, optionally Android Studio + +# How to build + +## Android + +Option 1. Command line + +Build and install the app from command line: + +``` +cd android +./gradlew installDebug +``` +Then find "Gluecodium calculator" on our simulator or device and run. + +Option 2. IDE + +Run Android Studio not older than Giraffe, open directory `android`, press "Run". + +## Apple +First of all Xcode solution must be configured with CMake + +Generated code and implementation for interfaces are built as framework. For iOS there is also test application which uses this framework. + +Create directory and `cd` there. There are many options to configure solution for Xcode: +``` +# iOS, simulator, M1 +cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS + +# iOS, simulator, Intel +cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS + +# iOS, simulator, both M1 and Intel (i.e. lipoed) +cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS + +# iOS, device +cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_SYSTEM_NAME=iOS + +# MacOS. Only framework will be built, without test application. +cmake path/to/example -GXcode +``` +Optionally parameter `-DENABLE_APP=OFF` can be passed to skip building the test application. In this case only framework is built which can be used in another existing Xcode project. + +To build framework from console: +``` +# Build debug +cmake --build . --target mylibrary + +# Build release +cmake --build . --target mylibrary --config Release +``` + +Test application is easier to build from Xcode. Open Xcode, opern generated project, click "Run" button. + +# Highlights + +## CMake + +File [CMakeLists.txt](CMakeLists.txt) describes library and adds compilation step to generate code with Gluecodium. + +Path to gluecodium cmake files must be specified before including gluecodium cmake files. In examples it's hardcoded, but can be specified outside: +``` +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../cmake/modules") +``` + +Gluecodium options can be configured with target properties preficed with `GLUECODIUM_`. To learn all the properties configure the project with `-DGLUECODIUM_PRINT_KNOWN_PROPERTIES=ON` parameter. + +The most convenient way to add lime files is to append them to `GLUECODIUM_LIME_SOURCES` property: +``` +set_property(TARGET target APPEND PROPERTY GLUECODIUM_LIME_SOURCES + "path/to/file1.lime" + "path/to/file2.lime") +``` + +## Lime file + +File [lime/Calculator.lime](lime/Calculator.lime) describes entities to be generated by Gluecoium. Syntax should look like very simplified Kotlin or Swift. Text which begins with `#` is local comment and not processed by Gluecodium somehow special. Text which begins with `//` is official documentation and Gluecodium uses it to generate Javadoc/Jazzy/Doxygen compatible documentation for related generated code. Check the lime file for more detailed description for used entites. + +For simplification everything is described in single file, but it's possible to use as many lime files. In case when type from different package is referenced then either fully qualified name or `import` should be used. + +Only simple subset of features are used, for more please refer to Gluecodium documentation. + +## Android sources + +Major part of android sources are generated automatically by Android Studio when simple project for this example was created. The most significant changes are the following: + +Gradle build script [android/app/build.gradle](android/app/build.gradle). It specifies path to folder to generate code using CMake variable `GLUECODIUM_BASE_OUTPUT_DIR_DEFAULT`: +``` +externalNativeBuild { + cmake { + // Put generated sources outside of default build directory because Android Gradle Plugin + // makes build directory for C++ using some unpredictable random-like path. + // Generated Java sources should be added to `sourceSet` below, so it's better to specify + // and know where code is generated. + arguments "-DGLUECODIUM_BASE_OUTPUT_DIR_DEFAULT=${project.buildDir}/generated/gluecodium" + } +} +``` + +Below directories with generated sources are added to Java source set. Note that this is based on the directory which is specified with `GLUECODIUM_BASE_OUTPUT_DIR_DEFAULT` and C++ target name: +``` +sourceSets { + main { + // Add generated Java code. + java.srcDirs += ["${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/common/android", + "${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/main/android"] + } +} +``` + +Relative path to [CMakeLists.txt](CMakeLists.txt) is specified: +``` +externalNativeBuild { + cmake { + // Path to root `CMakeLists.txt` + path file('../../CMakeLists.txt') + version '3.22.1+' + } +} +``` + +And finally it's necessary to compile Java code only after C++ is compiled (and Java sources are generated): +``` +tasks.configureEach { task -> + // Java compilation depends on .java created by Gluecodium, + // so depend Java compilation task on build C++ task. + if (task.name == 'javaPreCompileDebug') { + task.dependsOn 'externalNativeBuildDebug' + } + if (task.name == 'javaPreCompileRelease') { + task.dependsOn 'externalNativeBuildRelease' + } +} +``` + +Usage of generated code is in [android/app/src/main/java/com/gluecodium/calculator/MainActivity.java](android/app/src/main/java/com/gluecodium/calculator/MainActivity.java) + +## iOS + +Optional CMake file [ios/CMakeLists.txt](ios/CMakeLists.txt) describes simple iOS application. It's optional because the framework which is built in the root file [CMakeLists.txt](CMakeLists.txt) can be used in Xcode project which is created by Xcode itself or by for example `cocoapods`. There is nothing specific for Gluecodium. + +Usage of generated code is in [ios/ContentView.swift](ios/ContentView.swift). diff --git a/examples/calculator/android/.gitignore b/examples/calculator/android/.gitignore new file mode 100644 index 0000000000..aa724b7707 --- /dev/null +++ b/examples/calculator/android/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/examples/calculator/android/app/.gitignore b/examples/calculator/android/app/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/examples/calculator/android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/examples/calculator/android/app/build.gradle b/examples/calculator/android/app/build.gradle new file mode 100644 index 0000000000..4293df88ab --- /dev/null +++ b/examples/calculator/android/app/build.gradle @@ -0,0 +1,77 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.gluecodium.calculator' + compileSdk 33 + + defaultConfig { + applicationId "com.gluecodium.calculator" + minSdk 24 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + // Put generated sources outside of default build directory because Android Gradle Plugin + // makes build directory for C++ using some unpredictable random-like path. + // Generated Java sources should be added to `sourceSet` below, so it's better to specify + // and know where code is generated. + arguments "-DGLUECODIUM_BASE_OUTPUT_DIR_DEFAULT=${project.buildDir}/generated/gluecodium" + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + sourceSets { + main { + // Add generated Java code. + java.srcDirs += ["${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/common/android", + "${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/main/android"] + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + externalNativeBuild { + cmake { + // Path to root `CMakeLists.txt` + path file('../../CMakeLists.txt') + version '3.22.1+' + } + } + buildFeatures { + viewBinding true + } +} + +tasks.configureEach { task -> + // Java compilation depends on .java created by Gluecodium, + // so depend Java compilation task on build C++ task. + if (task.name == 'javaPreCompileDebug') { + task.dependsOn 'externalNativeBuildDebug' + } + if (task.name == 'javaPreCompileRelease') { + task.dependsOn 'externalNativeBuildRelease' + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.8.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} \ No newline at end of file diff --git a/examples/calculator/android/app/proguard-rules.pro b/examples/calculator/android/app/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/examples/calculator/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/examples/calculator/android/app/src/androidTest/java/com/gluecodium/calculator/ExampleInstrumentedTest.java b/examples/calculator/android/app/src/androidTest/java/com/gluecodium/calculator/ExampleInstrumentedTest.java new file mode 100644 index 0000000000..69e905f875 --- /dev/null +++ b/examples/calculator/android/app/src/androidTest/java/com/gluecodium/calculator/ExampleInstrumentedTest.java @@ -0,0 +1,40 @@ +// Copyright (C) 2016-2023 HERE Europe B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// License-Filename: LICENSE + +package com.gluecodium.calculator; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + } +} \ No newline at end of file diff --git a/examples/calculator/android/app/src/main/AndroidManifest.xml b/examples/calculator/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1b8af2b1b6 --- /dev/null +++ b/examples/calculator/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/calculator/android/app/src/main/java/com/gluecodium/calculator/MainActivity.java b/examples/calculator/android/app/src/main/java/com/gluecodium/calculator/MainActivity.java new file mode 100644 index 0000000000..1afb833789 --- /dev/null +++ b/examples/calculator/android/app/src/main/java/com/gluecodium/calculator/MainActivity.java @@ -0,0 +1,197 @@ +// Copyright (C) 2016-2023 HERE Europe B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// License-Filename: LICENSE + +package com.gluecodium.calculator; + +import static android.widget.Toast.LENGTH_LONG; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import android.graphics.Color; +import android.os.Bundle; +import android.util.Pair; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.gluecodium.calculator.databinding.ActivityMainBinding; + +public class MainActivity extends AppCompatActivity { + + // Used to load the 'libmylibrary.so' library on application startup. + static { + System.loadLibrary("mylibrary"); + } + + final private Calculator calculator = new Calculator(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + com.gluecodium.calculator.databinding.ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + findViewById(R.id.button_plus).setOnClickListener(view -> { + final Pair values = readValues(); + if (values == null) { + return; + } + try { + final int result = calculator.summarize(values.first, values.second); + final String resultText = values.first + " + " + values.second + " = " + result; + showResult(resultText); + } catch (Calculator.CalculatorExceptionException e) { + showError("Failure: " + e.getMessage()); + } + }); + + findViewById(R.id.button_minus).setOnClickListener(view -> { + final Pair values = readValues(); + if (values == null) { + return; + } + calculator.subtract(values.first, values.second, (error, result) -> { + if (error != null) { + showError("Failure: " + error); + } else { + final String resultText = values.first + " - " + values.second + " = " + result; + showResult(resultText); + } + }); + }); + + findViewById(R.id.button_multiply).setOnClickListener(view -> { + final Pair values = readValues(); + if (values == null) { + return; + } + + calculator.multiply(values.first, values.second, new Calculator.MultiplyCallback() { + @Override + public void onError(@NonNull Calculator.CalculatorError error) { + showError("Failure: " + error.toString()); + } + + @Override + public void onResult(int result) { + final String resultText = values.first + " x " + values.second + " = " + result; + showResult(resultText); + } + }); + }); + + findViewById(R.id.button_divide).setOnClickListener(view -> { + final Pair values = readValues(); + if (values == null) { + return; + } + final Calculator.DivideResult result = calculator.divide( + new Calculator.DivideArguments(values.first, values.second)); + if (result.error != null) { + showError("Failure: " + result.error.toString()); + } else { + final String resultText = values.first + " / " + values.second + " = " + result.result; + showResult(resultText); + } + }); + + findViewById(R.id.button_min).setOnClickListener(view -> { + final Pair values = readValues(); + if (values == null) { + return; + } + final Calculator.MinResultRetriever resultRetriever = calculator.min(values.first, values.second); + final int result = resultRetriever.getResult(); + final String resultText = "min(" + values.first + ", " + values.second + ") = " + result; + showResult(resultText); + }); + + findViewById(R.id.button_max).setOnClickListener(view -> { + Integer number1 = null; + Integer number2 = null; + + final String inputText1 = ((EditText)(findViewById(R.id.edit_number_1))).getText().toString(); + if (!inputText1.isEmpty()) { + number1 = Integer.parseInt(inputText1); + } + + final String inputText2 = ((EditText)(findViewById(R.id.edit_number_2))).getText().toString(); + if (!inputText2.isEmpty()) { + number2 = Integer.parseInt(inputText2); + } + + final Integer result = calculator.max(number1, number2); + + if (result != null) { + String resultText = "max("; + if (number1 != null && number2 != null) { + resultText += number1 + ", " + number2; + } else if (number1 != null) { + resultText += number1; + } else if (number2 != null) { + resultText += number2; + } + resultText += ") = " + result; + showResult(resultText); + } else { + showResult("null"); + } + }); + } + + private Pair readValues() { + try { + final int value1 = readValue1(); + final int value2 = readValue2(); + return new Pair<>(value1, value2); + } catch (Exception e) { + Toast.makeText(getApplicationContext(), e.getMessage(), LENGTH_LONG).show(); + return null; + } + } + + private void showResult(@NonNull String resultText) { + TextView textView = findViewById(R.id.text_result); + textView.setTextColor(Color.BLACK); + textView.setText(resultText); + } + + private void showError(@NonNull String resultText) { + TextView textView = findViewById(R.id.text_result); + textView.setTextColor(Color.RED); + textView.setText(resultText); + } + + private int readValue1() { + return readValueById(R.id.edit_number_1); + } + + private int readValue2() { + return readValueById(R.id.edit_number_2); + } + + private int readValueById(int id) { + EditText editText = findViewById(id); + final String inputText = editText.getText().toString(); + if (inputText.isEmpty()) { + throw new RuntimeException("Input value is empty"); + } + return Integer.parseInt(inputText); + } +} \ No newline at end of file diff --git a/examples/calculator/android/app/src/main/res/drawable/ic_launcher_background.xml b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..07d5da9cbf --- /dev/null +++ b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/calculator/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..2b068d1146 --- /dev/null +++ b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/examples/calculator/android/app/src/main/res/layout/activity_main.xml b/examples/calculator/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..f1c2976c1e --- /dev/null +++ b/examples/calculator/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,122 @@ + + + + + + + + + +