From ac3f7fe6850c474af428bebdccbfa27bf474ea07 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Thu, 16 Apr 2020 08:40:54 -0700 Subject: [PATCH 1/5] Implement AppearanceModule This change adds an implementation of AppearanceModule. This ends up being a little bit hairy, since it exposes sync methods, but on Windows we can only check app theme on the UI thread. We could have reused the model of AppTheme and asked to construct some partial state on the UI thread and pass to the module, but this ends up being somewhat cumbersome. We instead will post to the UI thread internally, and block on a result before returning initial data. This should never happen in practice. Validated we can correctly switch between light-mode and dark-mode RNTester when not using web debugging (where Appearance is disabled JS side since Chrome can't handle sync methods). --- .../Microsoft.ReactNative.vcxproj | 2 + .../Microsoft.ReactNative.vcxproj.filters | 6 ++ vnext/ReactUWP/Base/CoreNativeModules.cpp | 12 +++- vnext/ReactUWP/Modules/AppearanceModule.cpp | 68 +++++++++++++++++++ vnext/ReactUWP/Modules/AppearanceModule.h | 47 +++++++++++++ .../ReactUWP/Modules/ImageViewManagerModule.h | 2 + 6 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 vnext/ReactUWP/Modules/AppearanceModule.cpp create mode 100644 vnext/ReactUWP/Modules/AppearanceModule.h diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj index 359c92e5501..fdcffcd6d79 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj @@ -205,6 +205,7 @@ + @@ -368,6 +369,7 @@ + diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters index 4a536c8b629..cb3799a0440 100644 --- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters +++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters @@ -86,6 +86,9 @@ Modules\Animated + + Modules + Modules @@ -404,6 +407,9 @@ Modules\Animated + + Modules + Modules diff --git a/vnext/ReactUWP/Base/CoreNativeModules.cpp b/vnext/ReactUWP/Base/CoreNativeModules.cpp index d07deda071f..e5a1f46bdda 100644 --- a/vnext/ReactUWP/Base/CoreNativeModules.cpp +++ b/vnext/ReactUWP/Base/CoreNativeModules.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -104,9 +105,7 @@ std::vector GetCoreModules( modules.emplace_back( react::windows::AppThemeModule::name, - [appTheme = std::move(appTheme)]() mutable { - return std::make_unique(std::move(appTheme)); - }, + [appTheme]() mutable { return std::make_unique(std::move(appTheme)); }, messageQueue); modules.emplace_back(AlertModule::name, []() { return std::make_unique(); }, messageQueue); @@ -129,6 +128,13 @@ std::vector GetCoreModules( }, messageQueue); + modules.emplace_back( + AppearanceModule::Name, + [wpUwpInstance = std::weak_ptr(uwpInstance)]() mutable { + return std::make_unique(std::move(wpUwpInstance)); + }, + 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( diff --git a/vnext/ReactUWP/Modules/AppearanceModule.cpp b/vnext/ReactUWP/Modules/AppearanceModule.cpp new file mode 100644 index 00000000000..f10b6d84b17 --- /dev/null +++ b/vnext/ReactUWP/Modules/AppearanceModule.cpp @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "pch.h" +#include "AppearanceModule.h" + +#include + +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 &&reactInstance) noexcept + : Mso::ActiveObject<>(Mso::DispatchQueue::MainUIQueue()), m_weakReactInstance(std::move(reactInstance)) {} + +void AppearanceChangeListener::Initialize() noexcept { + m_currentTheme = Application::Current().RequestedTheme(); + + // UISettings will notify us on a background thread regardless of where we construct it or register for events. Let it + // be constructed before init, but redirect callbacks to the UI thread where we can check app theme. + m_uiSettings.ColorValuesChanged([weakThis{Mso::WeakPtr(this)}](const auto &, const auto &) noexcept { + if (auto strongThis = weakThis.GetStrongPtr()) { + strongThis->InvokeInQueueStrong([strongThis]() noexcept { strongThis->OnColorValuesChanged(); }); + } + }); + + m_inited.Set(); +} + +const char *AppearanceChangeListener::GetColorScheme() noexcept { + // Wait until we have an initial valid value from the UI thread before returning anything + m_inited.Wait(); + 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(std::weak_ptr &&reactInstance) noexcept + : m_changeListener(Mso::Make(std::move(reactInstance))) {} + +std::string AppearanceModule::getName() { + return AppearanceModule::Name; +} + +std::vector AppearanceModule::getMethods() { + return {Method( + "getColorScheme", [this](folly::dynamic /*args*/) { return m_changeListener->GetColorScheme(); }, SyncTag)}; +} + +} // namespace react::uwp diff --git a/vnext/ReactUWP/Modules/AppearanceModule.h b/vnext/ReactUWP/Modules/AppearanceModule.h new file mode 100644 index 00000000000..072bebf43dc --- /dev/null +++ b/vnext/ReactUWP/Modules/AppearanceModule.h @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +#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<> { + using ApplicationTheme = winrt::Windows::UI::Xaml::ApplicationTheme; + using UISettings = winrt::Windows::UI::ViewManagement::UISettings; + + public: + AppearanceChangeListener(std::weak_ptr &&reactInstance) noexcept; + void Initialize() noexcept override; + + const char *GetColorScheme() noexcept; + + private: + static const char *ToString(ApplicationTheme theme) noexcept; + void OnColorValuesChanged() noexcept; + + UISettings m_uiSettings; + std::atomic m_currentTheme; + Mso::ManualResetEvent m_inited; + std::weak_ptr m_weakReactInstance; +}; + +class AppearanceModule final : public facebook::xplat::module::CxxModule { + public: + static constexpr const char *Name = "Appearance"; + + AppearanceModule(std::weak_ptr &&reactInstance) noexcept; + std::string getName() override; + std::vector getMethods() override; + + private: + Mso::CntPtr m_changeListener; +}; + +} // namespace react::uwp diff --git a/vnext/ReactUWP/Modules/ImageViewManagerModule.h b/vnext/ReactUWP/Modules/ImageViewManagerModule.h index dcecfdd7509..2bf8fbb21d8 100644 --- a/vnext/ReactUWP/Modules/ImageViewManagerModule.h +++ b/vnext/ReactUWP/Modules/ImageViewManagerModule.h @@ -1,5 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +#pragma once #include namespace facebook { From e5a38e7b105322e61f9e874271af92fc3396ea04 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Thu, 16 Apr 2020 08:41:54 -0700 Subject: [PATCH 2/5] Change files --- ...ive-windows-2020-04-16-08-41-54-appearance-module.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 change/react-native-windows-2020-04-16-08-41-54-appearance-module.json diff --git a/change/react-native-windows-2020-04-16-08-41-54-appearance-module.json b/change/react-native-windows-2020-04-16-08-41-54-appearance-module.json new file mode 100644 index 00000000000..fe356c2fdee --- /dev/null +++ b/change/react-native-windows-2020-04-16-08-41-54-appearance-module.json @@ -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" +} \ No newline at end of file From 293956203dd662b3d3a5c62429d5b221c3688382 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Thu, 16 Apr 2020 08:46:50 -0700 Subject: [PATCH 3/5] Revert an accidental change --- vnext/ReactUWP/Base/CoreNativeModules.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vnext/ReactUWP/Base/CoreNativeModules.cpp b/vnext/ReactUWP/Base/CoreNativeModules.cpp index e5a1f46bdda..63ab181f157 100644 --- a/vnext/ReactUWP/Base/CoreNativeModules.cpp +++ b/vnext/ReactUWP/Base/CoreNativeModules.cpp @@ -105,7 +105,9 @@ std::vector GetCoreModules( modules.emplace_back( react::windows::AppThemeModule::name, - [appTheme]() mutable { return std::make_unique(std::move(appTheme)); }, + [appTheme = std::move(appTheme)]() mutable { + return std::make_unique(std::move(appTheme)); + }, messageQueue); modules.emplace_back(AlertModule::name, []() { return std::make_unique(); }, messageQueue); From 0e791d4f27932bdc146f0ac285e11967b24284fb Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Fri, 17 Apr 2020 07:22:54 -0700 Subject: [PATCH 4/5] Fix Build For ReactUwp/Win32 Playground --- packages/playground/windows/playground-win32.sln | 1 + vnext/ReactUWP/ReactUWP.vcxproj | 9 +++++++-- vnext/ReactUWP/ReactUWP.vcxproj.filters | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/playground/windows/playground-win32.sln b/packages/playground/windows/playground-win32.sln index 8f15aa41d58..8b30f151e52 100644 --- a/packages/playground/windows/playground-win32.sln +++ b/packages/playground/windows/playground-win32.sln @@ -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 diff --git a/vnext/ReactUWP/ReactUWP.vcxproj b/vnext/ReactUWP/ReactUWP.vcxproj index b0f95b487ef..826f165b7e1 100644 --- a/vnext/ReactUWP/ReactUWP.vcxproj +++ b/vnext/ReactUWP/ReactUWP.vcxproj @@ -65,6 +65,7 @@ + @@ -77,6 +78,8 @@ Use + pch.h + pch.h false true