Browse Source

main functional

master
parent
commit
a892c7b479
  1. 8
      aurora/desktop/su.markow.desktop
  2. 3
      aurora/rpm/su.markow.spec
  3. 22
      lib/address.dart
  4. 102
      lib/counters.dart
  5. 142
      lib/counters_page.dart
  6. 150
      lib/datbase.dart
  7. 19
      lib/l10n/app_en.arb
  8. 19
      lib/l10n/app_ru.arb
  9. 56
      lib/main.dart
  10. 69
      lib/new_counter_page.dart
  11. 208
      lib/new_value_page.dart
  12. 43
      lib/record_action_pane.dart
  13. 62
      lib/update_address_page.dart
  14. 76
      lib/update_counter_page.dart
  15. 45
      lib/value.dart
  16. 119
      lib/values_page.dart
  17. 88
      pubspec.lock
  18. 16
      pubspec.yaml

8
aurora/desktop/com.example.counters.desktop → aurora/desktop/su.markow.desktop

@ -2,11 +2,11 @@
Type=Application Type=Application
Name=counters Name=counters
Comment=A new Flutter project. Comment=A new Flutter project.
Icon=com.example.counters Icon=su.markow.counters
Exec=/usr/bin/com.example.counters Exec=/usr/bin/su.markow.counters
X-Nemo-Application-Type=silica-qt5 X-Nemo-Application-Type=silica-qt5
[X-Application] [X-Application]
Permissions= Permissions=UserDirs
OrganizationName=com.example OrganizationName=su.markow
ApplicationName=counters ApplicationName=counters

3
aurora/rpm/com.example.counters.spec → aurora/rpm/su.markow.spec

@ -1,7 +1,7 @@
%global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ %global __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$
%global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|.+_platform_plugin)\\.so.*$ %global __requires_exclude ^lib(dconf|flutter-embedder|maliit-glib|.+_platform_plugin)\\.so.*$
Name: com.example.counters Name: su.markow.counters
Summary: A new Flutter project. Summary: A new Flutter project.
Version: 0.1.0 Version: 0.1.0
Release: 1 Release: 1
@ -11,6 +11,7 @@ Source0: %{name}-%{version}.tar.zst
BuildRequires: cmake BuildRequires: cmake
BuildRequires: ninja BuildRequires: ninja
BuildRequires: pkgconfig(flutter-embedder) BuildRequires: pkgconfig(flutter-embedder)
BuildRequires: pkgconfig(sqlite3)
%description %description
%{summary}. %{summary}.

22
lib/address.dart

