Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
project(NitroText)
cmake_minimum_required(VERSION 3.9.0)

set (PACKAGE_NAME NitroText)
set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 20)

# Enable Raw Props parsing in react-native (for Nitro Views)
add_compile_options(-DRN_SERIALIZABLE_STATE=1)

# Define C++ library and add all sources
add_library(${PACKAGE_NAME} SHARED
src/main/cpp/cpp-adapter.cpp
)

# Add Nitrogen specs :)
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroText+autolinking.cmake)

# Set up local includes
include_directories(
"src/main/cpp"
"../cpp"
)

# Add custom ShadowNode implementation
target_sources(
${PACKAGE_NAME} PRIVATE
../cpp/NitroHtmlUtils.cpp
../cpp/NitroHtmlUtils.hpp
../cpp/NitroTextShadowNode.cpp
../cpp/NitroTextShadowNode.hpp
../cpp/NitroTextComponentDescriptor.cpp
../cpp/NitroTextComponentDescriptor.hpp
src/main/cpp/NitroTextRegisterProvider.cpp
..
)

find_library(LOG_LIB log)

# Link all libraries together
target_link_libraries(
${PACKAGE_NAME}
${LOG_LIB}
android # <-- Android core
)
148 changes: 148 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
buildscript {
repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:8.12.1"
}
}

def reactNativeArchitectures() {
def value = rootProject.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}

apply plugin: "com.android.library"
apply plugin: 'org.jetbrains.kotlin.android'
apply from: '../nitrogen/generated/android/NitroText+autolinking.gradle'

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroText_" + name]
}

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroText_" + name]).toInteger()
}

android {
namespace "com.nitrotext"

ndkVersion getExtOrDefault("ndkVersion")
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")

defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()

externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
abiFilters (*reactNativeArchitectures())

buildTypes {
debug {
cppFlags "-O1 -g"
}
release {
cppFlags "-O2"
}
}
}
}
}

externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

packagingOptions {
excludes = [
"META-INF",
"META-INF/**",
"**/libc++_shared.so",
"**/libfbjni.so",
"**/libjsi.so",
"**/libfolly_json.so",
"**/libfolly_runtime.so",
"**/libglog.so",
"**/libhermes.so",
"**/libhermes-executor-debug.so",
"**/libhermes_executor.so",
"**/libreactnative.so",
"**/libreactnativejni.so",
"**/libturbomodulejsijni.so",
"**/libreact_nativemodule_core.so",
"**/libjscexecutor.so"
]
}

buildFeatures {
buildConfig true
prefab true
}

buildTypes {
release {
minifyEnabled false
}
}

lintOptions {
disable "GradleCompatible"
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

sourceSets {
main {
if (isNewArchitectureEnabled()) {
java.srcDirs += [
// React Codegen files
"${project.buildDir}/generated/source/codegen/java"
]
}
}
}
}

repositories {
mavenCentral()
google()
}


dependencies {
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"

// Add a dependency on NitroModules
implementation project(":react-native-nitro-modules")

}

if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "NitroText"
codegenJavaPackageName = "com.nitrotext"
}
}
5 changes: 5 additions & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
NitroText_kotlinVersion=2.0.21
NitroText_minSdkVersion=29
NitroText_targetSdkVersion=34
NitroText_compileSdkVersion=34
NitroText_ndkVersion=27.1.12297006
2 changes: 2 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
25 changes: 25 additions & 0 deletions android/src/main/cpp/NitroTextRegisterProvider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// NitroTextRegisterProvider.cpp
// Registers custom ComponentDescriptor for NitroText on Android
//

#include "NitroTextRegisterProvider.hpp"

#include <react/fabric/CoreComponentsRegistry.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include "../../../cpp/NitroTextComponentDescriptor.hpp"

namespace margelo::nitro::nitrotext {

// Call this from JNI_OnLoad after nitrogen initialization
void registerNitroTextComponentDescriptor() {
using namespace facebook::react;
using margelo::nitro::nitrotext::views::NitroTextComponentDescriptor;

auto provider = concreteComponentDescriptorProvider<NitroTextComponentDescriptor>();
auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
// Add/override provider for component name "NitroText" (HybridNitroTextComponentName)
providerRegistry->add(provider);
}

} // namespace margelo::nitro::nitrotext
13 changes: 13 additions & 0 deletions android/src/main/cpp/NitroTextRegisterProvider.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// NitroTextRegisterProvider.hpp
// Declares helper to register custom ComponentDescriptor
//

#pragma once

namespace margelo::nitro::nitrotext {

void registerNitroTextComponentDescriptor();

}

8 changes: 8 additions & 0 deletions android/src/main/cpp/cpp-adapter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <jni.h>
#include "NitroTextOnLoad.hpp"
#include "NitroTextRegisterProvider.hpp"

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
margelo::nitro::nitrotext::registerNitroTextComponentDescriptor();
return margelo::nitro::nitrotext::initialize(vm);
}
47 changes: 47 additions & 0 deletions android/src/main/java/com/nitrotext/HtmlLinkMovementMethod.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.nitrotext

import android.text.Selection
import android.text.Spannable
import android.text.method.MovementMethod
import android.text.style.ClickableSpan
import android.view.MotionEvent
import android.widget.TextView

internal object HtmlLinkMovementMethod : MovementMethod {
override fun initialize(widget: TextView?, text: Spannable?) = Unit
override fun onKeyDown(widget: TextView?, text: Spannable?, keyCode: Int, event: android.view.KeyEvent?) = false
override fun onKeyUp(widget: TextView?, text: Spannable?, keyCode: Int, event: android.view.KeyEvent?) = false
override fun onKeyOther(widget: TextView?, text: Spannable?, event: android.view.KeyEvent?) = false
override fun onTrackballEvent(widget: TextView?, text: Spannable?, event: MotionEvent?) = false
override fun onTakeFocus(widget: TextView?, text: Spannable?, direction: Int) = Unit
override fun canSelectArbitrarily() = false
override fun onGenericMotionEvent(widget: TextView?, text: Spannable?, event: MotionEvent?) = false

override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
val action = event.action

if (action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_DOWN) {
return false
}

val layout = widget.layout ?: return false
val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX
val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY

val line = layout.getLineForVertical(y)
val offset = layout.getOffsetForHorizontal(line, x.toFloat())

val links = buffer.getSpans(offset, offset, ClickableSpan::class.java)
if (links.isNotEmpty()) {
val link = links[0]
if (action == MotionEvent.ACTION_UP) {
link.onClick(widget)
} else {
Selection.setSelection(buffer, buffer.getSpanStart(link), buffer.getSpanEnd(link))
}
return true
}

return false
}
}
Loading