Skip to content

Commit

Permalink
Merge pull request #3 from dominik-masson/develop
Browse files Browse the repository at this point in the history
NEW:

images for entries
a backup of the database is automatically created as soon as the app is closed
reset an entry and/or mark an entry as sent to the provider
archive meters
compare contacts,


Update UI:

new entrycard design
add dynamic color
material 3 bottom app bar
new database settings screen
new object screens for rooms and contracts
new details page for contracts
meter units are now displayed more nicely


also a few bugs have been fixed
  • Loading branch information
dominik-masson authored Dec 16, 2023
2 parents 844c1b8 + 5990bc2 commit e33d14f
Show file tree
Hide file tree
Showing 160 changed files with 15,084 additions and 4,550 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Over time, many more features are to be added and eventually the app will be rel
### Current functions

- create tags and assign them to meters
- local notifications
- local notifications (currently android only)
- keep display awake
- part meter in rooms
- dark mode
Expand Down
26 changes: 23 additions & 3 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,28 @@
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

<application
android:label="OpenMeter"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
>
<!-- Local Notifications -->
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"/>
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" />

<activity
android:name=".MainActivity"
Expand All @@ -20,7 +37,10 @@
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true">
android:turnScreenOn="true"
>


<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
Expand All @@ -40,6 +60,6 @@
android:name="flutterEmbedding"
android:value="2"/>

android:requestLegacyExternalStorage="true"

</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.example.openmeter

import android.content.Context
import android.content.pm.PackageManager
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.lang.Exception

class MainActivity : FlutterActivity() {
private val channel = "com.example.openmeter/main"
private val enableTorch = "enableTorch"
private val disableTorch = "disableTorch"
private val torchAvailable = "torchAvailable"
private val getStateTorch = "getStateTorch"
private val isTorchOn = "isTorchOn"

private var cameraManager: CameraManager? = null
private var cameraID: String? = null
Expand Down Expand Up @@ -43,7 +49,6 @@ class MainActivity : FlutterActivity() {
}
}