@ -1,28 +1,22 @@
// To parse this JSON data, do
//
// final address = addressFromJson(jsonString);
import 'dart:convert';
Address addressFromJson(String str) => Address.fromJson(json.decode(str));
String addressToJson(Address data) => json.encode(data.toJson());
class Address { class Address {
String streetName; final int? id;
String comments; final String streetName;
final String comments;
Address({ Address({
this.id,
required this.streetName, required this.streetName,
required this.comments, required this.comments,
}); });
factory Address.fromJson(Map<String, dynamic> json) => Address( factory Address.fromMap(Map<String, dynamic> json) => Address(
id: json["id"],
streetName: json["street_name"], streetName: json["street_name"],
comments: json["comments"], comments: json["comments"],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toMap() => {
"id": id,
"street_name": streetName, "street_name": streetName,
"comments": comments, "comments": comments,
}; };

102
lib/counters.dart

@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
enum CounterType {
coldWater(_coldWaterId, Colors.blue, Icons.water_drop),
hotWater(_hotWaterId, Colors.red, Icons.water_drop),
electrisity1rates(_electrisity1rateId, Colors.lightBlue, Icons.tungsten),
electrisity2rates(_electrisity2rateId, Colors.lightBlue, Icons.tungsten),
electrisity3rates(_electrisity3rateId, Colors.lightBlue, Icons.tungsten),
gas(_gasId, Colors.blueAccent, Icons.local_fire_department);
const CounterType(this.id, this.color, this.icon);
static const int _coldWaterId = 0;
static const int _hotWaterId = 1;
static const int _gasId = 2;
static const int _electrisity1rateId = 3;
static const int _electrisity2rateId = 4;
static const int _electrisity3rateId = 5;
final int id;
final Color color;
final IconData icon;
String getLabel(BuildContext context) {
switch (id) {
case _coldWaterId:
return AppLocalizations.of(context)!.cold_water_type_str;
case _hotWaterId:
return AppLocalizations.of(context)!.hot_water_type_str;
case _electrisity1rateId:
return AppLocalizations.of(context)!.electrisity1_type_str;
case _electrisity2rateId:
return AppLocalizations.of(context)!.electrisity2_type_str;
case _electrisity3rateId:
return AppLocalizations.of(context)!.electrisity3_type_str;
case _gasId:
return AppLocalizations.of(context)!.gas_type_str;
default:
}
return AppLocalizations.of(context)!.unknown_type_str;
}
CounterUnits getUnits() {
if (id < _electrisity1rateId) {
return CounterUnits.m3;
}
return CounterUnits.kVt;
}
}
enum CounterUnits {
m3(0),
kVt(1);
const CounterUnits(this.id);
final int id;
String getLabel(BuildContext context) {
switch (id) {
case 0:
return AppLocalizations.of(context)!.m3;
case 1:
return AppLocalizations.of(context)!.kVt;
}
return '';
}
}
class Counter {
final int? id;
final int addressId;
final CounterType counterType;
final String name;
final double? value;
Counter(
{this.id,
required this.addressId,
required this.counterType,
required this.name,
this.value});
factory Counter.fromMap(Map<String, dynamic> json) {
return Counter(
id: json["id"],
addressId: json["address_id"],
counterType: CounterType.values
.firstWhere((element) => element.id == json["counter_type"]),
name: json["name"],
value: json["value"].toDouble());
}
Map<String, dynamic> toMap() => {
"id": id,
"address_id": addressId,
"counter_type": counterType.id,
"name": name
};
}

142
lib/counters_page.dart

@ -1,9 +1,25 @@
import 'dart:ffi';
import 'package:counters/address.dart';
import 'package:counters/datbase.dart';
import 'package:counters/new_counter_page.dart';
import 'package:counters/record_action_pane.dart';
import 'package:counters/update_counter_page.dart';
import 'package:counters/values_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'counters.dart';
class CountersPage extends StatefulWidget { class CountersPage extends StatefulWidget {
const CountersPage({super.key, required this.title, required this.comments}); const CountersPage(
{super.key,
required this.title,
required this.comments,
required this.address});
final Address address;
final String title; final String title;
final String comments; final String comments;
@ -14,12 +30,13 @@ class CountersPage extends StatefulWidget {
class _CountersPageState extends State<CountersPage> { class _CountersPageState extends State<CountersPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final counters = DBProvider.db.getCountersOfAddress(widget.address);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
widget.title, widget.title,
@ -32,14 +49,29 @@ class _CountersPageState extends State<CountersPage> {
], ],
), ),
), ),
body: const SizedBox(), body: Center(
child: FutureBuilder(
future: counters,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return CountersListView(
counters: snapshot.data!,
onUpdated: () {
setState(() {});
});
}
return Text(AppLocalizations.of(context)!.empty_counters_list);
},
)),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const NewCounterPage())) builder: (context) => NewCounterPage(
.then((_) => setState(() {})); addressId: widget.address.id!,
))).then((_) => setState(() {}));
}, },
tooltip: AppLocalizations.of(context)!.add_new_counter_tooltip, tooltip: AppLocalizations.of(context)!.add_new_counter_tooltip,
child: const Icon(Icons.add), child: const Icon(Icons.add),
@ -47,16 +79,100 @@ class _CountersPageState extends State<CountersPage> {
} }
} }
class NewCounterPage extends StatelessWidget { class CountersListView extends StatefulWidget {
const NewCounterPage({super.key}); const CountersListView({
super.key,
required this.counters,
this.onUpdated,
});
final List<Counter> counters;
final VoidCallback? onUpdated;
@override
State<CountersListView> createState() => _CountersListViewState();
}
class _CountersListViewState extends State<CountersListView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ListView.builder(
appBar: AppBar( itemCount: widget.counters.length,
backgroundColor: Theme.of(context).colorScheme.inversePrimary, itemBuilder: (context, index) {
title: Text(AppLocalizations.of(context)!.new_counter_title)), return Slidable(
body: const SizedBox(), key: const ValueKey(0),
endActionPane: RecordActionPane(onEdit: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UpdateCounterPage(
counter: widget.counters[index],
))).then((_) => widget.onUpdated?.call());
}, onDelete: () {
DBProvider.db.deleteCounter(widget.counters[index]).then((value) {
widget.onUpdated?.call();
});
}).build(context),
child: SizedBox(
height: 60,
child: TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ValuesPage(
counter: widget.counters[index],
))).then((value) => widget.onUpdated?.call());
},
style:
TextButton.styleFrom(shape: const BeveledRectangleBorder()),
child: Row(
children: [
Icon(widget.counters[index].counterType.icon,
color: widget.counters[index].counterType.color),
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.counters[index].counterType
.getLabel(context),
style: Theme.of(context).textTheme.bodyLarge,
),
Row(
children: [
Text(
widget.counters[index].name.isNotEmpty
? "${widget.counters[index].name} "
: "",
style: Theme.of(context).textTheme.bodySmall,
),
Text(
'${widget.counters[index].value} ',
//style: Theme.of(context).textTheme.bodySmall,
),
Text(
widget.counters[index].counterType
.getUnits()
.getLabel(context),
style: Theme.of(context).textTheme.bodySmall,
)
],
)
],
),
),
),
],
),
),
),
);
},
); );
} }
} }

150
lib/datbase.dart

@ -2,6 +2,8 @@ import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'package:counters/address.dart'; import 'package:counters/address.dart';
import 'package:counters/counters.dart';
import 'package:counters/value.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
@ -9,6 +11,9 @@ import 'package:sqflite/sqflite.dart';
class DBProvider { class DBProvider {
static const int dbVersion = 1; static const int dbVersion = 1;
static const String addressTableName = 'address$dbVersion'; static const String addressTableName = 'address$dbVersion';
static const String countersTableName = 'counters$dbVersion';
static const String ratesTableName = 'rates$dbVersion';
static const String valuesTableName = 'values$dbVersion';
DBProvider._(); DBProvider._();
static final DBProvider db = DBProvider._(); static final DBProvider db = DBProvider._();
@ -20,23 +25,59 @@ class DBProvider {
return _database!; return _database!;
} }
createTables(Database db) async {
await db.execute("CREATE TABLE IF NOT EXISTS $addressTableName ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"street_name TEXT,"
"comments TEXT"
")");
await db.execute("CREATE TABLE IF NOT EXISTS $countersTableName ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"address_id INTEGER,"
"counter_type INTEGER,"
"name TEXT,"
"FOREIGN KEY (address_id) REFERENCES $addressTableName (id)"
")");
await db.execute("CREATE TABLE IF NOT EXISTS $ratesTableName ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"counter_id INTEGER,"
"rate INTEGER,"
"name TEXT,"
"FOREIGN KEY (counter_id) REFERENCES $countersTableName (id)"
")");
await db.execute("CREATE TABLE IF NOT EXISTS $valuesTableName ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"counter_id INTEGER,"
"date INTEGER,"
"rate1_id INTEGER,"
"value1 REAL default 0.0,"
"rate2_id INTEGER,"
"value2 REAL default 0.0,"
"rate3_id INTEGER,"
"value3 REAL default 0.0,"
"FOREIGN KEY (counter_id) REFERENCES $countersTableName (id),"
"FOREIGN KEY (rate1_id) REFERENCES $ratesTableName (id),"
"FOREIGN KEY (rate2_id) REFERENCES $ratesTableName (id),"
"FOREIGN KEY (rate3_id) REFERENCES $ratesTableName (id)"
")");
}
initDB() async { initDB() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory(); Directory documentsDirectory = await getApplicationSupportDirectory();
String path = join(documentsDirectory.path, "counters.db"); String path = join(documentsDirectory.path, "counters.db");
print(path); print(path);
return await openDatabase(path, version: dbVersion, onOpen: (db) {}, return await openDatabase(path, version: dbVersion, onOpen: createTables,
onCreate: (Database db, int version) async { onCreate: (Database db, int version) async {
await db.execute("CREATE TABLE $addressTableName (" await createTables(db);
"id INTEGER PRIMARY KEY,"
"street_name TEXT,"
"comments TEXT"
")");
}); });
} }
Future<int> newAddress(Address newAddress) async { Future<int> newAddress(Address newAddress) async {
final db = await database; final db = await database;
var res = await db.insert(addressTableName, newAddress.toJson()); var res = await db.insert(addressTableName, newAddress.toMap());
return res; return res;
} }
@ -44,14 +85,103 @@ class DBProvider {
final db = await database; final db = await database;
var res = var res =
await db.query(addressTableName, where: "id = ?", whereArgs: [id]); await db.query(addressTableName, where: "id = ?", whereArgs: [id]);
return res.isNotEmpty ? Address.fromJson(res.first) : Null; return res.isNotEmpty ? Address.fromMap(res.first) : Null;
} }
Future<List<Address>> getAllAddress() async { Future<List<Address>> getAllAddress() async {
final db = await database; final db = await database;
var res = await db.query(addressTableName); var res = await db.query(addressTableName);
List<Address> list = List<Address> list =
res.isNotEmpty ? res.map((c) => Address.fromJson(c)).toList() : []; res.isNotEmpty ? res.map((c) => Address.fromMap(c)).toList() : [];
return list;
}
Future deleteAddress(Address address) async {
final db = await database;
await deleteValuesByAddress(address);
await deleteCountersByAddress(address);
await db.delete(addressTableName, where: "id = ?", whereArgs: [address.id]);
}
Future updateAddress(Address address) async {
final db = await database;
var res = await db.update(addressTableName, address.toMap(),
where: "id = ?", whereArgs: [address.id]);
return res;
}
Future<List<Counter>> getCountersOfAddress(Address address) async {
final db = await database;
var sql =
'select $countersTableName.id, $countersTableName.address_id, $countersTableName.counter_type, $countersTableName.name, 0 as value '
' from $countersTableName '
' where address_id=${address.id} and $countersTableName.id not in (select counter_id from $valuesTableName) '
' union '
'select $countersTableName.id, $countersTableName.address_id, $countersTableName.counter_type, $countersTableName.name, sum($valuesTableName.value1) + sum($valuesTableName.value2) + sum($valuesTableName.value3) as value '
' from $countersTableName '
' INNER JOIN $valuesTableName ON $valuesTableName.counter_id = $countersTableName.id '
' where address_id=${address.id} '
' group by counter_type';
var res = await db.rawQuery(sql, []);
print(res);
List<Counter> list =
res.isNotEmpty ? res.map((c) => Counter.fromMap(c)).toList() : [];
return list;
}
Future<int> newCounter(Counter newCounter) async {
final db = await database;
var res = await db.insert(countersTableName, newCounter.toMap());
return res;
}
Future updateCounter(Counter counter) async {
final db = await database;
var res = await db.update(countersTableName, counter.toMap(),
where: "id = ?", whereArgs: [counter.id]);
return res;
}
Future deleteCounter(Counter counter) async {
final db = await database;
await deleteValuesByCounter(counter);
await db
.delete(countersTableName, where: "id = ?", whereArgs: [counter.id]);
}
deleteCountersByAddress(Address address) async {
final db = await database;
await db.delete(countersTableName,
where: 'address_id = ?', whereArgs: [address.id]);
}
Future<List<Value>> getValuesOfCounter(Counter counter) async {
final db = await database;
var res = await db.query(valuesTableName,
where: "counter_id = ?", whereArgs: [counter.id]);
List<Value> list =
res.isNotEmpty ? res.map((v) => Value.fromMap(v)).toList() : [];
return list; return list;
} }
Future<int> newValue(Value newValue) async {
final db = await database;
var res = await db.insert(valuesTableName, newValue.toMap());
return res;
}
deleteValuesByAddress(Address address) async {
final db = await database;
await db.delete(valuesTableName,
where:
'counter_id in (select id from $countersTableName where address_id = ?)',
whereArgs: [address.id]);
}
deleteValuesByCounter(Counter counter) async {
final db = await database;
await db.delete(valuesTableName,
where: 'counter_id = ?', whereArgs: [counter.id]);
}
} }

19
lib/l10n/app_en.arb

@ -10,5 +10,22 @@
"enter_your_address_comments": "Enter your comment", "enter_your_address_comments": "Enter your comment",
"add_new_address_button": "Add", "add_new_address_button": "Add",
"new_counter_title": "New counter", "new_counter_title": "New counter",
"add_new_counter_tooltip": "New counter" "add_new_counter_tooltip": "New counter",
"empty_counters_list": "List of counters is empty",
"enter_counter_name": "Enter counter name",
"cold_water_type_str": "Cold water",
"hot_water_type_str": "Hot water",
"electrisity1_type_str": "Electrisity (1 rate)",
"electrisity2_type_str": "Electrisity (2 rates)",
"electrisity3_type_str": "Electrisity (3 reates)",
"gas_type_str": "Gas",
"unknown_type_str": "Unknown counter type",
"choose_type_of_counter": "choose type of counter",
"empty_values_list": "List of values is empty",
"new_value_title": "Values",
"delete": "Delete",
"edit": "Edit",
"kVt": "kVt",
"m3": "m3",
"values": "Values"
} }

19
lib/l10n/app_ru.arb

@ -7,5 +7,22 @@
"enter_your_address_comments": "Добавте комментарий (опционально)", "enter_your_address_comments": "Добавте комментарий (опционально)",
"add_new_address_button": "Добавить", "add_new_address_button": "Добавить",
"new_counter_title": "Новый счетчик", "new_counter_title": "Новый счетчик",
"add_new_counter_tooltip": "Новый счетчик" "add_new_counter_tooltip": "Новый счетчик",
"empty_counters_list": "Список счетчиков пуст",
"enter_counter_name": "Введите название счетчика",
"cold_water_type_str": "Холодная вода",
"hot_water_type_str": "Горячая вода",
"electrisity1_type_str": "Электричество (1 тариф)",
"electrisity2_type_str": "Электричество (2 тариф)",
"electrisity3_type_str": "Электричество (3 тариф)",
"gas_type_str": "Газ",
"unknown_type_str": "Неизвестный тип счетчка",
"choose_type_of_counter": "Выберите тип счетчика",
"empty_values_list": "Список показаний пуст",
"new_value_title": "Показания счетчика",
"delete": "Удалить",
"edit": "Изменить",
"kVt": "кВт",
"m3": "куб.м",
"values": "Показания"
} }

56
lib/main.dart

@ -4,12 +4,18 @@ import 'package:counters/address.dart';
import 'package:counters/counters_page.dart'; import 'package:counters/counters_page.dart';
import 'package:counters/datbase.dart'; import 'package:counters/datbase.dart';
import 'package:counters/new_address_page.dart'; import 'package:counters/new_address_page.dart';
import 'package:counters/record_action_pane.dart';
import 'package:counters/update_address_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:intl/intl_standalone.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized();
await findSystemLocale();
if (Platform.isWindows || Platform.isLinux) { if (Platform.isWindows || Platform.isLinux) {
sqfliteFfiInit(); sqfliteFfiInit();
} }
@ -60,7 +66,12 @@ class _MyHomePageState extends State<MyHomePage> {
future: addresses, future: addresses,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) { if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return AddressesListView(addresses: snapshot.data!); return AddressesListView(
addresses: snapshot.data!,
onUpdated: () {
setState(() {});
},
);
} }
return Text(AppLocalizations.of(context)!.empty_addresses_list); return Text(AppLocalizations.of(context)!.empty_addresses_list);
@ -79,28 +90,54 @@ class _MyHomePageState extends State<MyHomePage> {
} }
} }
class AddressesListView extends StatelessWidget { class AddressesListView extends StatefulWidget {
const AddressesListView({ const AddressesListView({
super.key, super.key,
required this.addresses, required this.addresses,
this.onUpdated,
}); });
final List<Address> addresses; final List<Address> addresses;
final VoidCallback? onUpdated;
@override
State<AddressesListView> createState() => _AddressesListViewState();
}
class _AddressesListViewState extends State<AddressesListView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView.builder( return ListView.builder(
itemCount: addresses.length, itemCount: widget.addresses.length,
itemBuilder: (context, possition) { itemBuilder: (context, possition) {
return TextButton( return Slidable(
key: const ValueKey(0),
endActionPane: RecordActionPane(
onEdit: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UpdateAddressPage(
address: widget.addresses[possition],
))).then((_) => widget.onUpdated?.call());
},
onDelete: () {
DBProvider.db.deleteAddress(widget.addresses[possition]);
widget.onUpdated?.call();
},
).build(context),
child: TextButton(
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => CountersPage( builder: (context) => CountersPage(
title: addresses[possition].streetName, address: widget.addresses[possition],
comments: addresses[possition].comments))); title: widget.addresses[possition].streetName,
comments: widget.addresses[possition].comments)));
}, },
style:
TextButton.styleFrom(shape: const BeveledRectangleBorder()),
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
@ -110,16 +147,17 @@ class AddressesListView extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
addresses[possition].streetName, widget.addresses[possition].streetName,
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
Text( Text(
addresses[possition].comments, widget.addresses[possition].comments,
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
], ],
)), )),
), ),
),
); );
}); });
} }

69
lib/new_counter_page.dart

@ -0,0 +1,69 @@
import 'package:counters/counters.dart';
import 'package:counters/datbase.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class NewCounterPage extends StatelessWidget {
final int addressId;
final nameController = TextEditingController();
CounterType counterType = CounterType.coldWater;
NewCounterPage({super.key, required this.addressId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.new_counter_title)),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
DropdownMenu<CounterType>(
enableFilter: true,
hintText:
AppLocalizations.of(context)!.choose_type_of_counter,
expandedInsets: EdgeInsets.zero,
dropdownMenuEntries:
CounterType.values.map<DropdownMenuEntry<CounterType>>(
(CounterType icon) {
return DropdownMenuEntry<CounterType>(
value: icon,
labelWidget: Text(icon.getLabel(context)),
label: icon.getLabel(context)
//leadingIcon: Icon(icon.icon),
);
},
).toList(),
onSelected: (value) => counterType = value!,
),
const SizedBox(height: 50),
TextField(
controller: nameController,
decoration: InputDecoration(
hintStyle: const TextStyle(color: Colors.blue),
hintText:
AppLocalizations.of(context)!.enter_counter_name),
),
const SizedBox(height: 50),
TextButton(
onPressed: () {
DBProvider.db
.newCounter(Counter(
addressId: addressId,
counterType: counterType,
name: nameController.text))
.then((value) => Navigator.pop(context));
},
child: Text(
AppLocalizations.of(context)!.add_new_address_button))
]),
),
),
);
}
}

208
lib/new_value_page.dart

@ -0,0 +1,208 @@
import 'package:counters/counters.dart';
import 'package:counters/datbase.dart';
import 'package:counters/value.dart';
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
class NewValuePage extends StatelessWidget {
NewValuePage({super.key, required this.counter});
factory NewValuePage.forCounter({required Counter counter}) {
switch (counter.counterType) {
case CounterType.coldWater:
return NewValuePageWithOneRate(counter: counter);
case CounterType.hotWater:
return NewValuePageWithOneRate(counter: counter);
case CounterType.gas:
return NewValuePageWithOneRate(counter: counter);
case CounterType.electrisity1rates:
return NewValuePageWithOneRate(counter: counter);
case CounterType.electrisity2rates:
return NewValuePageWithTwoRates(counter: counter);
case CounterType.electrisity3rates:
return NewValuePageWithThreeRates(counter: counter);
}
}
final Counter counter;
final dataFormat = DateFormat("dd-MM-yyyy");
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.new_value_title)),
body: SizedBox.shrink(),
);
}
}
class NewValuePageWithOneRate extends NewValuePage {
final valueController = TextEditingController();
final dateController = TextEditingController();
NewValuePageWithOneRate({super.key, required super.counter});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.new_value_title)),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
DateTimeField(
format: dataFormat,
initialValue: DateTime.now(),
controller: dateController,
onShowPicker: (context, currentValue) async {
final date = await showDatePicker(
context: context,
initialDate: currentValue ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
return date;
},
),
const SizedBox(height: 20),
TextField(
controller: valueController,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'(^-?\d*\.?\d*)'))
],
decoration: InputDecoration(
label:
Text(AppLocalizations.of(context)!.new_value_title),
//hintStyle: const TextStyle(color: Colors.blue),
hintText: '0.0',
suffixText:
counter.counterType.getUnits().getLabel(context))),
const SizedBox(height: 50),
TextButton(
onPressed: () {
DBProvider.db
.newValue(Value(
counterId: counter.id!,
date: dataFormat.parse(dateController.text).toUtc(),
rate1Id: 0,
value1:
double.tryParse(valueController.text) ?? 0.0,
value2: 0,
value3: 0))
.then((value) => Navigator.pop(context));
},
child: Text(
AppLocalizations.of(context)!.add_new_address_button))
],
),
),
),
);
}
}
class NewValuePageWithTwoRates extends NewValuePage {
final value1Controller = TextEditingController();
final value2Controller = TextEditingController();
final dateController = TextEditingController();
NewValuePageWithTwoRates({super.key, required super.counter});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.new_value_title)),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
DateTimeField(
format: DateFormat("dd-MM-yyyy"),
initialValue: DateTime.now(),
controller: dateController,
onShowPicker: (context, currentValue) async {
final date = await showDatePicker(
context: context,
initialDate: currentValue ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
return date;
},
),
const SizedBox(height: 20),
TextField(
controller: value1Controller,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'(^-?\d*\.?\d*)'))
],
decoration: InputDecoration(
label:
Text(AppLocalizations.of(context)!.new_value_title),
//hintStyle: const TextStyle(color: Colors.blue),
hintText: '0.0',
suffixText:
counter.counterType.getUnits().getLabel(context))),
TextField(
controller: value2Controller,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'(^-?\d*\.?\d*)'))
],
decoration: InputDecoration(
label:
Text(AppLocalizations.of(context)!.new_value_title),
//hintStyle: const TextStyle(color: Colors.blue),
hintText: '0.0',
suffixText:
counter.counterType.getUnits().getLabel(context))),
const SizedBox(height: 50),
TextButton(
onPressed: () {
DBProvider.db
.newValue(Value(
counterId: counter.id!,
date: dataFormat.parse(dateController.text).toUtc(),
rate1Id: 0,
value1:
double.tryParse(value1Controller.text) ?? 0.0,
value2:
double.tryParse(value2Controller.text) ?? 0.0,
value3: 0))
.then((value) => Navigator.pop(context));
},
child: Text(
AppLocalizations.of(context)!.add_new_address_button))
],
),
),
),
);
}
}
class NewValuePageWithThreeRates extends NewValuePage {
NewValuePageWithThreeRates({super.key, required super.counter});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.new_value_title)),
body: SizedBox.shrink(),
);
}
}

43
lib/record_action_pane.dart

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class RecordActionPane extends StatelessWidget {
const RecordActionPane({
super.key,
this.onDelete,
this.onEdit,
});
final VoidCallback? onDelete;
final VoidCallback? onEdit;
@override
ActionPane build(BuildContext context) {
return ActionPane(
motion: const BehindMotion(),
//dismissible: DismissiblePane(onDismissed: () {}),
children: [
SlidableAction(
onPressed: (context) {
onEdit?.call();
},
backgroundColor: Colors.green,
foregroundColor: Colors.white,
icon: Icons.edit,
label: AppLocalizations.of(context)!.edit,
),
SlidableAction(
onPressed: (context) {
onDelete?.call();
},
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.delete,
label: AppLocalizations.of(context)!.delete,
),
],
);
}
}

62
lib/update_address_page.dart

@ -0,0 +1,62 @@
import 'package:counters/address.dart';
import 'package:counters/datbase.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class UpdateAddressPage extends StatelessWidget {
final streetNameController = TextEditingController();
final commentsController = TextEditingController();
UpdateAddressPage({super.key, required this.address});
final Address address;
@override
Widget build(BuildContext context) {
streetNameController.text = address.streetName;
commentsController.text = address.comments;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.new_address_title),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(
controller: streetNameController,
decoration: InputDecoration(
hintStyle: const TextStyle(color: Colors.blue),
hintText:
AppLocalizations.of(context)!.enter_your_address),
),
const SizedBox(height: 50),
TextField(
controller: commentsController,
decoration: InputDecoration(
hintStyle: const TextStyle(color: Colors.blue),
hintText: AppLocalizations.of(context)!
.enter_your_address_comments),
),
const SizedBox(height: 50),
TextButton(
onPressed: () {
DBProvider.db
.updateAddress(Address(
id: address.id,
streetName: streetNameController.text,
comments: commentsController.text))
.then((value) => Navigator.pop(context));
},
child: Text(AppLocalizations.of(context)!.edit))
]),
),
),
);
}
}

76
lib/update_counter_page.dart

@ -0,0 +1,76 @@
import 'package:counters/counters.dart';
import 'package:counters/datbase.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class UpdateCounterPage extends StatelessWidget {
UpdateCounterPage({super.key, required this.counter});
final Counter counter;
final nameController = TextEditingController();
final dropdownController = TextEditingController();
CounterType counterType = CounterType.coldWater;
@override
@override
Widget build(BuildContext context) {
nameController.text = counter.name;
counterType = counter.counterType;
dropdownController.text = counter.counterType.getLabel(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.new_counter_title)),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
DropdownMenu<CounterType>(
controller: dropdownController,
enableFilter: true,
hintText:
AppLocalizations.of(context)!.choose_type_of_counter,
expandedInsets: EdgeInsets.zero,
dropdownMenuEntries:
CounterType.values.map<DropdownMenuEntry<CounterType>>(
(CounterType icon) {
return DropdownMenuEntry<CounterType>(
value: icon,
labelWidget: Text(icon.getLabel(context)),
label: icon.getLabel(context)
//leadingIcon: Icon(icon.icon),
);
},
).toList(),
onSelected: (value) => counterType = value!,
),
const SizedBox(height: 50),
TextField(
controller: nameController,
decoration: InputDecoration(
hintStyle: const TextStyle(color: Colors.blue),
hintText:
AppLocalizations.of(context)!.enter_counter_name),
),
const SizedBox(height: 50),
TextButton(
onPressed: () {
DBProvider.db
.updateCounter(Counter(
id: counter.id,
addressId: counter.addressId,
counterType: counterType,
name: nameController.text))
.then((value) => Navigator.pop(context));
},
child: Text(AppLocalizations.of(context)!.edit))
]),
),
),
);
}
}

45
lib/value.dart

@ -0,0 +1,45 @@
class Value {
final int? id;
final int counterId;
final DateTime date;
final int rate1Id;
final double value1;
final int? rate2Id;
final double? value2;
final int? rate3Id;
final double? value3;
Value(
{this.id,
required this.counterId,
required this.date,
required this.rate1Id,
required this.value1,
this.rate2Id,
this.value2,
this.rate3Id,
this.value3});
factory Value.fromMap(Map<String, dynamic> json) => Value(
id: json["id"],
counterId: json["counter_id"],
date: DateTime.fromMicrosecondsSinceEpoch(json["date"], isUtc: true),
rate1Id: json["rate1_id"],
value1: json["value1"],
rate2Id: json["rate2_id"],
value2: json["value2"],
rate3Id: json["rate3_id"],
value3: json["value3"]);
Map<String, dynamic> toMap() => {
"id": id,
"counter_id": counterId,
"date": date.microsecondsSinceEpoch,
"rate1_id": rate1Id,
"value1": value1,
"rate2_id": rate2Id,
"value2": value2,
"rate3_id": rate3Id,
"value3": value3
};
}

119
lib/values_page.dart

@ -0,0 +1,119 @@
import 'package:counters/counters.dart';
import 'package:counters/datbase.dart';
import 'package:counters/new_value_page.dart';
import 'package:counters/value.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
class ValuesPage extends StatefulWidget {
const ValuesPage({super.key, required this.counter});
final Counter counter;
@override
State<ValuesPage> createState() => _ValuesPageState();
}
class _ValuesPageState extends State<ValuesPage> {
@override
Widget build(BuildContext context) {
var values = DBProvider.db.getValuesOfCounter(widget.counter);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(AppLocalizations.of(context)!.app_title)),
body: Center(
child: FutureBuilder(
future: values,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return ValuesListView(
counter: widget.counter,
values: snapshot.data!.reversed.toList());
}
return Text(AppLocalizations.of(context)!.empty_values_list);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
NewValuePage.forCounter(counter: widget.counter)))
.then((_) => setState(() {}));
},
tooltip: AppLocalizations.of(context)!.add_new_counter_tooltip,
child: const Icon(Icons.add),
));
}
}
class ValuesListView extends StatelessWidget {
ValuesListView({
super.key,
required this.values,
required this.counter,
});
final List<Value> values;
final Counter counter;
final dataFormat = DateFormat("dd-MM-yyyy");
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: values.length,
itemBuilder: (context, index) {
var units = counter.counterType.getUnits().getLabel(context);
var valueWidgets = List<Widget>.empty(growable: true);
valueWidgets.add(Text("T1 - ${values[index].value1} $units"));
var sum = values[index].value1;
var value = values[index];
if (value.value2 != null &&
counter.counterType.id > CounterType.electrisity1rates.id) {
valueWidgets.add(Text("T2 - ${value.value2} $units"));
sum += value.value2!;
}
if (value.value3 != null &&
counter.counterType.id > CounterType.electrisity2rates.id) {
valueWidgets.add(Text("T3 - ${value.value3} $units"));
sum += value.value3!;
}
if (valueWidgets.length > 1) {
valueWidgets.add(Text("Итого - $sum $units"));
}
return Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: Card(
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(dataFormat.format(value.date.toLocal())),
Text(' ${AppLocalizations.of(context)!.values}:'),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: valueWidgets,
)
],
),
),
)),
);
},
);
}
}

88
pubspec.lock

@ -49,6 +49,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.8"
datetime_picker_formfield:
dependency: "direct main"
description:
name: datetime_picker_formfield
sha256: "6d0412c98cc5da18a5dca1f81f82a834fbacdb5d249fd6d9bed42d912339720e"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -83,11 +91,40 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_slidable:
dependency: "direct main"
description:
name: flutter_slidable
sha256: "673403d2eeef1f9e8483bd6d8d92aae73b1d8bd71f382bc3930f699c731bc27c"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev"
source: hosted
version: "1.2.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -136,6 +173,31 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
package_info_plus:
dependency: transitive
description:
name: package_info_plus
sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
package_info_plus_aurora:
dependency: "direct main"
description:
path: "packages/package_info_plus/package_info_plus_aurora"
ref: "package_info_plus_aurora-0.5.0"
resolved-ref: "529306911d320e4b37391baa233842803173b3a1"
url: "https://gitlab.com/omprussia/flutter/flutter-plugins.git"
source: git
version: "0.5.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
@ -160,6 +222,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.2.4"
path_provider_aurora:
dependency: "direct main"
description:
path: "packages/path_provider/path_provider_aurora"
ref: "path_provider_aurora-0.5.0"
resolved-ref: "529306911d320e4b37391baa233842803173b3a1"
url: "https://gitlab.com/omprussia/flutter/flutter-plugins.git"
source: git
version: "0.5.0"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -310,6 +381,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.6.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -342,6 +421,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
xdga_directories:
dependency: transitive
description:
path: "packages/xdga_directories"
ref: "xdga_directories-0.5.0"
resolved-ref: "529306911d320e4b37391baa233842803173b3a1"
url: "https://gitlab.com/omprussia/flutter/flutter-plugins.git"
source: git
version: "0.5.0"
sdks: sdks:
dart: ">=3.2.2 <4.0.0" dart: ">=3.2.2 <4.0.0"
flutter: ">=3.16.0" flutter: ">=3.16.0"

16
pubspec.yaml

@ -1,5 +1,5 @@
name: counters name: counters
description: "A new Flutter project." description: "Values of counters"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
@ -23,7 +23,21 @@ dependencies:
sdk: flutter sdk: flutter
intl: any intl: any
path_provider: ^2.1.3 path_provider: ^2.1.3
path_provider_aurora:
git:
url: https://gitlab.com/omprussia/flutter/flutter-plugins.git
ref: path_provider_aurora-0.5.0
path: packages/path_provider/path_provider_aurora
package_info_plus_aurora:
git:
url: https://gitlab.com/omprussia/flutter/flutter-plugins.git
ref: package_info_plus_aurora-0.5.0
path: packages/package_info_plus/package_info_plus_aurora
path: ^1.8.3 path: ^1.8.3
flutter_slidable: ^3.1.0
datetime_picker_formfield: ^2.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save