Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.0.7+7 #24

Merged
merged 25 commits into from
Apr 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e24e75a
ready for development on 1.0.7+7
abertschi Sep 15, 2022
1d88798
Reduce fontSize & SizedBox height values
katrinleinweber Oct 9, 2022
baf34b2
Merge branch 'master' into develop
abertschi Mar 4, 2023
8718625
build: update versions
abertschi Mar 4, 2023
16f93f5
introduce restart widget to reload app.
abertschi Apr 2, 2023
de6d92f
add external dependencies for storage
abertschi Apr 2, 2023
598b04a
update model to provide import/export
abertschi Apr 2, 2023
65e2d85
add navigation and import/export ui
abertschi Apr 2, 2023
6d53000
permission and build updates
abertschi Apr 2, 2023
8651aa7
Merge pull request #22 from katrinleinweber/compact-plant-show-screen
abertschi Apr 2, 2023
62f1639
plant_show: padding and ui
abertschi Apr 2, 2023
36ab400
store plant pics to dedicated directory
abertschi Apr 2, 2023
df319b1
groups: introduce default group
abertschi Apr 2, 2023
973e79e
update changelog
abertschi Apr 2, 2023
bbf7985
update changelog
abertschi Apr 2, 2023
5a82d85
Update README.md
abertschi Apr 2, 2023
be1a22d
migration code to restore from no group
abertschi Apr 2, 2023
da6c200
Merge branch 'develop' of github.com:abertschi/water-me into develop
abertschi Apr 2, 2023
17b9d09
clean up
abertschi Apr 2, 2023
69600a5
fix issue with backup path
abertschi Apr 2, 2023
8ff5019
clean up
abertschi Apr 2, 2023
a4c784f
add changelog for store
abertschi Apr 2, 2023
6c6ff4b
add qna
abertschi Apr 2, 2023
f6969ca
Update README.md
abertschi Apr 2, 2023
069b558
Update qna.md
abertschi Apr 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}
}