Layout created for single screen app (#2)

This commit is contained in:
2022-09-05 22:58:27 +01:00
committed by GitHub
parent 1e5bbad051
commit 7acca02fc8
12 changed files with 541 additions and 87 deletions

View File

@@ -0,0 +1,54 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'BaseViewModel.dart';
import 'Utils/Constants.dart';
import 'Utils/ViewState.dart';
import 'Utils/ViewUtils.dart';
abstract class BaseStatelessWidget<T extends BaseViewmodel>
extends StatelessWidget {
const BaseStatelessWidget({super.key});
T createViewModel();
Widget displayWidget(BuildContext context, T model, Widget? child);
@override
Widget build(BuildContext parent) {
return Scaffold(
body: Container(
padding: const EdgeInsets.all(PADDING_GLOBAL),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [colourTwo, colourThree])),
child: ViewModelBuilder<T>.reactive(
viewModelBuilder: () => createViewModel(),
builder: (context, model, child) {
var state = model.viewState;
if (state is HasStarted) {
onStarted();
return const Center(
child: CircularProgressIndicator(),
);
} else if (state is HasError) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => ViewUtils.displayToast(parent, state.error));
}
return Center(
child: displayWidget(context, model, child),
);
},
onModelReady: (T model) => onModelReady(model)),
),
);
}
void onModelReady(model) {}
void onStarted() {}
}

24
lib/BaseViewModel.dart Normal file
View File

@@ -0,0 +1,24 @@
import 'package:stacked/stacked.dart';
import 'Utils/ViewState.dart';
abstract class BaseViewmodel extends BaseViewModel{
ViewState _viewState = Idle();
ViewState get viewState => _viewState;
void onStart() {
_viewState = HasStarted();
notifyListeners();
}
void onSuccess(dynamic data) {
_viewState = HasData(data);
notifyListeners();
}
void onError(String error) {
_viewState = HasError(error);
notifyListeners();
}
}

43
lib/Home.dart Normal file
View File

@@ -0,0 +1,43 @@
import 'package:easy_cc_flutter/MainViewModel.dart';
import 'package:easy_cc_flutter/views/DropDownBox.dart';
import 'package:easy_cc_flutter/views/EditText.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'BaseStatelessWidget.dart';
class HomePage extends BaseStatelessWidget<MainViewModel> {
@override
MainViewModel createViewModel() {
return MainViewModel();
}
@override
Widget displayWidget(
BuildContext context, MainViewModel model, Widget? child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
DropDownBox(model.data, (selected) {}),
EditText("Enter conversion from", model.top, (selection) => {})
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
DropDownBox(model.data, (selected) {}),
EditText("Enter conversion from", model.bottom, (selection) => {})
],
),
),
],
);
}
}

163
lib/MainViewModel.dart Normal file
View File

@@ -0,0 +1,163 @@
import 'package:easy_cc_flutter/BaseViewModel.dart';
class MainViewModel extends BaseViewmodel {
String? top;
String? bottom;
final List<String> data = <String>['ALL - Albanian Lek',
'AFN - Afghan Afghani',
'DZD - Algerian Dinar',
'AOA - Angolan Kwanza',
'ARS - Argentine Peso',
'AMD - Armenian Dram',
'AWG - Aruban Florin',
'AUD - Australian Dollar',
'AZN - Azerbaijani Manat',
'BSD - Bahamian Dollar',
'BHD - Bahraini Dinar',
'BDT - Bangladeshi Taka',
'BBD - Barbadian Dollar',
'BYR - Belarusian Ruble',
'BZD - Belize Dollar',
'BTN - Bhutanese Ngultrum',
'BTC - Bitcoin',
'BOB - Bolivian Boliviano',
'BAM - Bosnia And Herzegovina Konvertibilna Marka',
'BWP - Botswana Pula',
'BRL - Brazilian Real',
'GBP - British Pound',
'BND - Brunei Dollar',
'BGN - Bulgarian Lev',
'BIF - Burundi Franc',
'KHR - Cambodian Riel',
'CAD - Canadian Dollar',
'CVE - Cape Verdean Escudo',
'KYD - Cayman Islands Dollar',
'XAF - Central African CFA Franc',
'XPF - CFP Franc',
'CLP - Chilean Peso',
'CNY - Chinese Yuan',
'COP - Colombian Peso',
'KMF - Comorian Franc',
'CDF - Congolese Franc',
'CRC - Costa Rican Colon',
'HRK - Croatian Kuna',
'CUP - Cuban Peso',
'CZK - Czech Koruna',
'DKK - Danish Krone',
'DJF - Djiboutian Franc',
'DOP - Dominican Peso',
'XCD - East Caribbean Dollar',
'EGP - Egyptian Pound',
'ERN - Eritrean Nakfa',
'ETB - Ethiopian Birr',
'EUR - Euro',
'FKP - Falkland Islands Pound',
'FJD - Fijian Dollar',
'GMD - Gambian Dalasi',
'GEL - Georgian Lari',
'GHS - Ghanaian Cedi',
'GIP - Gibraltar Pound',
'GTQ - Guatemalan Quetzal',
'GNF - Guinean Franc',
'GYD - Guyanese Dollar',
'HTG - Haitian Gourde',
'HNL - Honduran Lempira',
'HKD - Hong Kong Dollar',
'HUF - Hungarian Forint',
'ISK - Icelandic Kr\u00f3na',
'INR - Indian Rupee',
'IDR - Indonesian Rupiah',
'IRR - Iranian Rial',
'IQD - Iraqi Dinar',
'ILS - Israeli New Sheqel',
'JMD - Jamaican Dollar',
'JPY - Japanese Yen',
'JOD - Jordanian Dinar',
'KZT - Kazakhstani Tenge',
'KES - Kenyan Shilling',
'KWD - Kuwaiti Dinar',
'KGS - Kyrgyzstani Som',
'LAK - Lao Kip',
'LVL - Latvian Lats',
'LBP - Lebanese Lira',
'LSL - Lesotho Loti',
'LRD - Liberian Dollar',
'LYD - Libyan Dinar',
'MOP - Macanese Pataca',
'MKD - Macedonian Denar',
'MGA - Malagasy Ariary',
'MWK - Malawian Kwacha',
'MYR - Malaysian Ringgit',
'MVR - Maldivian Rufiyaa',
'MRO - Mauritanian Ouguiya',
'MUR - Mauritian Rupee',
'MXN - Mexican Peso',
'MDL - Moldovan Leu',
'MNT - Mongolian Tugrik',
'MAD - Moroccan Dirham',
'MZN - Mozambican Metical',
'MMK - Myanma Kyat',
'NAD - Namibian Dollar',
'NPR - Nepalese Rupee',
'ANG - Netherlands Antillean Gulden',
'TWD - New Taiwan Dollar',
'NZD - New Zealand Dollar',
'NIO - Nicaraguan Cordoba',
'NGN - Nigerian Naira',
'KPW - North Korean Won',
'NOK - Norwegian Krone',
'OMR - Omani Rial',
'TOP - Paanga',
'PKR - Pakistani Rupee',
'PAB - Panamanian Balboa',
'PGK - Papua New Guinean Kina',
'PYG - Paraguayan Guarani',
'PEN - Peruvian Nuevo Sol',
'PHP - Philippine Peso',
'PLN - Polish Zloty',
'QAR - Qatari Riyal',
'RON - Romanian Leu',
'RUB - Russian Ruble',
'RWF - Rwandan Franc',
'SHP - Saint Helena Pound',
'WST - Samoan Tala',
'STD - Sao Tome And Principe Dobra',
'SAR - Saudi Riyal',
'RSD - Serbian Dinar',
'SCR - Seychellois Rupee',
'SLL - Sierra Leonean Leone',
'SGD - Singapore Dollar',
'SBD - Solomon Islands Dollar',
'SOS - Somali Shilling',
'ZAR - South African Rand',
'KRW - South Korean Won',
'XDR - Special Drawing Rights',
'LKR - Sri Lankan Rupee',
'SDG - Sudanese Pound',
'SRD - Surinamese Dollar',
'SZL - Swazi Lilangeni',
'SEK - Swedish Krona',
'CHF - Swiss Franc',
'SYP - Syrian Pound',
'TJS - Tajikistani Somoni',
'TZS - Tanzanian Shilling',
'THB - Thai Baht',
'TTD - Trinidad and Tobago Dollar',
'TND - Tunisian Dinar',
'TRY - Turkish New Lira',
'TMT - Turkmenistan Manat',
'AED - UAE Dirham',
'UGX - Ugandan Shilling',
'UAH - Ukrainian Hryvnia',
'USD - United States Dollar',
'UYU - Uruguayan Peso',
'UZS - Uzbekistani Som',
'VUV - Vanuatu Vatu',
'VEF - Venezuelan Bolivar',
'VND - Vietnamese Dong',
'XOF - West African CFA Franc',
'YER - Yemeni Rial',
'ZMW - Zambian Kwacha'];
}

9
lib/Utils/Constants.dart Normal file
View File

@@ -0,0 +1,9 @@
import 'package:flutter/cupertino.dart';
const Color colourOne = Color(0xFF253031);
const Color colourTwo = Color(0xFF315659);
const Color colourThree = Color(0xFF2978A0);
const Color colourFour = Color(0xFF8549ff);
const Color colourFive = Color(0xFFC6E0FF);
const double PADDING_GLOBAL = 12;

20
lib/Utils/ViewState.dart Normal file
View File

@@ -0,0 +1,20 @@
import 'package:sealed_annotations/sealed_annotations.dart';
@Sealed()
abstract class ViewState {}
class Idle implements ViewState {}
class HasStarted implements ViewState {}
class HasData implements ViewState {
final dynamic data;
HasData(this.data);
}
class HasError implements ViewState {
final String error;
HasError(this.error);
}

18
lib/Utils/ViewUtils.dart Normal file
View File

@@ -0,0 +1,18 @@
import 'package:flutter/cupertino.dart';
import 'package:toast/toast.dart';
class ViewUtils{
static displayToast(BuildContext context, String message){
Toast.show(
message,
duration: Toast.lengthLong,
gravity: Toast.bottom
);
}
static displayToastDeferred(BuildContext context, String message){
WidgetsBinding.instance.addPostFrameCallback(
(_) => displayToast(context, message));
}
}

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'Home.dart';
void main() {
runApp(const MyApp());
}
@@ -24,92 +26,7 @@ class MyApp extends StatelessWidget {
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
home: HomePage(),
);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../Utils/Constants.dart';
class DropDownBox extends StatelessWidget {
final List<String> _selection;
final Function(String?) _onChanged;
const DropDownBox(this._selection, this._onChanged, {super.key});
@override
Widget build(BuildContext context) {
return Card(
color: colourThree,
elevation: 2,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(22))),
child: DropdownSearch<String>(
popupProps: const PopupProps.dialog(showSearchBox: true),
items: _selection,
dropdownDecoratorProps: const DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
contentPadding: EdgeInsets.all(14),
border: InputBorder.none,
filled: true,
fillColor: Colors.transparent,
hintText: "Select a currency",
),
),
onChanged: _onChanged,
),
);
}
}

36
lib/views/EditText.dart Normal file
View File

@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import '../Utils/Constants.dart';
class EditText extends StatelessWidget {
final String _hintText;
final String? _input;
final Function(String?) _onChanged;
const EditText(this._hintText, this._input, this._onChanged, {super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4),
child: TextField(
controller: TextEditingController(
text: _input
),
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(14),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(22)),
borderSide: BorderSide.none,
),
filled: true,
fillColor: colourTwo,
hintText: _hintText,
),
onChanged: _onChanged,
),
);
}
}