From 75610388250c33be2130caec57354b6af3e7d290 Mon Sep 17 00:00:00 2001 From: "huayong.xu" Date: Tue, 21 Feb 2023 16:45:22 +0800 Subject: [PATCH] Implement BasicCalculator using NUI Xaml. --- .../NUI/BasicCalculator/BasicCalculator.sln | 25 + Mobile/NUI/BasicCalculator/LICENSE | 201 ++++++++ Mobile/NUI/BasicCalculator/README.md | 17 + .../NUI/BasicCalculator/Screenshots/main.png | Bin 0 -> 43654 bytes .../BasicCalculator.Tizen.Mobile.cs | 70 +++ .../BasicCalculator.Tizen.Mobile.csproj | 35 ++ .../BasicCalculator/BasicCalculator.csproj | 12 + .../BasicCalculator/Models/MathExpression.cs | 314 +++++++++++++ .../ViewModels/CalculatorViewModel.cs | 438 ++++++++++++++++++ .../ViewModels/IKeyboardService.cs | 40 ++ .../ViewModels/KeyboardHandler.cs | 47 ++ .../BasicCalculator/Views/IViewResolver.cs | 38 ++ .../Services/KeyboardService.cs | 64 +++ .../Views/MobileMainView.xaml | 175 +++++++ .../Views/MobileMainView.xaml.cs | 37 ++ .../Views/MobileViewResolver.cs | 51 ++ .../res/bg_01.png | Bin 0 -> 22312 bytes .../res/BasicCalculator.Tizen.Mobile.png | Bin 0 -> 10097 bytes .../tizen-manifest.xml | 15 + 19 files changed, 1579 insertions(+) create mode 100755 Mobile/NUI/BasicCalculator/BasicCalculator.sln create mode 100755 Mobile/NUI/BasicCalculator/LICENSE create mode 100755 Mobile/NUI/BasicCalculator/README.md create mode 100755 Mobile/NUI/BasicCalculator/Screenshots/main.png create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.csproj create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/BasicCalculator.csproj create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Models/MathExpression.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/CalculatorViewModel.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/IKeyboardService.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/KeyboardHandler.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Views/IViewResolver.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Services/KeyboardService.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileViewResolver.cs create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/res/bg_01.png create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/shared/res/BasicCalculator.Tizen.Mobile.png create mode 100755 Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/tizen-manifest.xml diff --git a/Mobile/NUI/BasicCalculator/BasicCalculator.sln b/Mobile/NUI/BasicCalculator/BasicCalculator.sln new file mode 100755 index 000000000..dd3fbf309 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/BasicCalculator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31229.75 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicCalculator.Tizen.Mobile", "src\BasicCalculator\BasicCalculator.Tizen.Mobile\BasicCalculator.Tizen.Mobile.csproj", "{9757AA34-E55B-410B-A2F0-C041ADC75ED0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F98B7925-D0AD-4FBD-9041-9AC9757AD495} + EndGlobalSection +EndGlobal diff --git a/Mobile/NUI/BasicCalculator/LICENSE b/Mobile/NUI/BasicCalculator/LICENSE new file mode 100755 index 000000000..51a2cd23d --- /dev/null +++ b/Mobile/NUI/BasicCalculator/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Samsung + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Mobile/NUI/BasicCalculator/README.md b/Mobile/NUI/BasicCalculator/README.md new file mode 100755 index 000000000..89123e19f --- /dev/null +++ b/Mobile/NUI/BasicCalculator/README.md @@ -0,0 +1,17 @@ +# BasicCalculator +BasicCalculator is a sample application which demonstrates how to create a calculator +with basic mathematical operations using NUI Xaml library. + +![Main page](./Screenshots/main.png) + +### Features +* Preview of the result +* Basic mathematical operations + +### Prerequisites + +* [Visual Studio](https://www.visualstudio.com/) - Buildtool, IDE +* [Visual Studio Tools for Tizen](https://docs.tizen.org/application/vstools/install) - Visual Studio plugin for Tizen .NET application development + +### Author +* Guowei Wang diff --git a/Mobile/NUI/BasicCalculator/Screenshots/main.png b/Mobile/NUI/BasicCalculator/Screenshots/main.png new file mode 100755 index 0000000000000000000000000000000000000000..9d70132c498e1d4d697b02a392ef14e0935a1426 GIT binary patch literal 43654 zcmeFYXH=8lw=N2V9(wP+LJZK#24X0Ribqlae5)2qHzLcchn4 z6i`G^2ob4*6lwRx|K8`GefA#b-h0Lw<9<0G!0|@bT5r~D&z$p_2`CFAMtVMaGBPqo z6JrBwGBR=u85u=6Ej92-rR=mG@DI74wGoo6YE<9{@IdK_xQrkpt4ReP-=YGZ>3ods z{K&|dy3c>e`@CPck&(URH!(n54Rl;Tp?L%Ke7ury?RtCB#&3_2)r}7)&pd-ahU|De zOUjqQ`TUBEO^el$sq~>hm>J%pG>pPkJJ>WtKZ8dqcF_j;6;|zEn-1|FZEczl2|3o> zf8rg%n_kl{c-BAs@X_$2;klbX>o>*+bfm@i>d)HQd4HUeQo|cRLlR3Mf@H_fN+5|7 z;T_&Gz~eg2e|+q7P1W^~cW5dF{`QU1k}qdd3B;5JhrG^V_=Dvt>3oA|dIu|xGJOKt$J)PWtI0skF0rGSy|_0MpV z`}}g^I!9S@|8>%OEU;S^(^Culrn0NhY2;xCjj7J@(N`LZ-F8cz+8M`UO1snfZja%r zg^)eZULCQjBrk>S6=9`R%KH8OZ=`oa^#f%Br+P=HRZ7e>?U^Tzjoz;Q37;c+pdRh7 z-<)*ra~N9PfVUZuj(?`TPt9d+*pyRC^;yJDGfLQH2Oj?EJj*HH-HD3*;dwZ{xO}|7 zU94ZPueaata@Mq=+;oE0{vu4?RMdPz9}x@elyBgQ-EiwmDIGGZUxRR;m~WjO_s
!5+u6IKiSG~jH{$cyC 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 Rh$Z0P5p%191*Tqc{{<_bpa}o~ literal 0 HcmV?d00001 diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.cs new file mode 100755 index 000000000..bdfd11d3d --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.cs @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using BasicCalculator.Tizen.Mobile.Views; +using BasicCalculator.ViewModels; +using Tizen.NUI; +using Tizen.NUI.Binding; + +namespace BasicCalculator.Tizen.Mobile +{ + internal class Program : NUIApplication + { + #region methods + + private readonly string[] _keyList = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; + + ///

+ /// Loads NUI application. + /// + protected override void OnCreate() + { + base.OnCreate(); + + Window window = Window.Instance; + window.BackgroundColor = Color.Cyan; + window.KeyEvent += OnKeyEvent; + + MobileMainView page = new MobileMainView(); + page.PositionUsesPivotPoint = true; + page.ParentOrigin = ParentOrigin.Center; + page.PivotPoint = PivotPoint.Center; + page.Size = new Size(window.WindowSize); + window.Add(page); + } + + public void OnKeyEvent(object sender, Window.KeyEventArgs e) + { + if (e.Key.State == Key.StateType.Down && (e.Key.KeyPressedName == "XF86Back" || e.Key.KeyPressedName == "Escape")) + { + Exit(); + } + } + + /// + /// Main application entry point. + /// Initializes Tizen extensions. + /// + /// Application arguments. + private static void Main(string[] args) + { + var app = new Program(); + app.Run(args); + } + + #endregion + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.csproj b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.csproj new file mode 100755 index 000000000..231216c03 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.csproj @@ -0,0 +1,35 @@ + + + + Exe + netcoreapp3.1 + 1 + + + + portable + + + None + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/BasicCalculator.csproj b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/BasicCalculator.csproj new file mode 100755 index 000000000..3ac350415 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/BasicCalculator.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.1 + + + + + + + + diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Models/MathExpression.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Models/MathExpression.cs new file mode 100755 index 000000000..af79fcdc2 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Models/MathExpression.cs @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace BasicCalculator.Models +{ + /// + /// Exception thrown when expression result length is exceeded. + /// + internal class MathExpressionResultLengthExceededException : Exception + { + + } + + /// + /// Exception thrown when expression evaluation fails. + /// + internal class MathExpressionEvaluationException : Exception + { + + } + + internal class MathExpression + { + #region fields + + /// + /// String holding whole current expression, which is to be calculated. + /// + private StringBuilder _expression; + + #endregion fields + + #region methods + + /// + /// Constructor setting initial expression, which shall be calculated with the + /// method. + /// + /// Expression to be calculated. + public MathExpression(string expression) + { + _expression = new StringBuilder(expression); + } + + /// + /// Retrieves the value to the left of the operator finding it's start index. + /// + /// Whole expression. + /// Position of the operator. + /// Returned start position of the value. + /// The value to the left of the operator. + private decimal GetLeftValue(StringBuilder expression, int operatorPosition, out int valueStart) + { + valueStart = operatorPosition - 1; + while (valueStart > 0 && (char.IsDigit(expression[valueStart - 1]) || expression[valueStart - 1] == '.' + || expression[valueStart - 1] == '-')) + { + --valueStart; + } + + if (!decimal.TryParse(expression.ToString(valueStart, operatorPosition - valueStart), out decimal value)) + { + throw new ArgumentException("Wrong value for left value of [" + expression.ToString(valueStart, + operatorPosition - valueStart + 1)); + } + + return value; + } + + /// + /// Retrieves the value to the right of the operator finding it's end index. + /// + /// Whole expression. + /// Position of the operator. + /// Returned end position of the value. + /// The value to the right of the operator. + private decimal GetRightValue(StringBuilder expression, int operatorPosition, out int valueEnd) + { + valueEnd = operatorPosition + 1; + while (valueEnd + 1 < expression.Length && (char.IsDigit(expression[valueEnd + 1]) + || expression[valueEnd + 1] == '.' || expression[valueEnd + 1] == '-')) + { + ++valueEnd; + } + + decimal value; + if (!decimal.TryParse(expression.ToString(operatorPosition + 1, valueEnd - operatorPosition), out value)) + { + throw new ArgumentException("Wrong right value for expression " + expression.ToString(operatorPosition, + valueEnd - operatorPosition + 1)); + } + + return value; + } + + /// + /// Evaluate string expression from _expression, starting at specified character. + /// + /// Position start for evaluate. + /// Next position after evaluated expression. + /// Thrown when there is invalid expression in parsed string. + /// Thrown on divide by 0. + /// + /// When the parser find "(" character it start itself recursively parsing from that character. When it finds + /// ")" it ends recursive instance and substitutes parsed expression together with brackets (if any) with it's + /// result. + /// + private int EvaluateSubExpression(int startPosition) + { + int endPosition = startPosition; + + // Expression operators order: () % ×/÷ +/- + + // () + for (; endPosition < _expression.Length; ++endPosition) + { + if (_expression[endPosition] == '(') + { + endPosition = EvaluateSubExpression(endPosition + 1); + } + + if (endPosition < _expression.Length && _expression[endPosition] == ')') + { + break; + } + } + + if (endPosition > _expression.Length) + { + endPosition = _expression.Length; + } + + StringBuilder thisExpression = new StringBuilder( + _expression.ToString(startPosition, endPosition - startPosition)); + + // % + for (int position = 0; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '%') + { + int percentStart; + string newValue = (GetLeftValue(thisExpression, position, out percentStart) / 100).ToString(); + thisExpression.Remove(percentStart, position - percentStart + 1); + thisExpression.Insert(percentStart, newValue); + position = percentStart; + } + } + + // "--" convert to positive + // End parsing bound is -2 as we don't accept "--" at the end of expression + for (int position = 0; position < thisExpression.Length - 2; ++position) + { + if (thisExpression[position] == '-' && thisExpression[position + 1] == '-') + { + thisExpression.Remove(position, 2); + if (position > 0 && char.IsDigit(thisExpression[position - 1])) + { + thisExpression.Insert(position, '+'); + } + } + } + + // "-" when should be treated as decrease we convert it to "+" + for (int position = 1; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '-' && char.IsDigit(thisExpression[position - 1])) + { + thisExpression.Insert(position, '+'); + } + } + + // ×/÷ + for (int position = 0; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '×') + { + decimal lValue, rValue; + lValue = GetLeftValue(thisExpression, position, out var leftValueStart); + rValue = GetRightValue(thisExpression, position, out var rightValueEnd); + thisExpression.Remove(leftValueStart, rightValueEnd - leftValueStart + 1); + thisExpression.Insert(leftValueStart, (lValue * rValue).ToString()); + position = leftValueStart; + } + else if (thisExpression[position] == '÷') + { + decimal lValue, rValue; + lValue = GetLeftValue(thisExpression, position, out var leftValueStart); + rValue = GetRightValue(thisExpression, position, out var rightValueEnd); + thisExpression.Remove(leftValueStart, rightValueEnd - leftValueStart + 1); + thisExpression.Insert(leftValueStart, (lValue / rValue).ToString()); + position = leftValueStart; + } + } + + // + + for (int position = 0; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '+') + { + decimal lValue, rValue; + lValue = GetLeftValue(thisExpression, position, out var leftValueStart); + rValue = GetRightValue(thisExpression, position, out var rightValueEnd); + thisExpression.Remove(leftValueStart, rightValueEnd - leftValueStart + 1); + thisExpression.Insert(leftValueStart, (lValue + rValue).ToString()); + position = leftValueStart; + } + } + + if (startPosition > 0 && _expression[startPosition - 1] == '(') + { + --startPosition; + } + + if (endPosition < _expression.Length && _expression[endPosition] == ')') + { + ++endPosition; + } + + _expression.Remove(startPosition, endPosition - startPosition); + _expression.Insert(startPosition, thisExpression); + + return startPosition + thisExpression.Length; + } + + /// + /// Returns string with formatted rounded to fit maximum length + /// ( parameter) + /// + /// Value to convert to string. + /// Maximum resulting string length. + /// Resulting string. + private string GetValueString(decimal value, int maxLength) + { + int dotIndex = value.ToString(CultureInfo.InvariantCulture).IndexOf('.'); + if (dotIndex == -1) + { + dotIndex = value.ToString(CultureInfo.InvariantCulture).Length; + } + + if (dotIndex > maxLength) + { + throw new MathExpressionResultLengthExceededException(); + } + + value = (dotIndex >= maxLength - 1) ? Math.Round(value) : Math.Round(value, maxLength - dotIndex - 1); + value /= 1.000000000000000000000000000000000M; + return value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Validates brackets count. + /// When opening and closing brackets count differs, does nothing. + /// Evaluates expression passed by constructor. + /// If successful evaluated result is returned as string with dots replaced with commas. + /// + /// Maximum returned result string length. + /// Result string or null if there are any expression errors. + public string Evaluate(int maxLength) + { + if (new Regex(Regex.Escape("(")).Matches(_expression.ToString()).Count - + new Regex(Regex.Escape(")")).Matches(_expression.ToString()).Count < 0) + { + throw new MathExpressionEvaluationException(); + } + + int position; + try + { + position = EvaluateSubExpression(0); + } + catch (Exception e) + { + if (e is ArgumentException || e is DivideByZeroException) + { + throw new MathExpressionEvaluationException(); + } + + throw; + } + + if (position < _expression.Length) + { + throw new MathExpressionEvaluationException(); + } + + if (!decimal.TryParse(_expression.ToString(), out decimal value)) + { + throw new MathExpressionEvaluationException(); + } + + return GetValueString(value, maxLength); + } + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/CalculatorViewModel.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/CalculatorViewModel.cs new file mode 100755 index 000000000..1e4ce4eaa --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/CalculatorViewModel.cs @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Input; +using BasicCalculator.Models; +using Tizen.NUI; +using Tizen.NUI.Binding; + +namespace BasicCalculator.ViewModels +{ + public class CalculatorViewModel : INotifyPropertyChanged + { + #region fields + + /// + /// Maximum length of the expression. + /// + private const int EXPRESSION_MAX_LENGTH = 24; + + /// + /// Maximum length of the expression's calculated value. + /// + private const int RESULT_MAX_LENGTH = 12; + + /// + /// Backing field for the property. + /// + private string _enteredExpression = "0"; + + /// + /// Backing field for the property. + /// + private string _currentResult = "0"; + + /// + /// Event fired on property changed. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion fields + + #region properties + + /// + /// Current result of entered expression. + /// + public string CurrentResult + { + get => _currentResult; + set + { + _currentResult = value; + OnPropertyChanged(); + } + } + + /// + /// Currently entered expression for calculation. + /// Sets expression new value if it has valid length. + /// Calls result recalculation when changed. + /// + public string EnteredExpression + { + get => _enteredExpression; + set + { + if (value.Length > EXPRESSION_MAX_LENGTH || value.Equals(_enteredExpression)) + { + return; + } + + _enteredExpression = value; + + // Update current result + MathExpression e = new MathExpression(_enteredExpression); + try + { + string result = e.Evaluate(RESULT_MAX_LENGTH); + CurrentResult = result; + } + catch (MathExpressionEvaluationException) + { + CurrentResult = "Expression error"; + } + catch (MathExpressionResultLengthExceededException) + { + CurrentResult = "Result length exceeded"; + } + catch (Exception) + { + CurrentResult = "Error"; + } + + OnPropertyChanged(); + } + } + + /// + /// Command for appending a character to the current expression. + /// + public ICommand AppendToExpressionCommand { get; set; } + + /// + /// Command for clearing current expression. + /// + public ICommand ClearInputCommand { get; set; } + + /// + /// Command for adding bracket to the current expression. + /// + public ICommand BracketCommand { get; set; } + + /// + /// Command to delete last character from the current expression. + /// + public ICommand DeleteCommand { get; set; } + + /// + /// Command for changing sign of the lastly entered value. + /// + public ICommand ChangeSignCommand { get; set; } + + /// + /// Command to evaluate current expression. + /// + public ICommand EvaluateExpressionCommand { get; set; } + + /// + /// Command for appending decimal point to the expression. + /// + public ICommand AppendDecimalPointCommand { get; set; } + + /// + /// Command for appending an operator to the current expression. + /// + public ICommand AppendOperatorCommand { get; set; } + + #endregion properties + + #region methods + + /// + /// Initializes view model. + /// Creates and assigns view model commands. + /// + public CalculatorViewModel() + { + AppendToExpressionCommand = new Command(AppendToExpression); + ClearInputCommand = new Command(ClearInput); + BracketCommand = new Command(AppendBracket); + DeleteCommand = new Command(DeleteLastCharacter); + ChangeSignCommand = new Command(ChangeSign); + EvaluateExpressionCommand = new Command(EvaluateExpression); + AppendDecimalPointCommand = new Command(AppendDecimalPoint); + AppendOperatorCommand = new Command(AppendOperator); + } + + /// + /// Invokes property changed with caller (property) name. + /// + /// Name of the property changed. + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Indicates if character passed in parameter is supported mathematic operator. + /// + /// Character + /// True if character is supported operator. + private bool IsOperator(char c) + { + return c == '+' || c == '-' || c == '×' || c == '÷'; + } + + /// + /// Clears current input (entered expression). + /// + private void ClearInput() + { + EnteredExpression = "0"; + } + + /// + /// Changes sign of the last value. + /// + private void ChangeSign() + { + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + + if (!char.IsDigit(lastCharacter)) + { + if (lastCharacter == '-' && EnteredExpression.Length > 1 + && EnteredExpression[EnteredExpression.Length - 2] == '(') + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 2, 2); + return; + } + + if (!IsOperator(lastCharacter)) + { + EnteredExpression += "×"; + } + + EnteredExpression += "(-"; + return; + } + + // Find last value start index + int lastValueStart = EnteredExpression.Length - 1; + while (lastValueStart > 0 + && (char.IsDigit(EnteredExpression[lastValueStart - 1]) || EnteredExpression[lastValueStart - 1] == '.')) + { + --lastValueStart; + } + + // If it is already negative, make it positive + if (lastValueStart >= 2 && EnteredExpression[lastValueStart - 2] == '(' + && EnteredExpression[lastValueStart - 1] == '-') + { + EnteredExpression = EnteredExpression.Remove(lastValueStart - 2, 2); + return; + } + + if (lastValueStart == 1 && EnteredExpression[0] == '-') + { + EnteredExpression = EnteredExpression.Remove(0, 1); + return; + } + + // Otherwise make it negative + EnteredExpression = EnteredExpression.Insert(lastValueStart, "(-"); + } + + /// + /// Compares number of the entered opening brackets to the closing brackets. + /// + /// 0 if the number equals, more than 0 if there are more opening brackets, + /// otherwise less than 0. + private int BracketBalance() + { + int balance = 0; + foreach (char t in EnteredExpression) + { + balance = t.Equals('(') ? balance + 1 : t.Equals(')') ? balance - 1 : balance; + } + + return balance; + } + + /// + /// Appends bracket to the current expression. + /// + private void AppendBracket() + { + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + + // remove last character if it's a '.' + if (lastCharacter == '.') + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 1); + lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + } + + if (EnteredExpression == "0") + { + EnteredExpression = "("; + } + else if (IsOperator(lastCharacter) || lastCharacter.Equals('(')) + { + EnteredExpression += "("; + } + else if (BracketBalance() > 0) + { + EnteredExpression += ")"; + } + else + { + EnteredExpression += "×("; + } + } + + /// + /// Deletes last character from the current expression. + /// + private void DeleteLastCharacter() + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 1); + if (EnteredExpression.Length == 0) + { + EnteredExpression = "0"; + } + } + + /// + /// Appends decimal point to the current expression. + /// Point is added only when last entered character is a number + /// and has no decimal point added already. + /// + private void AppendDecimalPoint() + { + int index = EnteredExpression.Length - 1; + if (!char.IsDigit(EnteredExpression[index])) + { + return; + } + + while (index > 0 && char.IsDigit(EnteredExpression[index])) + { + --index; + } + + if (!EnteredExpression[index].Equals('.')) + { + EnteredExpression += "."; + } + } + + /// + /// Appends and operator to the current expression. + /// Operator is added when last expression character is not an opening bracket. + /// If method is called with for '-' operator and expression has initial '0' value + /// then current expression is replaced with '-'. + /// If last expression character is a comma, it is removed before appending passed operator. + /// + /// The operator to be added. + private void AppendOperator(object operatorObject) + { + string operatorString = operatorObject as string; + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; // we always have it + + // ignore if last character was '(' + if (lastCharacter.Equals('(') && !operatorString.Equals("-")) + { + return; + } + + // exchange if it's a starting '-' sign + if (operatorString.Equals("-") && EnteredExpression.Equals("0")) + { + EnteredExpression = operatorString; + return; + } + + // if operator pressed after other operator, we exchange it + if (IsOperator(lastCharacter) || lastCharacter.Equals('.')) + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 1) + operatorString; + return; + } + + EnteredExpression += operatorString; + } + + /// + /// Appends one character to currently entered expression. + /// When adding digit to expression with default value (0) replaces 0 with provided digit. + /// + /// String containing character to append. + public void AppendToExpression(object contentObject) + { + string contentToAppend = contentObject as string; + if (contentToAppend == null || contentToAppend.Length > 1) + { + return; + } + + if (!(char.IsDigit(contentToAppend[0]) || contentToAppend[0].Equals('%'))) + { + return; + } + + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + + // digits exchange "0" if it's the only current input + if (EnteredExpression.Equals("0") && char.IsDigit(contentToAppend[0])) + { + EnteredExpression = contentToAppend; + } + + // insert '%' only after digits and ')', otherwise ignore it + else if (contentToAppend.Equals("%")) + { + if (char.IsDigit(lastCharacter) || lastCharacter.Equals(')')) + { + EnteredExpression += contentToAppend; + } + } + else + { + if (lastCharacter.Equals('%')) + { + EnteredExpression += '×'; + } + + EnteredExpression += contentToAppend; + } + } + + /// + /// Evaluates current expression. + /// Updates expression with result value if calculated successfully. + /// + private void EvaluateExpression() + { + MathExpression e = new MathExpression(EnteredExpression); + + try + { + EnteredExpression = e.Evaluate(RESULT_MAX_LENGTH); + } + catch (Exception) + { + // do nothing + } + + } + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/IKeyboardService.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/IKeyboardService.cs new file mode 100755 index 000000000..b66543c10 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/IKeyboardService.cs @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +using System; + +namespace BasicCalculator.ViewModels +{ + /// + /// Interface to maintain Keyboard. + /// + public interface IKeyboardService + { + /// + /// Receives keyboard events. + /// Sender. + /// Key events arguments. + void KeyEvent(object sender, EventArgs keyEventArgs); + + /// + /// Register for keyboard events for keys from the list. + /// + /// List of keys to register. + /// Delegate to call on key event. + void RegisterKeys(string[] keyList, KeyPressedDelegate keyPressedDelegate); + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/KeyboardHandler.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/KeyboardHandler.cs new file mode 100755 index 000000000..6372372d2 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/KeyboardHandler.cs @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace BasicCalculator.ViewModels +{ + /// + /// Delegate to register key-pressed events. + /// + /// Pressed key name. + public delegate void KeyPressedDelegate(string keyName); + + /// + /// Class to handle key-pressed events. + /// + public class KeyboardHandler + { + private readonly CalculatorViewModel _viewModel; + + /// + /// Method called when a key is pressed. + /// + /// Pressed key name. + public void KeyPressed(string keyName) + { + _viewModel.AppendToExpression(keyName); + } + + /// + /// Constructor to ViewModel object reference. + /// + /// CalculatorViewModel object reference. + public KeyboardHandler(CalculatorViewModel viewModel) => _viewModel = viewModel; + } +} \ No newline at end of file diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Views/IViewResolver.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Views/IViewResolver.cs new file mode 100755 index 000000000..9b1449fb5 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Views/IViewResolver.cs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.NUI.Components; + +namespace BasicCalculator.Views +{ + /// + /// View resolver class to obtain views for selected device. + /// + public interface IViewResolver + { + #region methods + + /// + /// Get root page view for this particular device. + /// + /// Root page view. + View GetRootPage(); + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Services/KeyboardService.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Services/KeyboardService.cs new file mode 100755 index 000000000..680ae5ade --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Services/KeyboardService.cs @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using BasicCalculator.Tizen.Mobile.Services; +using BasicCalculator.ViewModels; +using System; +using Tizen.NUI.Binding; + +namespace BasicCalculator.Tizen.Mobile.Services +{ + /// + /// Keyboard listener and event dispatcher. + /// + internal class KeyboardService : IKeyboardService + { + #region fields + + /// + /// Delegate used by to maintain + /// Event registered methods. + /// + private KeyPressedDelegate _keyPressedDelegate; + + #endregion fields + + #region methods + + /// + /// Event fired when any key is received by the application's MainWindow. + /// + /// Sending object's reference. + /// Key event arguments. + public void KeyEvent(object sender, EventArgs keyEventArgs) + { + //_keyPressedDelegate?.Invoke(keyEventArgs.KeyName); + } + + /// + /// Registers KeyUp Event for keys passed as list in the first argument to execute + /// delegate passed in the second argument. + /// + /// List of keys names. + /// Delegate to be executed on KeyUp event. + public void RegisterKeys(string[] keyList, KeyPressedDelegate keyPressedDelegate) + { + _keyPressedDelegate += keyPressedDelegate; + } + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml new file mode 100755 index 000000000..768583ec0 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +