Skip to content

Commit

Permalink
Merge pull request #24 from abertschi/develop
Browse files Browse the repository at this point in the history
1.0.7+7
  • Loading branch information
abertschi committed Apr 2, 2023
2 parents 3910e2c + 069b558 commit ea41e72
Show file tree
Hide file tree
Showing 17 changed files with 583 additions and 149 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.0.7+7
- Add option to import and export data.
- Add default group for future group support feature.
- Camera no longer creates temporary files but stores plants to app directory.
- Padding changes in plant details.
- Drop support for landscape mode.
- This version changes the model data. Add migration code to introduce a single group on import.

## 1.0.6+6
- Fix issue on Android 7 with crash due to notification misconfig

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ _Water Me_ is available on the [F-droid store](https://f-droid.org/packages/ch.a
Alternatively, download snapshots from the [continuous integration action](https://github.com/abertschi/water-me/actions/workflows/build.yml) (requires a Github account, be aware that Github zips the apk on download, so unzip first).


### Changelog
See [./CHANGELOG](./CHANGELOG) for a list of recent features.

### Need Help?
See [troupbleshooting guide](./qna.md) for a list of common questions.

### Build
This is a flutter based Android application. Ensure to have Android-Studio and flutter-sdk installed.
```
Expand Down
3 changes: 2 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion flutter.compileSdkVersion
// flutter.compileSdkVersion
compileSdkVersion 33
ndkVersion flutter.ndkVersion

compileOptions {
Expand Down
9 changes: 9 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
android:name="android.permission.RECORD_AUDIO"
tools:node="remove" />

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"
tools:node="remove" />
Expand All @@ -16,6 +19,7 @@
android:icon="@mipmap/ic_launcher"
android:label="Water Me">
<activity
android:screenOrientation="portrait"
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
Expand All @@ -40,5 +44,10 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />


<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"
android:exported="true">
</receiver>
</application>
</manifest>
62 changes: 55 additions & 7 deletions lib/app_context.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:water_me/services/db.dart';
import 'package:water_me/services/notification.dart';
Expand All @@ -7,18 +9,22 @@ import 'models/app_model.dart';

class AppContext {
late Db db = Db();
late AppModel model;
late AppModel _model;
late CameraDescription? camera;
late NotificationService? notifications;
late ScheduleService schedule;

get model {
return _model;
}

init() async {
print("init app Context");
// XXX: We only need model data after invocation
await initModel();
initCamera();
initNotifications();
initSchedule();

}

initSchedule() {
Expand All @@ -27,19 +33,61 @@ class AppContext {
}

saveModel() async {
db.saveModel(model!);
db.saveModel(_model);
}

Future<File> exportModelToFile() async {
final file = await db.exportFile;
return await db.exportToFile(model, file);
}

exportPath() {
return db.exportDbFilePath;
}

exportBackupPath() {
return db.exportDbBackupPath;
}

// returns backup file or exception
importModelFromFile() async {
final file = await db.exportFile;
final backupFile = await db.exportBackupFile;
await db.exportToFile(model, backupFile);

try {
final jsonData = await file.readAsString();
final appModel = db.importJsonString(jsonData);
_initModel(appModel);
} on Exception catch(_) {
print("=== troubleshooting info: ");
print("failed to import model");
print("import model data: ${await file.readAsString()}");
print("expected format: ${db.exportAsJsonString(new AppModel())}");
rethrow;
}

return backupFile;
}


initNotifications() async {
notifications = NotificationService();
notifications?.init();
}

initModel() async {
model = await db.loadModel();
model.addListener(() {
_initModel(AppModel model) async {
_model = model;
_model.addListener(() {
saveModel();
});
saveModel();
}

initModel() async {
var m = await db.loadModel();
print(m);
_initModel(m);
}

initCamera() async {
Expand All @@ -50,4 +98,4 @@ class AppContext {
print(_);
}
}
}
}
39 changes: 37 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final AppContext c = AppContext();
await c.init();
runApp(MyApp(appContext: c));
runApp(RestartWidget(child: MyApp(appContext: c)));
}

class MyApp extends StatefulWidget {
Expand All @@ -27,7 +27,6 @@ class MyApp extends StatefulWidget {
}

class _MyApp extends State<MyApp> with WidgetsBindingObserver {

@override
Widget build(BuildContext context) {
return MultiProvider(
Expand Down Expand Up @@ -62,5 +61,41 @@ class _MyApp extends State<MyApp> with WidgetsBindingObserver {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}

class RestartWidget extends StatefulWidget {
// taken from:
// https://stackoverflow.com/questions/50115311/how-to-force-a-flutter-application-restart-in-production-mode
// we wrap our app into this to force redraw of app
// when replacing the instance of appCtx.model upon restore from file feature.
// redraw with: RestartWidget.restartApp(context).

RestartWidget({required this.child});

final Widget child;

static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartWidgetState>()!.restartApp();
}

@override
_RestartWidgetState createState() => _RestartWidgetState();
}

class _RestartWidgetState extends State<RestartWidget> {
Key key = UniqueKey();

void restartApp() {
setState(() {
key = UniqueKey();
});
}

@override
Widget build(BuildContext context) {
return KeyedSubtree(
key: key,
child: widget.child,
);
}
}
84 changes: 64 additions & 20 deletions lib/models/app_model.dart
Original file line number Diff line number Diff line change
@@ -1,39 +1,62 @@
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:water_me/models/group_model.dart';
import 'package:water_me/models/plant_model.dart';

class AppModel extends ChangeNotifier {
List<PlantModel> _plants = [];
List<GroupModel> _groups = [];

static emptyModel() {
GroupModel group = GroupModel("My plants");
return group;
}

set plants(List<PlantModel> l) {
_plants = l;
GroupModel get defaultGroup {
if (_groups.isEmpty) {
addGroup(GroupModel("My plants"));
}
return _groups[0];
}

set groups(List<GroupModel> l) {
_groups = l;
notifyListeners();
}

List<PlantModel> get plants => plantsOrderedByWatering;
List<GroupModel> get groups {
return _groups;
}

List<PlantModel> get allPlants {
return _groups.expand((g) => g.plants).toList();
}

List<PlantModel> get plantsOrderedByWatering {
_plants.sort((a, b) {
var p = allPlants;
p.sort((a, b) {
return a.daysUntilNextWatering().compareTo(b.daysUntilNextWatering());
});
return _plants;
return p;
}

int plantsToWater() {
return _plants.where((p) => p.isWateringDue()).toList().length;
return allPlants.where((p) => p.isWateringDue()).toList().length;
}

bool isWateringDue() {
for (var p in _plants) {
for (var p in allPlants) {
if (p.isWateringDue()) {
return true;
}
}
return false;
}

void addPlant(PlantModel p) {
_plants.add(p);
void addGroup(GroupModel g) {
_groups.add(g);
notifyListeners();
p.addListener(notifyParent);
g.addListener(notifyParent);
}

void notifyParent() {
Expand All @@ -43,25 +66,46 @@ class AppModel extends ChangeNotifier {
notifyListeners();
}

void removePlant(PlantModel p) {
_plants.remove(p);
notifyListeners();
p.removeListener(notifyParent);
// workaround until group feature implemented
List<PlantModel> get plants => allPlants;

void addPlant(PlantModel m) {
defaultGroup.addPlant(m);
}

removePlant(PlantModel m) {
defaultGroup.removePlant(m);
}

static AppModel fromJson(Map<String, dynamic> json) {
AppModel model = AppModel();
var plants = List<Map<String, dynamic>>.from(json['plants']);
for (var plantMap in plants) {
model.addPlant(PlantModel.fromJson(plantMap));
var version = json['version'] ?? '';

// XXX: in version 1 we had no groups yet
if (version == '1') {
var group = GroupModel("My Plants");
var plants = List<Map<String, dynamic>>.from(json['plants']);
for (var plantMap in plants) {
group.addPlant(PlantModel.fromJson(plantMap));
}
model.addGroup(group);
} else {
var groups = List<Map<String, dynamic>>.from(json['groups']);
for (var groupMap in groups) {
model.addGroup(GroupModel.fromJson(groupMap));
}
}
return model;
}

/*
* version 1: init
* version 2: group support
*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['plants'] = plants.map((e) => e.toJson()).toList();
data['version'] = '1';
data['groups'] = groups.map((e) => e.toJson()).toList();
data['version'] = '2';
return data;
}
}

0 comments on commit ea41e72

Please sign in to comment.