Skip to content

Commit

Permalink
Move Appearance to TurboModule (#10682)
Browse files Browse the repository at this point in the history
* Make appearance module use codegen spec file and be a turbomodule

* Change files
  • Loading branch information
acoates-ms committed Oct 6, 2022
1 parent 70ef8ff commit 26bf492
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Make appearance module use codegen spec file and be a turbomodule",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
9 changes: 0 additions & 9 deletions vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <AppModelHelpers.h>
#include <AsyncStorageModule.h>
#include <Modules/Animated/NativeAnimatedModule.h>
#include <Modules/AppearanceModule.h>
#include <Modules/AsyncStorageModuleWin32.h>
#include <Modules/ClipboardModule.h>
#include <Modules/NativeUIManager.h>
Expand Down Expand Up @@ -39,7 +38,6 @@ std::vector<facebook::react::NativeModuleDescription> GetCoreModules(
const std::shared_ptr<facebook::react::MessageQueueThread> &batchingUIMessageQueue,
const std::shared_ptr<facebook::react::MessageQueueThread>
&jsMessageQueue, // JS engine thread (what we use for external modules)
Mso::CntPtr<AppearanceChangeListener> &&appearanceListener,
Mso::CntPtr<Mso::React::IReactContext> &&context) noexcept {
std::vector<facebook::react::NativeModuleDescription> modules;

Expand Down Expand Up @@ -72,13 +70,6 @@ std::vector<facebook::react::NativeModuleDescription> GetCoreModules(
[context = std::move(context)]() mutable { return std::make_unique<NativeAnimatedModule>(std::move(context)); },
batchingUIMessageQueue);

modules.emplace_back(
AppearanceModule::Name,
[appearanceListener = std::move(appearanceListener)]() mutable {
return std::make_unique<AppearanceModule>(std::move(appearanceListener));
},
jsMessageQueue);

// AsyncStorageModule doesn't work without package identity (it indirectly depends on
// Windows.Storage.StorageFile), so check for package identity before adding it.
modules.emplace_back(
Expand Down
2 changes: 0 additions & 2 deletions vnext/Microsoft.ReactNative/Base/CoreNativeModules.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#pragma once

#include <Modules/AppearanceModule.h>
#include <React.h>
#include <Shared/NativeModuleProvider.h>
#include <smartPtr/cntPtr.h>
Expand All @@ -26,7 +25,6 @@ struct ViewManagerProvider;
std::vector<facebook::react::NativeModuleDescription> GetCoreModules(
const std::shared_ptr<facebook::react::MessageQueueThread> &batchingUIMessageQueue,
const std::shared_ptr<facebook::react::MessageQueueThread> &jsMessageQueue,
Mso::CntPtr<AppearanceChangeListener> &&appearanceListener,
Mso::CntPtr<Mso::React::IReactContext> &&context) noexcept;

} // namespace Microsoft::ReactNative
95 changes: 58 additions & 37 deletions vnext/Microsoft.ReactNative/Modules/AppearanceModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,81 @@ using Application = xaml::Application;
using ApplicationTheme = xaml::ApplicationTheme;
using UISettings = winrt::Windows::UI::ViewManagement::UISettings;

using Method = facebook::xplat::module::CxxModule::Method;

namespace Microsoft::ReactNative {

AppearanceChangeListener::AppearanceChangeListener(
const Mso::React::IReactContext &context,
const Mso::React::IDispatchQueue2 &uiQueue) noexcept
: m_queue(&uiQueue), m_context(&context) {
static const React::ReactPropertyId<ApplicationTheme> &AppearanceCurrentThemePropertyId() noexcept {
static const React::ReactPropertyId<ApplicationTheme> prop{L"ReactNative.Appearance", L"ApplicationTheme"};
return prop;
}

void Appearance::Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept {
m_context = reactContext;

m_context.UIDispatcher().Post([wkThis = weak_from_this()]() {
if (auto pThis = wkThis.lock()) {
pThis->RequeryTheme();
}
});

// UISettings will notify us on a background thread regardless of where we construct it or register for events.
// Redirect callbacks to the UI thread where we can check app theme.
m_revoker = m_uiSettings.ColorValuesChanged(
winrt::auto_revoke,
[wkThis = weak_from_this(), reactContext](const auto & /*sender*/, const auto & /*args*/) noexcept {
if (auto pThis = wkThis.lock()) {
reactContext.UIDispatcher().Post([wkThis]() noexcept {
if (auto pThis = wkThis.lock()) {
pThis->RequeryTheme();
}
});
}
});
}

ApplicationTheme Appearance::GetCurrentTheme() noexcept {
assert(m_context.UIDispatcher().HasThreadAccess()); // xaml::Application is only accessible on the UI thread
if (auto currentApp = xaml::TryGetCurrentApplication()) {
m_currentTheme = currentApp.RequestedTheme();

// UISettings will notify us on a background thread regardless of where we construct it or register for events.
// Redirect callbacks to the UI thread where we can check app theme.
m_revoker = m_uiSettings.ColorValuesChanged(
winrt::auto_revoke, [weakThis{Mso::WeakPtr(this)}](const auto & /*sender*/, const auto & /*args*/) noexcept {
if (auto strongThis = weakThis.GetStrongPtr()) {
strongThis->m_queue->Post([weakThis]() noexcept {
if (auto strongThis = weakThis.GetStrongPtr()) {
strongThis->OnColorValuesChanged();
}
});
}
});
return currentApp.RequestedTheme();
}
}

const char *AppearanceChangeListener::GetColorScheme() const noexcept {
return ToString(m_currentTheme);
return ApplicationTheme::Light;
}

const char *AppearanceChangeListener::ToString(ApplicationTheme theme) noexcept {
const char *Appearance::ToString(ApplicationTheme theme) noexcept {
return theme == ApplicationTheme::Dark ? "dark" : "light";
}

void AppearanceChangeListener::OnColorValuesChanged() noexcept {
auto newTheme = Application::Current().RequestedTheme();
if (m_currentTheme != newTheme) {
m_currentTheme = newTheme;
void Appearance::RequeryTheme() noexcept {
auto theme = GetCurrentTheme();
auto oldThemeBoxed =
m_context.Properties().Handle().Set(AppearanceCurrentThemePropertyId().Handle(), winrt::box_value(theme));
auto oldTheme = winrt::unbox_value_or<ApplicationTheme>(oldThemeBoxed, ApplicationTheme::Light);

m_context->CallJSFunction(
"RCTDeviceEventEmitter", "emit", folly::dynamic::array("appearanceChanged", ToString(m_currentTheme)));
if (oldTheme != theme) {
appearanceChanged(ToString(theme));
}
}

AppearanceModule::AppearanceModule(Mso::CntPtr<AppearanceChangeListener> &&appearanceListener) noexcept
: m_changeListener(std::move(appearanceListener)) {}
void Appearance::InitOnUIThread(const Mso::React::IReactContext &context) noexcept {
xaml::ApplicationTheme theme = ApplicationTheme::Light;
if (auto currentApp = xaml::TryGetCurrentApplication()) {
theme = currentApp.RequestedTheme();
}

winrt::Microsoft::ReactNative::ReactPropertyBag pb{context.Properties()};
pb.Set(AppearanceCurrentThemePropertyId(), theme);
}

std::optional<std::string> Appearance::getColorScheme() noexcept {
return ToString(*(m_context.Properties().Get(AppearanceCurrentThemePropertyId())));
}

std::string AppearanceModule::getName() {
return AppearanceModule::Name;
void Appearance::addListener(std::string eventName) noexcept {
// no-op
}

std::vector<Method> AppearanceModule::getMethods() {
return {Method(
"getColorScheme", [this](folly::dynamic /*args*/) { return m_changeListener->GetColorScheme(); }, SyncTag)};
void Appearance::removeListeners(double count) noexcept {
// no-op
}

} // namespace Microsoft::ReactNative
57 changes: 25 additions & 32 deletions vnext/Microsoft.ReactNative/Modules/AppearanceModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,44 @@

#pragma once

#include <cxxreact/CxxModule.h>
#include <eventWaitHandle/eventWaitHandle.h>

#include <winrt/Windows.UI.ViewManagement.h>

#include <IReactDispatcher.h>
#include "../../codegen/NativeAppearanceSpec.g.h"
#include <React.h>
#include <object/refCountedObject.h>
#include "IReactInstance.h"
#include <winrt/Windows.UI.ViewManagement.h>

namespace Microsoft::ReactNative {

// Listens for the current theme on the UI thread, storing the most recent. Will emit JS events on Appearance change.
class AppearanceChangeListener final : public Mso::RefCountedObject<Mso::RefCountStrategy::WeakRef, Mso::IRefCounted> {
REACT_MODULE(Appearance)
struct Appearance : std::enable_shared_from_this<Appearance> {
using ApplicationTheme = xaml::ApplicationTheme;
using UISettings = winrt::Windows::UI::ViewManagement::UISettings;
using ModuleSpec = ReactNativeSpecs::AppearanceSpec;

public:
AppearanceChangeListener(
const Mso::React::IReactContext &context,
const Mso::React::IDispatchQueue2 &uiQueue) noexcept;
const char *GetColorScheme() const noexcept;
REACT_INIT(Initialize)
void Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept;

private:
static const char *ToString(ApplicationTheme theme) noexcept;
void OnColorValuesChanged() noexcept;
REACT_SYNC_METHOD(getColorScheme)
std::optional<std::string> getColorScheme() noexcept;

Mso::CntPtr<const Mso::React::IDispatchQueue2> m_queue;
UISettings m_uiSettings;
UISettings::ColorValuesChanged_revoker m_revoker;
std::atomic<ApplicationTheme> m_currentTheme;
Mso::CntPtr<const Mso::React::IReactContext> m_context;
std::weak_ptr<IReactInstance> m_weakReactInstance;
};
REACT_METHOD(addListener)
void addListener(std::string eventName) noexcept;

class AppearanceModule final : public facebook::xplat::module::CxxModule {
public:
static constexpr const char *Name = "Appearance";
REACT_METHOD(removeListeners)
void removeListeners(double count) noexcept;

AppearanceModule(Mso::CntPtr<AppearanceChangeListener> &&appearanceListener) noexcept;
std::string getName() override;
std::vector<Method> getMethods() override;
REACT_EVENT(appearanceChanged);
std::function<void(std::string const &)> appearanceChanged;

// This function allows the module to get the current theme on the UI thread before it is requested by any JS thread
static void InitOnUIThread(const Mso::React::IReactContext &context) noexcept;

private:
Mso::CntPtr<AppearanceChangeListener> m_changeListener;
static const char *ToString(ApplicationTheme theme) noexcept;
ApplicationTheme GetCurrentTheme() noexcept;
void RequeryTheme() noexcept;

winrt::Microsoft::ReactNative::ReactContext m_context;
UISettings m_uiSettings;
UISettings::ColorValuesChanged_revoker m_revoker;
};

} // namespace Microsoft::ReactNative
10 changes: 6 additions & 4 deletions vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ void ReactInstanceWin::LoadModules(
registerTurboModule(
L"Alert", winrt::Microsoft::ReactNative::MakeTurboModuleProvider<::Microsoft::ReactNative::Alert>());

registerTurboModule(
L"Appearance", winrt::Microsoft::ReactNative::MakeTurboModuleProvider<::Microsoft::ReactNative::Appearance>());

registerTurboModule(
L"AppState", winrt::Microsoft::ReactNative::MakeTurboModuleProvider<::Microsoft::ReactNative::AppState>());

Expand Down Expand Up @@ -383,8 +386,7 @@ void ReactInstanceWin::Initialize() noexcept {
Microsoft::ReactNative::AppThemeHolder::InitAppThemeHolder(strongThis->GetReactContext());
Microsoft::ReactNative::I18nManager::InitI18nInfo(
winrt::Microsoft::ReactNative::ReactPropertyBag(strongThis->Options().Properties));
strongThis->m_appearanceListener = Mso::Make<Microsoft::ReactNative::AppearanceChangeListener>(
strongThis->GetReactContext(), *(strongThis->m_uiQueue));
Microsoft::ReactNative::Appearance::InitOnUIThread(strongThis->GetReactContext());
Microsoft::ReactNative::DeviceInfoHolder::InitDeviceInfoHolder(strongThis->GetReactContext());

#endif // CORE_ABI
Expand Down Expand Up @@ -429,8 +431,8 @@ void ReactInstanceWin::Initialize() noexcept {
#else
// Acquire default modules and then populate with custom modules.
// Note that some of these have custom thread affinity.
std::vector<facebook::react::NativeModuleDescription> cxxModules = Microsoft::ReactNative::GetCoreModules(
m_batchingUIThread, m_jsMessageThread.Load(), std::move(m_appearanceListener), m_reactContext);
std::vector<facebook::react::NativeModuleDescription> cxxModules =
Microsoft::ReactNative::GetCoreModules(m_batchingUIThread, m_jsMessageThread.Load(), m_reactContext);
#endif

auto nmp = std::make_shared<winrt::Microsoft::ReactNative::NativeModulesProvider>();
Expand Down
3 changes: 0 additions & 3 deletions vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,6 @@ class ReactInstanceWin final : public Mso::ActiveObject<IReactInstanceInternal>
std::shared_ptr<facebook::react::MessageQueueThread> m_batchingUIThread;

std::shared_ptr<IRedBoxHandler> m_redboxHandler;
#ifndef CORE_ABI
Mso::CntPtr<Microsoft::ReactNative::AppearanceChangeListener> m_appearanceListener;
#endif
Mso::CntPtr<Mso::React::IDispatchQueue2> m_uiQueue;
std::deque<JSCallEntry> m_jsCallQueue;

Expand Down

0 comments on commit 26bf492

Please sign in to comment.