mirror of
https://github.com/fcitx5-android/fcitx5-android.git
synced 2026-08-02 04:44:35 +08:00
Merge branch 'master' into feat/swip-clear
This commit is contained in:
commit
06bd39eec8
@ -44,7 +44,7 @@ add_library(pinyin-customphrase STATIC "${CHINESE_ADDONS_PINYIN_DIR}/customphras
|
||||
target_include_directories(pinyin-customphrase INTERFACE "${CHINESE_ADDONS_PINYIN_DIR}")
|
||||
target_link_libraries(pinyin-customphrase PRIVATE Fcitx5::Utils LibIME::Core)
|
||||
|
||||
add_library(native-lib SHARED native-lib.cpp)
|
||||
add_library(native-lib SHARED native-lib.cpp androidaddonloader/androidaddonloader.cpp)
|
||||
target_link_libraries(native-lib
|
||||
log
|
||||
libuv::uv_a
|
||||
|
||||
102
app/src/main/cpp/androidaddonloader/androidaddonloader.cpp
Normal file
102
app/src/main/cpp/androidaddonloader/androidaddonloader.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* SPDX-FileCopyrightText: Copyright 2016-2016 CSSlayer <wengxt@gmail.com>
|
||||
* SPDX-FileCopyrightText: Copyright 2023-2025 Fcitx5 for Android Contributors
|
||||
* SPDX-FileComment: Modified from https://github.com/fcitx/fcitx5/blob/5.1.14/src/lib/fcitx/addonloader.cpp
|
||||
*/
|
||||
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fcitx-utils/flags.h>
|
||||
#include <fcitx-utils/library.h>
|
||||
#include <fcitx-utils/log.h>
|
||||
#include <fcitx-utils/standardpaths.h>
|
||||
#include <fcitx-utils/stringutils.h>
|
||||
#include <fcitx/addoninfo.h>
|
||||
#include <fcitx/addoninstance.h>
|
||||
|
||||
#define FCITX_LIBRARY_SUFFIX ".so"
|
||||
|
||||
#include "androidaddonloader.h"
|
||||
|
||||
namespace fcitx {
|
||||
|
||||
AddonInstance *AndroidSharedLibraryLoader::load(const AddonInfo &info,
|
||||
AddonManager *manager) {
|
||||
auto iter = registry_.find(info.uniqueName());
|
||||
if (iter == registry_.end()) {
|
||||
std::vector<std::string> libnames =
|
||||
stringutils::split(info.library(), ";");
|
||||
|
||||
if (libnames.empty()) {
|
||||
FCITX_ERROR() << "Failed to parse Library field: " << info.library()
|
||||
<< " for addon " << info.uniqueName();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Library> libraries;
|
||||
for (std::string_view libname: libnames) {
|
||||
Flags<LibraryLoadHint> flag = LibraryLoadHint::DefaultHint;
|
||||
if (stringutils::consumePrefix(libname, "export:")) {
|
||||
flag |= LibraryLoadHint::ExportExternalSymbolsHint;
|
||||
}
|
||||
const auto file =
|
||||
stringutils::concat(libname, FCITX_LIBRARY_SUFFIX);
|
||||
const auto libraryPaths = standardPaths_.locateAll(
|
||||
StandardPathsType::Addon, file);
|
||||
if (libraryPaths.empty()) {
|
||||
FCITX_ERROR() << "Could not locate library " << file
|
||||
<< " for addon " << info.uniqueName() << ".";
|
||||
}
|
||||
bool loaded = false;
|
||||
for (const auto &libraryPath: libraryPaths) {
|
||||
Library library(libraryPath);
|
||||
if (library.load(flag)) {
|
||||
libraries.push_back(std::move(library));
|
||||
loaded = true;
|
||||
break;
|
||||
}
|
||||
FCITX_ERROR()
|
||||
<< "Failed to load library for addon " << info.uniqueName()
|
||||
<< " on " << libraryPath << ". Error: " << library.error();
|
||||
}
|
||||
if (!loaded) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (libraries.size() == libnames.size()) {
|
||||
try {
|
||||
registry_.emplace(info.uniqueName(),
|
||||
std::make_unique<AndroidSharedLibraryFactory>(
|
||||
info, std::move(libraries)));
|
||||
} catch (const std::exception &e) {
|
||||
FCITX_ERROR() << "Failed to initialize addon factory for addon "
|
||||
<< info.uniqueName() << ". Error: " << e.what();
|
||||
}
|
||||
iter = registry_.find(info.uniqueName());
|
||||
}
|
||||
}
|
||||
|
||||
if (iter == registry_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try {
|
||||
return iter->second->factory()->create(manager);
|
||||
} catch (const std::exception &e) {
|
||||
FCITX_ERROR() << "Failed to create addon: " << info.uniqueName() << " "
|
||||
<< e.what();
|
||||
} catch (...) {
|
||||
FCITX_ERROR() << "Failed to create addon: " << info.uniqueName();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace fcitx
|
||||
85
app/src/main/cpp/androidaddonloader/androidaddonloader.h
Normal file
85
app/src/main/cpp/androidaddonloader/androidaddonloader.h
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* SPDX-FileCopyrightText: Copyright 2016-2016 CSSlayer <wengxt@gmail.com>
|
||||
* SPDX-FileCopyrightText: Copyright 2023-2025 Fcitx5 for Android Contributors
|
||||
* SPDX-FileComment: Modified from https://github.com/fcitx/fcitx5/blob/5.1.14/src/lib/fcitx/addonloader_p.h
|
||||
*/
|
||||
|
||||
#ifndef FCITX5_ANDROID_ANDROIDADDONLOADER_H
|
||||
#define FCITX5_ANDROID_ANDROIDADDONLOADER_H
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fcitx-utils/library.h>
|
||||
#include <fcitx-utils/standardpaths.h>
|
||||
#include <fcitx-utils/stringutils.h>
|
||||
#include <fcitx/addonfactory.h>
|
||||
#include <fcitx/addoninfo.h>
|
||||
#include <fcitx/addoninstance.h>
|
||||
#include <fcitx/addonloader.h>
|
||||
|
||||
namespace fcitx {
|
||||
|
||||
namespace {
|
||||
constexpr char FCITX_ADDON_FACTORY_ENTRY[] = "fcitx_addon_factory_instance";
|
||||
}
|
||||
|
||||
class AndroidSharedLibraryFactory {
|
||||
public:
|
||||
explicit AndroidSharedLibraryFactory(const AddonInfo &info, std::vector<Library> libraries)
|
||||
: libraries_(std::move(libraries)) {
|
||||
std::string v2Name = stringutils::concat(FCITX_ADDON_FACTORY_ENTRY, "_",
|
||||
info.uniqueName());
|
||||
if (libraries_.empty()) {
|
||||
throw std::runtime_error("Got empty libraries.");
|
||||
}
|
||||
|
||||
// Only resolve with last library.
|
||||
auto &library = libraries_.back();
|
||||
auto *funcPtr = library.resolve(v2Name.data());
|
||||
if (!funcPtr) {
|
||||
funcPtr = library.resolve(FCITX_ADDON_FACTORY_ENTRY);
|
||||
}
|
||||
if (!funcPtr) {
|
||||
throw std::runtime_error(library.error());
|
||||
}
|
||||
auto func = Library::toFunction<AddonFactory *()>(funcPtr);
|
||||
factory_ = func();
|
||||
if (!factory_) {
|
||||
throw std::runtime_error("Failed to get a factory");
|
||||
}
|
||||
}
|
||||
|
||||
AddonFactory *factory() { return factory_; }
|
||||
|
||||
private:
|
||||
std::vector<Library> libraries_;
|
||||
AddonFactory *factory_;
|
||||
};
|
||||
|
||||
class AndroidSharedLibraryLoader : public AddonLoader {
|
||||
public:
|
||||
[[nodiscard]] std::string type() const override { return "SharedLibrary"; }
|
||||
|
||||
AddonInstance *load(const AddonInfo &info, AddonManager *manager) override;
|
||||
|
||||
private:
|
||||
// Android specific: create a new StandardPaths instance in case FCITX_ADDON_DIRS changes
|
||||
StandardPaths standardPaths_ = StandardPaths(
|
||||
"fcitx5",
|
||||
std::unordered_map<std::string, std::vector<std::filesystem::path>>{},
|
||||
Flags<StandardPathsOption>(StandardPathsOption::SkipBuiltInPath)
|
||||
);
|
||||
// Android specific end
|
||||
std::unordered_map<std::string, std::unique_ptr<AndroidSharedLibraryFactory>>
|
||||
registry_;
|
||||
};
|
||||
|
||||
} // namespace fcitx
|
||||
|
||||
#endif //FCITX5_ANDROID_ANDROIDADDONLOADER_H
|
||||
@ -40,6 +40,7 @@
|
||||
#include <boost/iostreams/stream_buffer.hpp>
|
||||
#include "customphrase.h"
|
||||
|
||||
#include "androidaddonloader/androidaddonloader.h"
|
||||
#include "androidfrontend/androidfrontend_public.h"
|
||||
#include "jni-utils.h"
|
||||
#include "nativestreambuf.h"
|
||||
@ -82,7 +83,7 @@ public:
|
||||
|
||||
void startup(const std::function<void(fcitx::AddonInstance *)> &setupCallback) {
|
||||
p_instance = std::make_unique<fcitx::Instance>(0, nullptr);
|
||||
p_instance->addonManager().registerDefaultLoader(nullptr);
|
||||
p_instance->addonManager().registerLoader(std::make_unique<fcitx::AndroidSharedLibraryLoader>());
|
||||
p_dispatcher = std::make_unique<fcitx::EventDispatcher>();
|
||||
p_dispatcher->attach(&p_instance->eventLoop());
|
||||
p_instance->initialize();
|
||||
@ -527,7 +528,10 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx(
|
||||
const std::string data_home = fcitx::stringutils::joinPath(*extData_, "data");
|
||||
const std::string usr_share = fcitx::stringutils::joinPath(*appData_, "usr", "share");
|
||||
const std::string locale_dir = fcitx::stringutils::joinPath(usr_share, "locale");
|
||||
const std::string libime_data = fcitx::stringutils::joinPath(usr_share, "libime");
|
||||
const std::string libime_data = fcitx::stringutils::concat(
|
||||
fcitx::stringutils::joinPath(*extData_, "data", "libime"), ":",
|
||||
fcitx::stringutils::joinPath(usr_share, "libime")
|
||||
);
|
||||
const std::string lua_path = fcitx::stringutils::concat(
|
||||
fcitx::stringutils::joinPath(data_home, "lua", "?.lua"), ";",
|
||||
fcitx::stringutils::joinPath(data_home, "lua", "?", "init.lua"), ";",
|
||||
@ -542,7 +546,7 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx(
|
||||
);
|
||||
|
||||
// prevent StandardPath from resolving it's hardcoded installation path
|
||||
setenv("SKIP_FCITX_PATH", "1", 1);
|
||||
// setenv("SKIP_FCITX_PATH", "1", 1);
|
||||
// for fcitx default profile [DefaultInputMethod]
|
||||
setenv("LANG", lang_.c_str(), 1);
|
||||
// for libintl-lite loading gettext .mo translations
|
||||
|
||||
@ -17,6 +17,7 @@ import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@SuppressLint("AppCompatCustomView")
|
||||
class AutoScaleTextView @JvmOverloads constructor(
|
||||
@ -129,7 +130,7 @@ class AutoScaleTextView @JvmOverloads constructor(
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
val shouldAlignLeft = gravity and Gravity.HORIZONTAL_GRAVITY_MASK == Gravity.LEFT
|
||||
if (textWidth >= contentWidth) {
|
||||
if (textWidth > contentWidth) {
|
||||
when (scaleMode) {
|
||||
Mode.None -> {
|
||||
textScaleX = 1.0f
|
||||
@ -149,9 +150,9 @@ class AutoScaleTextView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
translateX = if (shouldAlignLeft) leftAlignOffset else centerAlignOffset
|
||||
textScaleX = 1.0f
|
||||
textScaleY = 1.0f
|
||||
translateX = if (shouldAlignLeft) leftAlignOffset else centerAlignOffset
|
||||
}
|
||||
val fontHeight = (fontMetrics.bottom - fontMetrics.top) * textScaleY
|
||||
val fontOffsetY = fontMetrics.top * textScaleY
|
||||
@ -176,4 +177,8 @@ class AutoScaleTextView @JvmOverloads constructor(
|
||||
override fun getTextScaleX(): Float {
|
||||
return textScaleX
|
||||
}
|
||||
|
||||
override fun getBaseline(): Int {
|
||||
return (-fontMetrics.top * textScaleY).roundToInt()
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,13 @@ class PagedCandidatesUi(
|
||||
}
|
||||
|
||||
private val candidatesAdapter = object : RecyclerView.Adapter<UiHolder>() {
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long =
|
||||
data.candidates[position].hashCode().toLong()
|
||||
|
||||
override fun getItemCount() =
|
||||
data.candidates.size + (if (data.hasPrev || data.hasNext) 1 else 0)
|
||||
|
||||
@ -98,6 +105,7 @@ class PagedCandidatesUi(
|
||||
adapter = candidatesAdapter
|
||||
layoutManager = candidatesLayoutManager
|
||||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
itemAnimator = null
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
|
||||
@ -163,6 +163,7 @@ class HorizontalCandidateComponent :
|
||||
}
|
||||
}.apply {
|
||||
id = R.id.candidate_view
|
||||
itemAnimator = null
|
||||
adapter = this@HorizontalCandidateComponent.adapter
|
||||
layoutManager = this@HorizontalCandidateComponent.layoutManager
|
||||
addItemDecoration(FlexboxVerticalDecoration(dividerDrawable))
|
||||
|
||||
@ -21,6 +21,10 @@ import splitties.views.setPaddingDp
|
||||
open class HorizontalCandidateViewAdapter(val theme: Theme) :
|
||||
RecyclerView.Adapter<CandidateViewHolder>() {
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
var candidates: Array<String> = arrayOf()
|
||||
private set
|
||||
|
||||
@ -36,6 +40,10 @@ open class HorizontalCandidateViewAdapter(val theme: Theme) :
|
||||
|
||||
override fun getItemCount() = candidates.size
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return candidates[position].hashCode().toLong()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CandidateViewHolder {
|
||||
val ui = CandidateItemUi(parent.context, theme)
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
package org.fcitx.fcitx5.android.ui.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
@ -20,7 +19,6 @@ import org.fcitx.fcitx5.android.utils.navigateWithAnim
|
||||
|
||||
class AboutFragment : PaddingPreferenceFragment() {
|
||||
|
||||
@SuppressLint("UseKtx")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()).apply {
|
||||
addPreference(R.string.privacy_policy) {
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
package org.fcitx.fcitx5.android.ui.main
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
@ -137,7 +136,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
val aboutMenuItems = listOf(
|
||||
menu.item(R.string.faq) {
|
||||
@SuppressLint("UseKtx")
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(Const.faqUrl)))
|
||||
},
|
||||
menu.item(R.string.developer) {
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
package org.fcitx.fcitx5.android.ui.main.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.net.Uri
|
||||
@ -108,7 +107,6 @@ class PinyinDictionaryFragment : Fragment(), OnItemChangedListener<PinyinDiction
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@SuppressLint("UseKtx")
|
||||
args.uri?.let { importFromUri(Uri.parse(it)) }
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
@ -26,6 +26,9 @@ open class AndroidBaseConventionPlugin : Plugin<Project> {
|
||||
sourceCompatibility = Versions.java
|
||||
targetCompatibility = Versions.java
|
||||
}
|
||||
lint {
|
||||
disable += setOf("UseKtx")
|
||||
}
|
||||
}
|
||||
|
||||
target.tasks.withType<KotlinCompile> {
|
||||
|
||||
@ -221,7 +221,6 @@ class AboutActivity : PreferenceActivity() {
|
||||
|
||||
private fun showLicenseContent(license: License) {
|
||||
if (license.url?.isNotBlank() == true) {
|
||||
@SuppressLint("UseKtx")
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(license.url)))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user