private fun isTorchAvailable(result: MethodChannel.Result) {
result.success(context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
}
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
Expand All @@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
Binary file modified assets/fonts/custom_icons.ttf
Binary file not shown.
Binary file modified assets/icons/database_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/empty_archiv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/icons/meter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/icons/no_data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/icons/no_data2.png
Binary file not shown.
Binary file modified assets/icons/no_meter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/icons/no_meter2.png
Binary file not shown.
Binary file modified assets/icons/notifications_disable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/icons/notifications_enable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/icons/tag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,13 @@
<true/>
<key>UIStatusBarHidden</key>
<false/>
<key>NSPhotoLibraryUsageDescription</key>
<string>Select an image on which a meter reading can be seen. </string>
<key>NSCameraUsageDescription </key>
<string>Create and save images of a meter reading.</string>
<key>NSMicrophoneUsageDescription</key>
<false/>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save self-taken pictures of your meter reading in the gallery.</string>
</dict>
</plist>
81 changes: 71 additions & 10 deletions lib/core/database/daos/contract_dao.dart
Original file line number Diff line number Diff line change
@@ -1,49 +1,110 @@
import 'package:drift/drift.dart';

import '../../model/compare_costs.dart';
import '../../model/contract_dto.dart';
import '../../model/provider_dto.dart';
import '../local_database.dart';
import '../tables/contract.dart';

part 'contract_dao.g.dart';

@DriftAccessor(tables: [Contract, Provider])
class ContractDao extends DatabaseAccessor<LocalDatabase> with _$ContractDaoMixin{
class ContractDao extends DatabaseAccessor<LocalDatabase>
with _$ContractDaoMixin {
final LocalDatabase db;

ContractDao(this.db) : super(db);

Future<int> createProvider(ProviderCompanion provider) async{
Future<int> createProvider(ProviderCompanion provider) async {
return await db.into(db.provider).insert(provider);
}

Future<int> createContract(ContractCompanion contract) async {
return await db.into(db.contract).insert(contract);
}

Stream<List<ContractData>> watchALlContracts() {
return db.select(db.contract).watch();
Stream<List<ContractData>> watchAllContracts(bool isArchived) {
return (select(db.contract)
..where((tbl) => tbl.isArchived.equals(isArchived)))
.watch();
}

Future<List<ContractDto>> getAllContractsDto() async {
List<ContractDto> result = [];
List<ContractData> contracts = await select(db.contract).get();

for (ContractData contract in contracts) {
ContractDto contractDto = ContractDto.fromData(contract, null);

if (contract.provider != null) {
ProviderData provider = await selectProvider(contract.provider!);

contractDto.provider = ProviderDto.fromData(provider);
}

final compareCosts = await db.costCompareDao.getCompareCost(contract.id);

if (compareCosts != null) {
contractDto.compareCosts = CompareCosts.fromData(compareCosts);
}

result.add(contractDto);
}

return result;
}

Future<ProviderData> selectProvider(int id) async {
return await (db.select(db.provider)..where((tbl) => tbl.uid.equals(id))).getSingle();
return await (db.select(db.provider)..where((tbl) => tbl.id.equals(id)))
.getSingle();
}

Future<int> deleteContract(int id) async {
return await (db.delete(db.contract)..where((tbl) => tbl.uid.equals(id))).go();
return await (db.delete(db.contract)..where((tbl) => tbl.id.equals(id)))
.go();
}

Future<int> deleteProvider(int id) async {
return await (db.delete(db.provider)..where((tbl) => tbl.uid.equals(id))).go();
return await (db.delete(db.provider)..where((tbl) => tbl.id.equals(id)))
.go();
}

Future<ContractData> getContractByTyp(String meterTyp) async {
return await (db.select(db.contract)..where((tbl) => tbl.meterTyp.equals(meterTyp))).getSingle();
return await (db.select(db.contract)
..where((tbl) => tbl.meterTyp.equals(meterTyp)))
.getSingle();
}

Future<bool> updateContract(ContractData contractData) async{
Future<bool> updateContract(ContractData contractData) async {
return await update(db.contract).replace(contractData);
}

Future<bool> updateProvider(ProviderData providerData) async {
return await update(db.provider).replace(providerData);
}
}

Future<int?> getTableLength() async {
var count = db.contract.id.count();

return await (db.selectOnly(db.contract)..addColumns([count]))
.map((row) => row.read(count))
.getSingleOrNull();
}

Future<int> linkProviderToContract(
{required int contractId, required int providerId}) async {
return await (update(db.contract)
..where((tbl) => tbl.id.equals(contractId)))
.write(
ContractCompanion(
provider: Value(providerId),
),
);
}

Future updateIsArchived(
{required int contractId, required bool isArchived}) async {
return await (update(contract)..where((tbl) => tbl.id.equals(contractId)))
.write(ContractCompanion(isArchived: Value(isArchived)));
}
}
33 changes: 33 additions & 0 deletions lib/core/database/daos/cost_compare_dao.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:drift/drift.dart';

import '../local_database.dart';
import '../tables/cost_compare.dart';

part 'cost_compare_dao.g.dart';

@DriftAccessor(tables: [CostCompare])
class CostCompareDao extends DatabaseAccessor<LocalDatabase>
with _$CostCompareDaoMixin {
final LocalDatabase db;

CostCompareDao(this.db) : super(db);

Future<CostCompareData?> getCompareCost(int parentId) async {
return await (select(db.costCompare)
..where((tbl) => tbl.parentId.equals(parentId)))
.getSingleOrNull();
}

Future createCompareCost(CostCompareCompanion costs) async {
return await into(db.costCompare).insert(costs);
}

Future deleteCompareCost(int id) async {
return await (delete(db.costCompare)..where((tbl) => tbl.id.equals(id)))
.go();
}

Future updateCompareCost(CostCompareData cost) async {
return await update(db.costCompare).replace(cost);
}
}
10 changes: 10 additions & 0 deletions lib/core/database/daos/cost_compare_dao.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 38 additions & 15 deletions lib/core/database/daos/entry_dao.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,57 @@ class EntryDao extends DatabaseAccessor<LocalDatabase> with _$EntryDaoMixin {
.go();
}

Future<bool> updateEntry(EntriesCompanion newEntry) async {
Future<bool> replaceEntry(EntriesCompanion newEntry) async {
return await update(db.entries).replace(newEntry);
}

Future<int> updateEntry(int id, EntriesCompanion entry) async {
return await (update(db.entries)..where((tbl) => tbl.id.equals(id)))
.write(entry);
}

Future<List<Entrie>> getLastEntry(int meterId) async {
return await (db.select(db.entries)
..where((tbl) => tbl.meter.equals(meterId))
..orderBy([(tbl) => OrderingTerm(expression: tbl.count,mode: OrderingMode.desc)]))
.get();
..orderBy([
(tbl) =>
OrderingTerm(expression: tbl.count, mode: OrderingMode.desc)
]))
.get();
}

Stream<List<Entrie>> watchAllEntries(int meterId) {
return (select(db.entries)
..where((tbl) => tbl.meter.equals(meterId))
..orderBy([(tbl) => OrderingTerm.asc(tbl.date)]))
..where((tbl) => tbl.meter.equals(meterId))
..orderBy([(tbl) => OrderingTerm.asc(tbl.date)]))
.watch();
}

Stream<List<Entrie>> getNewestEntry(int meterId) {
Future<List<Entrie>> getAllEntries(int meterId) async {
return (select(db.entries)
..where((tbl) => tbl.meter.equals(meterId))
..orderBy([
((tbl) => OrderingTerm(
expression: tbl.date,
mode: OrderingMode.desc,
))
])
..limit(1))
.watch();
..where((tbl) => tbl.meter.equals(meterId))
..orderBy([(tbl) => OrderingTerm.asc(tbl.date)]))
.get();
}

Stream<Entrie> getNewestEntry(int meterId) {
return (select(db.entries)
..where((tbl) => tbl.meter.equals(meterId))
..orderBy([
((tbl) => OrderingTerm(
expression: tbl.date,
mode: OrderingMode.desc,
))
])
..limit(1))
.watchSingle();
}

Future<int?> getTableLength() async {
var count = db.entries.id.count();

return await (db.selectOnly(db.entries)..addColumns([count]))
.map((row) => row.read(count))
.getSingleOrNull();
}
}
Loading

0 comments on commit e33d14f

Please sign in to comment.