Line data Source code
1 : import 'package:flutter/material.dart';
2 : import 'package:flutter/services.dart';
3 : import 'package:logger/logger.dart';
4 : import 'package:flutter_math_fork/flutter_math.dart';
5 : import 'package:clc/fractexpr.dart';
6 : import 'package:clc/fract.dart';
7 : import 'package:url_launcher/url_launcher.dart';
8 : import 'package:package_info_plus/package_info_plus.dart';
9 : import 'package:flutter_svg/flutter_svg.dart';
10 :
11 3 : var log = Logger(
12 1 : printer: SimplePrinter(printTime: true),
13 : );
14 :
15 0 : void main() {
16 0 : runApp(const MyApp());
17 : }
18 :
19 : class MyApp extends StatelessWidget {
20 4 : const MyApp({super.key});
21 :
22 1 : @override
23 : Widget build(BuildContext context) {
24 1 : return MaterialApp(
25 : title: 'Simple Calculator',
26 1 : theme: ThemeData(
27 : primarySwatch: Colors.blue,
28 : ),
29 : home: const MyHomePage(title: 'Simple Calculator'),
30 : );
31 : }
32 : }
33 :
34 : class MyHomePage extends StatefulWidget {
35 3 : const MyHomePage({super.key, required this.title});
36 :
37 : final String title;
38 :
39 1 : @override
40 1 : State<MyHomePage> createState() => _MyHomePageState();
41 : }
42 :
43 : class _MyHomePageState extends State<MyHomePage> {
44 : String _resultStr = "";
45 : String _resultStr2 = "";
46 : String _exprStr = "";
47 : Fraction _result = Fraction(BigInt.one, BigInt.one);
48 : TextEditingController ctrl = TextEditingController();
49 :
50 0 : void _showResult2() {
51 0 : setState(() {
52 0 : _resultStr = _result.toString();
53 0 : _resultStr2 = _result.toString2();
54 : });
55 : }
56 :
57 1 : void _showResult(String s) {
58 2 : setState(() {
59 1 : if (s == "") {
60 0 : _result = Fraction(BigInt.zero, BigInt.one);
61 0 : _resultStr = "";
62 0 : _resultStr2 = "";
63 0 : _exprStr = "";
64 : } else {
65 : try {
66 1 : FractRpn f = FractRpn();
67 1 : var v = f.eval(s);
68 : if (v != null) {
69 1 : _result = v;
70 3 : _resultStr = _result.toExpr();
71 3 : _resultStr2 = _result.toExpr2();
72 2 : _exprStr = f.toExpr();
73 : } else {
74 1 : _resultStr = "ERR";
75 1 : _resultStr2 = "ERR";
76 : }
77 0 : } on Exception catch (e) {
78 0 : _resultStr = "ERR";
79 0 : _resultStr2 = "ERR";
80 0 : log.d("$e");
81 : }
82 : }
83 : });
84 : }
85 :
86 1 : void pushTxt(String txt) {
87 3 : log.d("push $txt");
88 1 : if (txt == "AC") {
89 0 : ctrl.text = "";
90 : txt = "";
91 0 : _showResult(ctrl.text);
92 1 : } else if (txt == "BS") {
93 0 : var start = ctrl.selection.start;
94 0 : log.d("start=$start");
95 0 : if (start == -1) {
96 0 : if (ctrl.text.isNotEmpty) {
97 0 : ctrl.text = ctrl.text.substring(0, ctrl.text.length - 1);
98 : }
99 0 : } else if (start != 0) {
100 0 : ctrl.text =
101 0 : ctrl.text.substring(0, start - 1) + ctrl.text.substring(start);
102 0 : _showResult(ctrl.text);
103 : }
104 : return;
105 1 : } else if (txt == "+/-") {
106 0 : _showResult(ctrl.text);
107 0 : if (_resultStr != "ERR") {
108 0 : _showResult2();
109 0 : ctrl.text = _result.toString();
110 : }
111 : txt = "";
112 1 : } else if (txt == "=") {
113 3 : _showResult(ctrl.text);
114 2 : if (_resultStr != "ERR") {
115 4 : ctrl.text = _result.toString();
116 : }
117 : txt = "";
118 : }
119 3 : var start = ctrl.selection.start;
120 3 : var end = ctrl.selection.end;
121 3 : log.d("pre: start=$start, end=$end");
122 2 : if (start == -1) {
123 3 : start = ctrl.text.length;
124 : }
125 2 : if (end == -1) {
126 : end = start;
127 : }
128 3 : log.d("fix: start=$start, end=$end");
129 10 : ctrl.text = ctrl.text.substring(0, start) + txt + ctrl.text.substring(end);
130 2 : ctrl.selection =
131 4 : TextSelection.fromPosition(TextPosition(offset: start + txt.length));
132 3 : _showResult(ctrl.text);
133 : }
134 :
135 1 : Widget txtbtn(String txt,
136 : {Color fgcolor = Colors.black,
137 : Color bgcolor = Colors.black12,
138 : int flex = 1}) {
139 1 : return Expanded(
140 : flex: flex,
141 1 : child: TextButton(
142 2 : onPressed: () => pushTxt(txt),
143 1 : style: ButtonStyle(
144 1 : foregroundColor: WidgetStateProperty.all<Color>(fgcolor),
145 1 : backgroundColor: WidgetStateProperty.all<Color>(bgcolor),
146 : ),
147 1 : child: Text(txt,
148 : style: const TextStyle(
149 : fontWeight: FontWeight.bold,
150 : fontSize: 25,
151 : )),
152 : ));
153 : }
154 :
155 1 : Widget buttons() {
156 2 : return Column(children: <Widget>[
157 2 : Row(children: <Widget>[
158 1 : txtbtn("7"),
159 1 : txtbtn("8"),
160 1 : txtbtn("9"),
161 1 : txtbtn("(", bgcolor: Colors.orangeAccent),
162 1 : txtbtn(")", bgcolor: Colors.orangeAccent),
163 : ]),
164 2 : Row(children: <Widget>[
165 1 : txtbtn("4"),
166 1 : txtbtn("5"),
167 1 : txtbtn("6"),
168 1 : txtbtn("ร", bgcolor: Colors.orangeAccent),
169 1 : txtbtn("รท", bgcolor: Colors.orangeAccent),
170 : ]),
171 2 : Row(children: <Widget>[
172 1 : txtbtn("1"),
173 1 : txtbtn("2"),
174 1 : txtbtn("3"),
175 1 : txtbtn("+", bgcolor: Colors.orangeAccent),
176 1 : txtbtn("-", bgcolor: Colors.orangeAccent),
177 : ]),
178 2 : Row(children: <Widget>[
179 1 : txtbtn("0"),
180 1 : txtbtn("."),
181 1 : txtbtn("AC", bgcolor: Colors.orangeAccent),
182 1 : txtbtn("BS", bgcolor: Colors.orangeAccent),
183 1 : txtbtn("=", bgcolor: Colors.orangeAccent),
184 : ]),
185 : ]);
186 : }
187 :
188 1 : @override
189 : Widget build(BuildContext context) {
190 1 : return DefaultTabController(
191 : length: 4,
192 1 : child: Builder(
193 1 : builder: (BuildContext context) {
194 1 : return Scaffold(
195 : extendBody: false,
196 : floatingActionButtonLocation:
197 : FloatingActionButtonLocation.startDocked,
198 1 : floatingActionButton: FloatingActionButton(
199 0 : onPressed: () {
200 0 : var tabid = DefaultTabController.of(context).index;
201 : String copyText = "";
202 0 : if (tabid == 1) {
203 0 : copyText = _result.toString2();
204 0 : } else if (tabid == 2) {
205 0 : copyText = _result.toDouble().toString();
206 0 : } else if (tabid == 3) {
207 0 : copyText = _exprStr;
208 : } else {
209 0 : copyText = _result.toString();
210 : }
211 0 : log.d("copy$tabid: $copyText");
212 0 : Clipboard.setData(ClipboardData(text: copyText));
213 : },
214 : tooltip: "copy",
215 : child: const Icon(Icons.copy),
216 : ),
217 1 : drawer: Drawer(
218 1 : child: ListView(
219 1 : children: <Widget>[
220 1 : DrawerHeader(
221 : decoration: const BoxDecoration(color: Colors.blue),
222 3 : child: Text(widget.title,
223 : style: const TextStyle(color: Colors.white)),
224 : ),
225 1 : ListTile(
226 : title: const Row(children: <Widget>[
227 : Icon(Icons.bug_report),
228 : Text("Report issue ๐"),
229 : ]),
230 0 : onTap: () {
231 0 : launchUrl(Uri.parse("https://github.com/wtnb75/clc/issues"));
232 : },
233 : ),
234 1 : ListTile(
235 : title: const Row(children: <Widget>[
236 : Icon(Icons.search),
237 : Text("by google ๐"),
238 : ]),
239 0 : onTap: () {
240 0 : launchUrl(Uri.parse("https://google.com/search").replace(queryParameters: {"q": ctrl.text}));
241 : },
242 : ),
243 1 : ListTile(
244 : title: const Row(children: <Widget>[
245 : Icon(Icons.calculate),
246 : Text("by wolfram alpha ๐"),
247 : ]),
248 0 : onTap: () {
249 0 : launchUrl(Uri.parse("https://www.wolframalpha.com/input/").replace(queryParameters: {"i": ctrl.text}));
250 : },
251 : ),
252 1 : ListTile(
253 : title: const Text("About..."),
254 0 : onTap: () {
255 0 : var packageInfo = PackageInfo.fromPlatform();
256 0 : packageInfo.then((pkginfo) {
257 0 : showDialog(
258 : context: context,
259 0 : builder: (_) {
260 0 : String appName = pkginfo.appName;
261 0 : String packageName = pkginfo.packageName;
262 0 : String version = pkginfo.version;
263 0 : String buildId = pkginfo.buildNumber;
264 0 : String content = "appName = $appName\n";
265 0 : content += "packageName = $packageName\n";
266 0 : content += "version = $version\n";
267 0 : content += "buildId = $buildId\n";
268 0 : return AlertDialog(
269 0 : title: Row(children: [
270 0 : SvgPicture.asset(
271 : "assets/clc.svg",
272 : height: 50,
273 : ),
274 : const VerticalDivider(),
275 0 : Text("About $appName..."),
276 : ]),
277 0 : content: Text(content));
278 : });
279 : });
280 : },
281 : ),
282 : ],
283 : ),
284 : ),
285 1 : appBar: AppBar(
286 3 : title: Text(widget.title),
287 1 : bottom: TabBar(
288 1 : tabs: <Widget>[
289 2 : Tab(child: Math.tex(r'\frac{3}{2}')),
290 2 : Tab(child: Math.tex(r'1 \frac{1}{2}')),
291 2 : Tab(child: Math.tex(r'1.5')),
292 2 : Tab(child: Math.tex(r'\frac{1}{2} \times 3')),
293 : ],
294 : )),
295 1 : body: Container(
296 : decoration:
297 2 : BoxDecoration(border: Border.all(color: Colors.blueGrey)),
298 : constraints: const BoxConstraints(minHeight: 100),
299 : margin: const EdgeInsets.all(8),
300 2 : child: TabBarView(children: <Widget>[
301 1 : Center(
302 1 : child: Math.tex(
303 1 : _resultStr,
304 : mathStyle: MathStyle.display,
305 : textStyle: const TextStyle(fontSize: 42),
306 : )),
307 1 : Center(
308 1 : child: Math.tex(
309 1 : _resultStr2,
310 : mathStyle: MathStyle.display,
311 : textStyle: const TextStyle(fontSize: 42),
312 : )),
313 1 : Center(
314 1 : child: Math.tex(
315 3 : '${_result.toDouble()}',
316 : mathStyle: MathStyle.display,
317 : textStyle: const TextStyle(fontSize: 42),
318 : )),
319 1 : Center(
320 1 : child: Math.tex(
321 1 : _exprStr,
322 : mathStyle: MathStyle.display,
323 : textStyle: const TextStyle(fontSize: 42),
324 : )),
325 : ]),
326 : ),
327 1 : bottomNavigationBar: Container(
328 : padding: const EdgeInsets.all(10),
329 : child:
330 2 : Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
331 1 : Container(
332 : margin: const EdgeInsets.symmetric(horizontal: 8),
333 1 : child: TextField(
334 1 : controller: ctrl,
335 : textAlign: TextAlign.right,
336 : enabled: true,
337 : maxLines: 1,
338 : style: const TextStyle(fontSize: 30),
339 : keyboardType: TextInputType.number,
340 1 : onChanged: _showResult,
341 : decoration: const InputDecoration(
342 : border: OutlineInputBorder(),
343 : hintText: 'Enter formula',
344 : ))),
345 : const Divider(),
346 1 : buttons(),
347 : ]),
348 : ));
349 : },
350 : ));
351 : }
352 : }
|