Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Implement AppearanceModule",
"packageName": "react-native-windows",
"email": "ngerlem@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-04-16T15:41:54.563Z"
}
1 change: 1 addition & 0 deletions packages/playground/windows/playground-win32.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\..\..\vnext\Chakra\Chakra.vcxitems*{2d5d43d9-cffc-4c40-b4cd-02efb4e2742b}*SharedItemsImports = 4
..\..\..\vnext\Mso\Mso.vcxitems*{2d5d43d9-cffc-4c40-b4cd-02efb4e2742b}*SharedItemsImports = 4
..\..\..\vnext\Shared\Shared.vcxitems*{2d5d43d9-cffc-4c40-b4cd-02efb4e2742b}*SharedItemsImports = 4
..\..\..\vnext\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4
EndGlobalSection
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@
<ClInclude Include="..\ReactUWP\Modules\Animated\TrackingAnimatedNode.h" />
<ClInclude Include="..\ReactUWP\Modules\Animated\TransformAnimatedNode.h" />
<ClInclude Include="..\ReactUWP\Modules\Animated\ValueAnimatedNode.h" />
<ClInclude Include="..\ReactUwp\Modules\AppearanceModule.h" />
<ClInclude Include="..\ReactUWP\Modules\AppThemeModuleUwp.h" />
<ClInclude Include="..\ReactUWP\Modules\ClipboardModule.h" />
<ClInclude Include="..\ReactUWP\Modules\DeviceInfoModule.h" />
Expand Down Expand Up @@ -367,6 +368,7 @@
<ClCompile Include="..\ReactUWP\Modules\Animated\TrackingAnimatedNode.cpp" />
<ClCompile Include="..\ReactUWP\Modules\Animated\TransformAnimatedNode.cpp" />
<ClCompile Include="..\ReactUWP\Modules\Animated\ValueAnimatedNode.cpp" />
<ClCompile Include="..\ReactUwp\Modules\AppearanceModule.cpp" />
<ClCompile Include="..\ReactUWP\Modules\AppThemeModuleUwp.cpp" />
<ClCompile Include="..\ReactUWP\Modules\ClipboardModule.cpp" />
<ClCompile Include="..\ReactUWP\Modules\DeviceInfoModule.cpp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
<ClCompile Include="..\ReactUWP\Modules\Animated\ValueAnimatedNode.cpp">
<Filter>Modules\Animated</Filter>
</ClCompile>
<ClCompile Include="..\ReactUwp\Modules\AppearanceModule.cpp">
<Filter>Modules</Filter>
</ClCompile>
<ClCompile Include="..\ReactUWP\Modules\AppThemeModuleUwp.cpp">
<Filter>Modules</Filter>
</ClCompile>
Expand Down Expand Up @@ -401,6 +404,9 @@
<ClInclude Include="..\ReactUWP\Modules\Animated\ValueAnimatedNode.h">
<Filter>Modules\Animated</Filter>
</ClInclude>
<ClInclude Include="..\ReactUwp\Modules\AppearanceModule.h">
<Filter>Modules</Filter>
</ClInclude>
<ClInclude Include="..\ReactUWP\Modules\AppThemeModuleUwp.h">
<Filter>Modules</Filter>
</ClInclude>
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ void ReactInstanceWin::Initialize() noexcept {
strongThis->m_appTheme =
std::make_shared<react::uwp::AppTheme>(legacyInstance, strongThis->m_uiMessageThread.LoadWithLock());
strongThis->m_i18nInfo = react::uwp::I18nModule::GetI18nInfo();
strongThis->m_appearanceListener = Mso::Make<react::uwp::AppearanceChangeListener>(legacyInstance);
}
})
.Then(Queue(), [ this, weakThis = Mso::WeakPtr{this} ]() noexcept {
Expand Down Expand Up @@ -198,6 +199,7 @@ void ReactInstanceWin::Initialize() noexcept {
std::move(m_i18nInfo),
std::move(m_appState),
std::move(m_appTheme),
std::move(m_appearanceListener),
m_legacyReactInstance);

cxxModules.emplace_back(
Expand Down
2 changes: 2 additions & 0 deletions vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <Modules/AppStateModuleUwp.h>
#include <Modules/AppThemeModuleUwp.h>
#include <Modules/AppearanceModule.h>
#include <Modules/DeviceInfoModule.h>
#include <ReactUWP/Modules/I18nModule.h>
#include "UwpReactInstanceProxy.h"
Expand Down Expand Up @@ -155,6 +156,7 @@ class ReactInstanceWin final : public Mso::ActiveObject<IReactInstanceInternal,
std::shared_ptr<IRedBoxHandler> m_redboxHandler;
std::shared_ptr<react::uwp::AppTheme> m_appTheme;
std::pair<std::string, bool> m_i18nInfo{};
Mso::CntPtr<react::uwp::AppearanceChangeListener> m_appearanceListener;
std::string m_bundleRootPath;
};

Expand Down
9 changes: 9 additions & 0 deletions vnext/ReactUWP/Base/CoreNativeModules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <Modules/Animated/NativeAnimatedModule.h>
#include <Modules/AppStateModuleUwp.h>
#include <Modules/AppThemeModuleUwp.h>
#include <Modules/AppearanceModule.h>
#include <Modules/AsyncStorageModuleWin32.h>
#include <Modules/ClipboardModule.h>
#include <Modules/DeviceInfoModule.h>
Expand Down Expand Up @@ -55,6 +56,7 @@ std::vector<facebook::react::NativeModuleDescription> GetCoreModules(
I18nModule::I18nInfo &&i18nInfo,
std::shared_ptr<facebook::react::AppState> &&appstate,
std::shared_ptr<react::uwp::AppTheme> &&appTheme,
Mso::CntPtr<AppearanceChangeListener> &&appearanceListener,
const std::shared_ptr<IReactInstance> &uwpInstance) noexcept {
// Modules
std::vector<facebook::react::NativeModuleDescription> modules;
Expand Down Expand Up @@ -125,6 +127,13 @@ std::vector<facebook::react::NativeModuleDescription> GetCoreModules(
},
messageQueue);

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

// 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
3 changes: 3 additions & 0 deletions vnext/ReactUWP/Base/CoreNativeModules.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#pragma once

#include <ReactUWP/Modules/AppThemeModuleUwp.h>
#include <ReactUWP/Modules/AppearanceModule.h>
#include <ReactUWP/Modules/I18nModule.h>
#include <ReactWindowsCore/NativeModuleProvider.h>
#include <smartPtr/cntPtr.h>
#include <memory>
#include <vector>

Expand All @@ -30,6 +32,7 @@ std::vector<facebook::react::NativeModuleDescription> GetCoreModules(
I18nModule::I18nInfo &&i18nInfo,
std::shared_ptr<facebook::react::AppState> &&appstate,
std::shared_ptr<react::uwp::AppTheme> &&appTheme,
Mso::CntPtr<AppearanceChangeListener> &&appearanceListener,
const std::shared_ptr<IReactInstance> &uwpInstance) noexcept;

} // namespace react::uwp
5 changes: 4 additions & 1 deletion vnext/ReactUWP/Base/UwpReactInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ void UwpReactInstance::Start(const std::shared_ptr<IReactInstance> &spThis, cons
std::shared_ptr<react::uwp::AppTheme> appTheme =
std::make_shared<react::uwp::AppTheme>(spThis, m_defaultNativeThread);
std::pair<std::string, bool> i18nInfo = I18nModule::GetI18nInfo();
auto appearanceListener = Mso::Make<AppearanceChangeListener>(spThis);

// TODO: Figure out threading. What thread should this really be on?
m_initThread = std::make_unique<react::uwp::WorkerMessageQueueThread>();
Expand All @@ -124,7 +125,8 @@ void UwpReactInstance::Start(const std::shared_ptr<IReactInstance> &spThis, cons
settings,
i18nInfo = std::move(i18nInfo),
appstate = std::move(appstate),
appTheme = std::move(appTheme)]() mutable {
appTheme = std::move(appTheme),
appearanceListener = std::move(appearanceListener)]() mutable {
// Setup DevSettings based on our own internal structure
auto devSettings(std::make_shared<facebook::react::DevSettings>());
devSettings->debugBundlePath = settings.DebugBundlePath;
Expand Down Expand Up @@ -203,6 +205,7 @@ void UwpReactInstance::Start(const std::shared_ptr<IReactInstance> &spThis, cons
std::move(i18nInfo),
std::move(appstate),
std::move(appTheme),
std::move(appearanceListener),
spThis);

cxxModules.emplace_back(
Expand Down
66 changes: 66 additions & 0 deletions vnext/ReactUWP/Modules/AppearanceModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "pch.h"
#include "AppearanceModule.h"

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

using Application = winrt::Windows::UI::Xaml::Application;
using ApplicationTheme = winrt::Windows::UI::Xaml::ApplicationTheme;
using UISettings = winrt::Windows::UI::ViewManagement::UISettings;

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

namespace react::uwp {

AppearanceChangeListener::AppearanceChangeListener(std::weak_ptr<IReactInstance> &&reactInstance) noexcept
: Mso::ActiveObject<>(Mso::DispatchQueue::MainUIQueue()), m_weakReactInstance(std::move(reactInstance)) {
// Ensure we're constructed on the UI thread
VerifyIsInQueueElseCrash();

m_currentTheme = Application::Current().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->InvokeInQueueStrong([strongThis]() noexcept { strongThis->OnColorValuesChanged(); });
}
});
}

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

const char *AppearanceChangeListener::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;

if (auto reactInstance = m_weakReactInstance.lock()) {
reactInstance->CallJsFunction(
"RCTDeviceEventEmitter", "emit", folly::dynamic::array("appearanceChanged", ToString(m_currentTheme)));
}
}
}

AppearanceModule::AppearanceModule(Mso::CntPtr<AppearanceChangeListener> &&appearanceListener) noexcept
: m_changeListener(std::move(appearanceListener)) {}

std::string AppearanceModule::getName() {
return AppearanceModule::Name;
}

std::vector<Method> AppearanceModule::getMethods() {
return {Method(
"getColorScheme", [this](folly::dynamic /*args*/) { return m_changeListener->GetColorScheme(); }, SyncTag)};
}

} // namespace react::uwp
45 changes: 45 additions & 0 deletions vnext/ReactUWP/Modules/AppearanceModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once

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

#include "IReactInstance.h"

namespace react::uwp {

// 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::ActiveObject<> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very weird ActiveObject. Initially I wanted to do more, but then I discovered:

  1. The thread you construct UISettings on doesn't matter in terms of where it notifies you, and it itself is apparently freethreaded, so we don't delay construction until init
  2. m_currentTheme was going to be an ActiveReadableField set by the UI thread, but adding a mutex felt like extra overhead when we could use an atomic for an integral type, so I didn't do that
  3. IReactInstance is already free-threaded and didn't need protection it sounded like?

In the end, we basically just use ActiveObject to associate the UI Queue without really needing a lot of its features. I could this this making sense to just be a normal object keeping a queue instead, but don't think there would be too much of a difference.

using ApplicationTheme = winrt::Windows::UI::Xaml::ApplicationTheme;
using UISettings = winrt::Windows::UI::ViewManagement::UISettings;

public:
AppearanceChangeListener(std::weak_ptr<IReactInstance> &&reactInstance) noexcept;
const char *GetColorScheme() const noexcept;

private:
static const char *ToString(ApplicationTheme theme) noexcept;
void OnColorValuesChanged() noexcept;

UISettings m_uiSettings;
UISettings::ColorValuesChanged_revoker m_revoker;
std::atomic<ApplicationTheme> m_currentTheme;
std::weak_ptr<IReactInstance> m_weakReactInstance;
};

class AppearanceModule final : public facebook::xplat::module::CxxModule {
public:
static constexpr const char *Name = "Appearance";

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

private:
Mso::CntPtr<AppearanceChangeListener> m_changeListener;
};

} // namespace react::uwp
2 changes: 2 additions & 0 deletions vnext/ReactUWP/Modules/ImageViewManagerModule.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related, just noticed this was missing

#include <cxxreact/CxxModule.h>

namespace facebook {
Expand Down
9 changes: 7 additions & 2 deletions vnext/ReactUWP/ReactUWP.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
</ImportGroup>
<ImportGroup Label="Shared">
<Import Project="..\Chakra\Chakra.vcxitems" Label="Shared" />
<Import Project="..\Mso\Mso.vcxitems" Label="Shared" />
<Import Project="..\Shared\Shared.vcxitems" Label="Shared" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
Expand All @@ -77,6 +78,8 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<ForcedIncludeFiles>pch.h</ForcedIncludeFiles>
<CompileAsWinRT>false</CompileAsWinRT>
<SDLCheck>true</SDLCheck>
<!--
Expand Down Expand Up @@ -106,7 +109,7 @@
$(ReactNativeWindowsDir);
$(ReactNativeWindowsDir)Common;
$(ReactNativeWindowsDir)JSI\Shared;
$(ReactNativeWindowsDir)Pch;
$(ReactNativeWindowsDir)ReactUwp\Pch;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReactUwp [](start = 32, length = 8)

Good catch! nit: Please keep the original folder name spelling: ReactUWP.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to go ahead and merge as is since we will be nuking the project soonish anyway.

$(ReactNativeWindowsDir)ReactUWP\GeneratedWinmdHeader;
$(ReactNativeWindowsDir)ReactWindowsCore;
$(ReactNativeWindowsDir)ReactWindowsCore\tracing;
Expand Down Expand Up @@ -180,6 +183,7 @@
<ClInclude Include="Modules\Animated\SpringAnimationDriver.h" />
<ClInclude Include="Modules\Animated\TrackingAnimatedNode.h" />
<ClInclude Include="..\include\ReactUWP\TurboModuleUtils.h" />
<ClInclude Include="Modules\AppearanceModule.h" />
<ClInclude Include="Threading\BatchingUIMessageQueueThread.h" />
<ClInclude Include="Threading\MessageQueueThreadFactory.h" />
<ClInclude Include="Utils\StandardControlResourceKeyNames.h" />
Expand Down Expand Up @@ -275,6 +279,7 @@
<ClCompile Include="Modules\Animated\CalculatedAnimationDriver.cpp" />
<ClCompile Include="Modules\Animated\SpringAnimationDriver.cpp" />
<ClCompile Include="Modules\Animated\TrackingAnimatedNode.cpp" />
<ClCompile Include="Modules\AppearanceModule.cpp" />
<ClCompile Include="Threading\BatchingUIMessageQueueThread.cpp" />
<ClCompile Include="Threading\MessageQueueThreadFactory.cpp" />
<ClCompile Include="TurboModule\TurboModuleUtils.cpp" />
Expand Down Expand Up @@ -452,4 +457,4 @@
<RemoveDir Directories="$(IdlHeaderDirectory)" ContinueOnError="true" />
<RemoveDir Directories="$(UnmergedWinmdDirectory)" ContinueOnError="true" />
</Target>
</Project>
</Project>
2 changes: 2 additions & 0 deletions vnext/ReactUWP/ReactUWP.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
<ClCompile Include="Views\XamlFeatures.cpp">
<Filter>Views</Filter>
</ClCompile>
<ClCompile Include="Modules\AppearanceModule.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\ReactUWP\InstanceFactory.h">
Expand Down Expand Up @@ -655,6 +656,7 @@
<ClInclude Include="Views\VirtualTextViewManager.h">
<Filter>Views</Filter>
</ClInclude>
<ClInclude Include="Modules\AppearanceModule.h" />
</ItemGroup>
<ItemGroup>
<None Include="EndPoints\dll\react-native-uwp.arm.def">
Expand Down