diff --git a/ReactWindows/Playground.Net46/App.config b/ReactWindows/Playground.Net46/App.config new file mode 100644 index 00000000000..2d2a12d81be --- /dev/null +++ b/ReactWindows/Playground.Net46/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/ReactWindows/Playground.Net46/App.xaml b/ReactWindows/Playground.Net46/App.xaml new file mode 100644 index 00000000000..9dddbb94baf --- /dev/null +++ b/ReactWindows/Playground.Net46/App.xaml @@ -0,0 +1,5 @@ + + + diff --git a/ReactWindows/Playground.Net46/App.xaml.cs b/ReactWindows/Playground.Net46/App.xaml.cs new file mode 100644 index 00000000000..41b1dd530f7 --- /dev/null +++ b/ReactWindows/Playground.Net46/App.xaml.cs @@ -0,0 +1,101 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Navigation; + +namespace Playground.Net46 +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + private readonly AppReactPage _reactPage = new AppReactPage(); + + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + } + + /// + /// Override method fired prior to the Startup event when the Run method of the Application object is called... + /// + /// + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + OnCreate(e.Args); + } + + /// + /// Called whenever the app is opened to initialized... + /// + /// + private void OnCreate(string[] arguments) + { + var shellWindow = Application.Current.MainWindow; + + if (shellWindow == null) + { + shellWindow = new Window + { + ShowActivated = true, + ShowInTaskbar = true, + Title = "Playground.Net46", + Height = 768, + Width = 1024, + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + + Application.Current.MainWindow = shellWindow; + } + + //Show Window if it is not already active... + if (!shellWindow.IsLoaded) + { + shellWindow.Show(); + } + + var rootFrame = shellWindow.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == null) + { + _reactPage.OnCreate(arguments); + + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + rootFrame.NavigationFailed += OnNavigationFailed; + + // Place the frame in the current Window + shellWindow.Content = rootFrame; + } + + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Content = _reactPage; + } + + // Ensure the current window is active + shellWindow.Activate(); + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + private void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page..."); + } + } +} diff --git a/ReactWindows/Playground.Net46/AppReactPage.cs b/ReactWindows/Playground.Net46/AppReactPage.cs new file mode 100644 index 00000000000..16e8c22acb1 --- /dev/null +++ b/ReactWindows/Playground.Net46/AppReactPage.cs @@ -0,0 +1,41 @@ +using ReactNative; +using ReactNative.Modules.Core; +using ReactNative.Shell; +using System.Collections.Generic; + +namespace Playground.Net46 +{ + internal class AppReactPage : ReactPage + { + public override string MainComponentName => "Playground.Net46"; + + public override string JavaScriptMainModuleName => "ReactWindows/Playground.Net46/index.windows"; + +#if BUNDLE + public override string JavaScriptBundleFile + { + get + { + return "ms-appx:///ReactAssets/index.windows.bundle"; + } + } +#endif + + public override List Packages => new List + { + new MainReactPackage(), + }; + + public override bool UseDeveloperSupport + { + get + { +#if !BUNDLE || DEBUG + return true; +#else + return false; +#endif + } + } + } +} diff --git a/ReactWindows/Playground.Net46/Playground.Net46.csproj b/ReactWindows/Playground.Net46/Playground.Net46.csproj new file mode 100644 index 00000000000..c1090542750 --- /dev/null +++ b/ReactWindows/Playground.Net46/Playground.Net46.csproj @@ -0,0 +1,126 @@ + + + + + Debug + AnyCPU + {7AB3A24A-D5D9-479F-8E12-22213DD40D3F} + WinExe + Properties + Playground.Net46 + Playground.Net46 + v4.6 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + + true + bin\x86\Debug\ + TRACE;DEBUG;NET46 + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + false + + + bin\x86\Release\ + TRACE;NET46 + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + ARM + bin\ARM\Debug\ + DEBUG + + + ARM + bin\ARM\Release\ + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + + Code + + + + + + + + + {22cbff9c-fe36-43e8-a246-266c7635e662} + ReactNative.Net46 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReactWindows/Playground.Net46/Properties/AssemblyInfo.cs b/ReactWindows/Playground.Net46/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..9f6451f4896 --- /dev/null +++ b/ReactWindows/Playground.Net46/Properties/AssemblyInfo.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Playground.Net46")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Playground.Net46")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ReactWindows/Playground.Net46/index.windows.js b/ReactWindows/Playground.Net46/index.windows.js new file mode 100644 index 00000000000..22df388f5b0 --- /dev/null +++ b/ReactWindows/Playground.Net46/index.windows.js @@ -0,0 +1,56 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + */ + +import React, { Component } from 'react'; +import { + AppRegistry, + StyleSheet, + Text, + View, +} from 'react-native'; + +class Playground extends Component { + render() { + return ( + + + Welcome to React Native! + + + To get started, edit index.windows.js + + + Press Ctrl+R to reload + + + Press Ctrl+D or Ctrl+M for dev menu + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + width: 500 + }, + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + width: 500 + }, +}); + +AppRegistry.registerComponent('Playground.Net46', () => Playground); diff --git a/ReactWindows/Playground.Net46/packages.config b/ReactWindows/Playground.Net46/packages.config new file mode 100644 index 00000000000..b45aef647cd --- /dev/null +++ b/ReactWindows/Playground.Net46/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ReactWindows/ReactNative.Net46.Tests/Internal/DispatcherHelpers.cs b/ReactWindows/ReactNative.Net46.Tests/Internal/DispatcherHelpers.cs index b9afe563a01..f5b617424b8 100644 --- a/ReactWindows/ReactNative.Net46.Tests/Internal/DispatcherHelpers.cs +++ b/ReactWindows/ReactNative.Net46.Tests/Internal/DispatcherHelpers.cs @@ -1,9 +1,9 @@ -using System; +using NUnit.Framework; +using System; using System.Threading; using System.Threading.Tasks; -using System.Windows.Threading; using System.Windows; -using NUnit.Framework; +using System.Windows.Threading; namespace ReactNative.Tests { @@ -14,7 +14,7 @@ public static async Task RunOnDispatcherAsync(Action action) Dispatcher dispatcher = Application.Current != null ? Application.Current.Dispatcher : Dispatcher.CurrentDispatcher; - if (Thread.CurrentThread == Dispatcher.CurrentDispatcher.Thread) + if (dispatcher.CheckAccess()) { dispatcher.Invoke(action); } diff --git a/ReactWindows/ReactNative.Net46.Tests/Modules/NetInfo/NetInfoModuleTests.cs b/ReactWindows/ReactNative.Net46.Tests/Modules/NetInfo/NetInfoModuleTests.cs index 19748e37c11..23090d4ef32 100644 --- a/ReactWindows/ReactNative.Net46.Tests/Modules/NetInfo/NetInfoModuleTests.cs +++ b/ReactWindows/ReactNative.Net46.Tests/Modules/NetInfo/NetInfoModuleTests.cs @@ -1,13 +1,12 @@ -using NMock; +using Newtonsoft.Json.Linq; using NUnit.Framework; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using ReactNative.Bridge; using ReactNative.Modules.Core; using ReactNative.Modules.NetInfo; using System; using System.Net.NetworkInformation; using System.Threading; +using System.Windows.Threading; namespace ReactNative.Tests.Modules.NetInfo { @@ -34,8 +33,11 @@ public void NetInfoModule_JsonResponse() } [Test] + [Apartment(ApartmentState.STA)] public void NetInfoModule_Event() { + SetDispatcherForTest(); + var networkInterface = new MockNetworkInterface("None"); var networkInfo = new MockNetworkInformation(networkInterface); @@ -61,8 +63,11 @@ public void NetInfoModule_Event() } [Test] + [Apartment(ApartmentState.STA)] public void NetInfoModule_LifecycleChecks() { + SetDispatcherForTest(); + var started = new AutoResetEvent(false); var stopped = new AutoResetEvent(false); @@ -105,6 +110,11 @@ private static ReactContext CreateReactContext(IInvocationHandler handler) return context; } + private static void SetDispatcherForTest() + { + ReactNative.Bridge.DispatcherHelpers.CurrentDispatcher = Dispatcher.CurrentDispatcher; + } + class TestReactInstance : MockReactInstance { private readonly object _eventEmitter; diff --git a/ReactWindows/ReactNative.Net46/Bridge/DispatcherHelpers.cs b/ReactWindows/ReactNative.Net46/Bridge/DispatcherHelpers.cs index b700b756bd1..deef2ff870e 100644 --- a/ReactWindows/ReactNative.Net46/Bridge/DispatcherHelpers.cs +++ b/ReactWindows/ReactNative.Net46/Bridge/DispatcherHelpers.cs @@ -7,6 +7,38 @@ namespace ReactNative.Bridge { static class DispatcherHelpers { + private static Dispatcher _dispatcher; + + internal static Dispatcher CurrentDispatcher + { + get + { + AssertDispatcherSet(); + + return _dispatcher; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Thread.GetApartmentState() != ApartmentState.STA) + { + throw new ArgumentException("Dispatcher must be an STA thread"); + } + + _dispatcher = value; + } + } + + public static bool IsDispatcherSet() + { + return _dispatcher != null; + } + public static void AssertOnDispatcher() { if (!IsOnDispatcher()) @@ -17,12 +49,16 @@ public static void AssertOnDispatcher() public static bool IsOnDispatcher() { - return Thread.CurrentThread == Dispatcher.CurrentDispatcher.Thread; + AssertDispatcherSet(); + + return CurrentDispatcher.CheckAccess(); } public static async void RunOnDispatcher(Action action) { - await Dispatcher.CurrentDispatcher.InvokeAsync(action).Task.ConfigureAwait(false); + AssertDispatcherSet(); + + await CurrentDispatcher.InvokeAsync(action).Task.ConfigureAwait(false); } public static Task CallOnDispatcher(Func func) @@ -40,5 +76,13 @@ public static Task CallOnDispatcher(Func func) return taskCompletionSource.Task; } + + private static void AssertDispatcherSet() + { + if (_dispatcher == null) + { + throw new InvalidOperationException("Dispatcher has not been set"); + } + } } } diff --git a/ReactWindows/ReactNative.Net46/DevSupport/Controls/LoadingIndicator.cs b/ReactWindows/ReactNative.Net46/DevSupport/Controls/LoadingIndicator.cs new file mode 100644 index 00000000000..57857eb7781 --- /dev/null +++ b/ReactWindows/ReactNative.Net46/DevSupport/Controls/LoadingIndicator.cs @@ -0,0 +1,140 @@ +using System.Windows; +using System.Windows.Controls; + +namespace ReactNative.DevSupport.Controls +{ + /// + /// A control featuring a range of loading indicating animations. + /// + [TemplatePart(Name = "Border", Type = typeof(Border))] + public class LoadingIndicator : Control + { + /// + /// Property used to set the speed of the animation... + /// + public static readonly DependencyProperty SpeedRatioProperty = + DependencyProperty.Register("SpeedRatio", typeof(double), typeof(LoadingIndicator), new PropertyMetadata(1d, (o, e) => + { + var li = (LoadingIndicator)o; + + if (li.PART_Border == null || li.IsActive == false) + { + return; + } + + foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(li.PART_Border)) + { + if (group.Name == "ActiveStates") + { + foreach (VisualState state in group.States) + { + if (state.Name == "Active") + { + state.Storyboard.SetSpeedRatio(li.PART_Border, (double)e.NewValue); + } + } + } + } + })); + + /// + /// Property used to indicate whether the animation should be made active... + /// + public static readonly DependencyProperty IsActiveProperty = + DependencyProperty.Register("IsActive", typeof(bool), typeof(LoadingIndicator), new PropertyMetadata(true, (o, e) => + { + var li = (LoadingIndicator)o; + + if (li.PART_Border == null) + { + return; + } + + if ((bool)e.NewValue == false) + { + VisualStateManager.GoToElementState(li.PART_Border, "Inactive", false); + li.PART_Border.Visibility = Visibility.Collapsed; + } + else + { + VisualStateManager.GoToElementState(li.PART_Border, "Active", false); + li.PART_Border.Visibility = Visibility.Visible; + + foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(li.PART_Border)) + { + if (group.Name == "ActiveStates") + { + foreach (VisualState state in group.States) + { + if (state.Name == "Active") + { + state.Storyboard.SetSpeedRatio(li.PART_Border, li.SpeedRatio); + } + } + } + } + } + })); + + /// + /// Border Template Part Accessor + /// + protected Border PART_Border; + + /// + /// Get/set the speed ratio of the animation. + /// + public double SpeedRatio + { + get { return (double)GetValue(SpeedRatioProperty); } + set { SetValue(SpeedRatioProperty, value); } + } + + /// + /// Get/set whether the loading indicator is active. + /// + public bool IsActive + { + get { return (bool)GetValue(IsActiveProperty); } + set { SetValue(IsActiveProperty, value); } + } + + /// + /// When overridden in a derived class, is invoked whenever application code + /// or internal processes call System.Windows.FrameworkElement.ApplyTemplate(). + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + PART_Border = (Border)GetTemplateChild("PART_Border"); + + if (PART_Border != null) + { + VisualStateManager.GoToElementState(PART_Border, (this.IsActive ? "Active" : "Inactive"), false); + foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(PART_Border)) + { + if (group.Name == "ActiveStates") + { + foreach (VisualState state in group.States) + { + if (state.Name == "Active") + { + state.Storyboard.SetSpeedRatio(PART_Border, this.SpeedRatio); + } + } + } + } + + PART_Border.Visibility = (this.IsActive ? Visibility.Visible : Visibility.Collapsed); + } + } + + /// + /// Initializes a new instance of the class. + /// + public LoadingIndicator() + { + } + } +} diff --git a/ReactWindows/ReactNative.Net46/DevSupport/DevOptionDialog.xaml b/ReactWindows/ReactNative.Net46/DevSupport/DevOptionDialog.xaml index 2b090394fb9..3352aaa75e8 100644 --- a/ReactWindows/ReactNative.Net46/DevSupport/DevOptionDialog.xaml +++ b/ReactWindows/ReactNative.Net46/DevSupport/DevOptionDialog.xaml @@ -1,17 +1,58 @@  - - - - - + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:ReactNative.DevSupport" + mc:Ignorable="d" + Style="{DynamicResource ContentDialogStyle}" + WindowStartupLocation="CenterOwner"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +