Skip to content

Commit

Permalink
PAINTROID-440, PAINTROID-441, PAINTROID-442 Created the landing page …
Browse files Browse the repository at this point in the history
…and implemented the basic functionality (#8)

PAINTROID-440, PAINTROID-441, PAINTROID-442 Created the landing page and implemented the basic functionality
  • Loading branch information
CodeChamp-SS committed Nov 1, 2022
1 parent 5c648d5 commit 5739d86
Show file tree
Hide file tree
Showing 28 changed files with 1,515 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup
run: |
flutter pub get
flutter pub run build_runner build
flutter pub run build_runner build --delete-conflicting-outputs
dart pub global activate protoc_plugin
chmod +x generate_protos.sh
./generate_protos.sh
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ migrate_working_dir/
coverage
*.mocks.dart
*.pb*.dart
*.g.dart

# Web related
lib/generated_plugin_registrant.dart
Expand Down
4 changes: 3 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ analyzer:
missing_enum_constant_in_switch: error
exhaustive_cases: error
exclude:
- lib/**.pb*.dart
- lib/**.pb*.dart
- lib/data/*.g.dart
- test/**
10 changes: 10 additions & 0 deletions assets/svg/ic_edit_circle.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions lib/data/model/project.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:floor/floor.dart';

@entity
class Project {
String name;
String path;
DateTime lastModified;
DateTime creationDate;
String? resolution;
String? format;
int? size;
String? imagePreviewPath;
@PrimaryKey(autoGenerate: true)
final int? id;

Project({
required this.name,
required this.path,
required this.lastModified,
required this.creationDate,
this.resolution,
this.format,
this.size,
this.imagePreviewPath,
this.id,
});
}
21 changes: 21 additions & 0 deletions lib/data/project_dao.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:floor/floor.dart';

import 'model/project.dart';

@dao
abstract class ProjectDAO {
@Insert(onConflict: OnConflictStrategy.replace)
Future<int> insertProject(Project project);

@Insert(onConflict: OnConflictStrategy.replace)
Future<List<int>> insertProjects(List<Project> projects);

@Query('DELETE FROM Project WHERE id = :id')
Future<void> deleteProject(int id);

@delete
Future<void> deleteProjects(List<Project> projects);

@Query('SELECT * FROM Project order by lastModified desc')
Future<List<Project>> getProjects();
}
19 changes: 19 additions & 0 deletions lib/data/project_database.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'dart:async';

import 'package:floor/floor.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/data/model/project.dart';
import 'package:paintroid/data/project_dao.dart';
import 'package:paintroid/data/typeconverters/date_time_converter.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

part 'project_database.g.dart';

@TypeConverters([DateTimeConverter])
@Database(version: 1, entities: [Project])
abstract class ProjectDatabase extends FloorDatabase {
ProjectDAO get projectDAO;

static final provider = FutureProvider((ref) =>
$FloorProjectDatabase.databaseBuilder("project_database.db").build());
}
13 changes: 13 additions & 0 deletions lib/data/typeconverters/date_time_converter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:floor/floor.dart';

class DateTimeConverter extends TypeConverter<DateTime, int> {
@override
DateTime decode(int databaseValue) {
return DateTime.fromMillisecondsSinceEpoch(databaseValue);
}

@override
int encode(DateTime value) {
return value.millisecondsSinceEpoch;
}
}
34 changes: 34 additions & 0 deletions lib/io/src/service/file_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import 'package:oxidized/oxidized.dart';
import 'package:paintroid/core/failure.dart';
import 'package:paintroid/core/loggable_mixin.dart';
import 'package:paintroid/io/io.dart';
import 'package:path_provider/path_provider.dart';

abstract class IFileService {
Future<Result<File, Failure>> save(String filename, Uint8List data);

Future<Result<File, Failure>> saveToApplicationDirectory(
String filename, Uint8List data);

Future<Result<File, Failure>> pick();

Result<File, Failure> getFile(String path);

static final provider = Provider<IFileService>((ref) => FileService());
}

Expand Down Expand Up @@ -51,4 +57,32 @@ class FileService with LoggableMixin implements IFileService {
return Result.err(SaveImageFailure.unidentified);
}
}

Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}

@override
Future<Result<File, Failure>> saveToApplicationDirectory(
String filename, Uint8List data) async {
try {
String saveDirectory = "${await _localPath}/$filename";
final file = await File(saveDirectory).create(recursive: true);
return Result.ok(await file.writeAsBytes(data));
} catch (err, stacktrace) {
logger.severe("Could not save file", err, stacktrace);
return Result.err(SaveImageFailure.unidentified);
}
}

@override
Result<File, Failure> getFile(String path) {
try {
return Result.ok(File(path));
} catch (err, stacktrace) {
logger.severe("Could not load file", err, stacktrace);
return Result.err(LoadImageFailure.unidentified);
}
}
}
15 changes: 15 additions & 0 deletions lib/io/src/service/image_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

Expand All @@ -19,6 +20,8 @@ abstract class IImageService {

Future<Result<Uint8List, Failure>> exportAsPng(ui.Image image);

Result<Uint8List, Failure> getProjectPreview(String? path);

static final provider = Provider<IImageService>((ref) => ImageService());
}

Expand Down Expand Up @@ -61,4 +64,16 @@ class ImageService with LoggableMixin implements IImageService {
return Result.err(SaveImageFailure.unidentified);
}
}

@override
Result<Uint8List, Failure> getProjectPreview(String? path) {
try {
if (path == null) throw "Unable to get the project preview";
final file = File(path);
return Result.ok(file.readAsBytesSync());
} catch (err, stacktrace) {
logger.severe("Could not get the project preview", err, stacktrace);
return Result.err(LoadImageFailure.invalidImage);
}
}
}
57 changes: 57 additions & 0 deletions lib/io/src/ui/delete_project_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';

/// Returns [true] if user chose to delete the project or [null] if user
/// dismiss the dialog by tapping outside
Future<bool?> showDeleteDialog(BuildContext context, String name) =>
showGeneralDialog<bool>(
context: context,
pageBuilder: (_, __, ___) => DeleteProjectDialog(name: name),
barrierDismissible: true,
barrierLabel: "Show delete project dialog box");

class DeleteProjectDialog extends StatefulWidget {
final String name;

const DeleteProjectDialog({Key? key, required this.name}) : super(key: key);

@override
State<DeleteProjectDialog> createState() => _DeleteProjectDialogState();
}

class _DeleteProjectDialogState extends State<DeleteProjectDialog> {
@override
Widget build(BuildContext context) => AlertDialog(
title: Text("Delete ${widget.name}"),
actions: const [
DialogElevatedButton(text: 'Cancel'),
DialogTextButton(text: 'Delete'),
],
content: const Text("Do you really want to delete your project?"),
);
}

class DialogTextButton extends StatelessWidget {
final String text;

const DialogTextButton({Key? key, required this.text}) : super(key: key);

@override
Widget build(BuildContext context) => TextButton(
style:
ButtonStyle(foregroundColor: MaterialStateProperty.all(Colors.red)),
onPressed: () => Navigator.of(context).pop(true),
child: Text(text),
);
}

class DialogElevatedButton extends StatelessWidget {
final String text;

const DialogElevatedButton({Key? key, required this.text}) : super(key: key);

@override
Widget build(BuildContext context) => ElevatedButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(text, style: const TextStyle(color: Colors.white)),
);
}
28 changes: 8 additions & 20 deletions lib/io/src/ui/discard_changes_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:paintroid/io/src/ui/delete_project_dialog.dart';

/// Returns [true] if user chose to discard changes or [null] if user
/// dismissed the dialog by tapping outside
Expand All @@ -19,26 +20,13 @@ class DiscardChangesDialog extends StatefulWidget {
class _DiscardChangesDialogState extends State<DiscardChangesDialog> {
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text("Discard changes"),
actions: [_discardButton, _saveButton],
content: const Text(
"You have not saved your last changes. They will be lost!"),
);
}

TextButton get _discardButton {
return TextButton(
style: TextButton.styleFrom(primary: Colors.red),
onPressed: () => Navigator.of(context).pop(true),
child: const Text("Discard"),
);
}

ElevatedButton get _saveButton {
return ElevatedButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Save", style: TextStyle(color: Colors.white)),
return const AlertDialog(
title: Text("Discard changes"),
actions: [
DialogTextButton(text: 'Discard'),
DialogElevatedButton(text: 'Save'),
],
content: Text("You have not saved your last changes. They will be lost!"),
);
}
}

0 comments on commit 5739d86

Please sign in to comment.