Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions assets/displays.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would be actually needing JSON storage for this ? The number of displays that we have to interact with are very limited and we already have an abstract class Epd, which is extended to model a display. Those classes could simply be modified to add whatever more parameters you want to show about the displays.

"displays": [
{
"id": "epaper-3.7-bwr",
"model": "GDEY037Z03",
"name": "E-Paper 3.7\"",
"size": 3.7,
"resolution": [416, 240],
"colors": ["black", "white", "red"],
"driver": "UC8253",
"imagePath": "assets/images/displays/epaper_3.7_bwr.png",
"epdClass": "Gdey037z03"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, how is this approach scalable, when we are anyways creating a new class for any new display that we want to add ?

},
{
"id": "epaper-3.7-bw",
"model": "GDEY037T03",
"name": "E-Paper 3.7\"",
"size": 3.7,
"resolution": [416, 240],
"colors": ["black", "white"],
"driver": "UC8253",
"imagePath": "assets/images/displays/epaper_3.7_bw.PNG",
"epdClass": "Gdey037z03BW"
}
]
}
Binary file added assets/images/displays/epaper_3.7_bw.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/images/displays/epaper_3.7_bwr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:magic_epaper_app/provider/image_loader.dart';
import 'package:magic_epaper_app/provider/display_provider.dart';
import 'package:provider/provider.dart';

import 'package:magic_epaper_app/view/home_screen.dart';
import 'package:magic_epaper_app/view/display_selection_screen.dart';

void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => ImageLoader()),
ChangeNotifierProvider(create: (context) => DisplayProvider()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DisplayProvider() is something which we are using to manage and select displays only. There's no need to register it as a ChangeNotifierProvider() in main() for the whole app.

], child: const MyApp()));
}

Expand All @@ -21,7 +23,7 @@ class MyApp extends StatelessWidget {
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: const SelectDisplay(),
home: const DisplaySelectionScreen(),
);
}
}
65 changes: 65 additions & 0 deletions lib/model/display_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:magic_epaper_app/util/epd/edp.dart';

class DisplayModel {
final String id;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having so many parameters for each display (id, name, ModelName) doesn't make sense to me. This just increases complexity.

final String name;
final String ModelName; // For future use
final double size; // Inches
final int width, height; // Pixels
final List<Color> colors;
final String driver;
final String imagePath;
final Epd epd;

// Constructor
DisplayModel({
required this.id,
required this.name,
required this.size,
required this.width,
required this.height,
required this.colors,
required this.ModelName,
required this.driver,
required this.imagePath,
required this.epd,
});

// Computed properties
bool get isColor => colors.length > 2; // More than black and white
bool get isHd => width >= 1280 && height >= 720;
double get ppi => sqrt(pow(width, 2) + pow(height, 2)) / size;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use this anywhere ?


// Format aspect ratio
String get aspectRatio {
int gcd = _findGCD(width, height);
return '${width ~/ gcd}:${height ~/ gcd}';
}

// Helper method to find GCD for aspect ratio calculation
int _findGCD(int a, int b) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a separate function for GCD? Core dart libraries can be used for this purpose.

while (b != 0) {
int t = b;
b = a % b;
a = t;
}
return a;
}

// Get color names as a formatted string
String get colorNames {
final List<String> colorNames = [];

if (colors.contains(Colors.black)) colorNames.add('Black');
if (colors.contains(Colors.white)) colorNames.add('White');
if (colors.contains(Colors.red)) colorNames.add('Red');
if (colors.contains(Colors.yellow)) colorNames.add('Yellow');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have yellow anywhere in the displays.


return colorNames.join(', ');
}

// Format resolution as a string
String get resolution => '$width × $height';
}
145 changes: 145 additions & 0 deletions lib/provider/display_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:magic_epaper_app/model/display_model.dart';
import 'package:magic_epaper_app/util/epd/gdey037z03.dart';
import 'package:magic_epaper_app/util/epd/gdey037z03bw.dart';

class DisplayProvider extends ChangeNotifier {
// Filter options
String _activeFilter = 'All Displays';
int _selectedDisplayIndex = -1; // -1 means no selection

// Getters
String get activeFilter => _activeFilter;
int get selectedDisplayIndex => _selectedDisplayIndex;
bool get hasSelection => _selectedDisplayIndex != -1;
DisplayModel? get selectedDisplay =>
hasSelection ? filteredDisplays[_selectedDisplayIndex] : null;

// All filter options
final List<String> filterOptions = [
'All Displays',
'Color',
'Black & White',
'HD',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am assuming you are aware of the hardware that would interact with this app, right ?

];

// List of all available displays
List<DisplayModel> allDisplays = [];

// Constructor - Initialize by loading from JSON
DisplayProvider() {
loadDisplaysFromJson();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asynchronous method inside constructor ? Maybe handle this in initState() of the widget that uses this Provider.

}

// Load displays from JSON file
Future<void> loadDisplaysFromJson() async {
try {
// Load the JSON file from the assets
final String jsonString =
await rootBundle.loadString('assets/displays.json');
final Map<String, dynamic> jsonData = json.decode(jsonString);

// Clear the existing displays
allDisplays = [];

// Parse the JSON data
for (var displayData in jsonData['displays']) {
// Convert colors from strings to Color objects
List<Color> colors = [];
for (var colorName in displayData['colors']) {
switch (colorName) {
case 'black':
colors.add(Colors.black);
break;
case 'white':
colors.add(Colors.white);
break;
case 'red':
colors.add(Colors.red);
break;
case 'yellow':
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for here.

colors.add(Colors.yellow);
break;
}
}

// Determine which EPD to use based on the class name
var epd = _getEpdForClassName(displayData['epdClass']);

// Create a DisplayModel from the JSON data
final display = DisplayModel(
id: displayData['id'],
name: displayData['name'],
size: displayData['size'],
ModelName: displayData['model'],
width: displayData['resolution'][0],
height: displayData['resolution'][1],
colors: colors,
driver: displayData['driver'],
imagePath: displayData['imagePath'],
epd: epd,
);

allDisplays.add(display);
}

// Notify listeners that the data has changed
notifyListeners();
} catch (e) {
print('Error loading displays from JSON: $e');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid having print() in your code. Use a logging library instead.

}
}

// Helper method to get the appropriate EPD based on class name
dynamic _getEpdForClassName(String className) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another function which just increases complexity, if you're aiming for a scalable approach.

switch (className) {
case 'Gdey037z03':
return Gdey037z03();
case 'Gdey037z03BW':
return Gdey037z03BW();
default:
return Gdey037z03(); // Default fallback
}
}

// Get filtered displays based on the active filter
List<DisplayModel> get filteredDisplays {
switch (_activeFilter) {
case 'HD':
return allDisplays.where((display) => display.isHd).toList();
case 'Color':
return allDisplays.where((display) => display.isColor).toList();
case 'Black & White':
return allDisplays.where((display) => !display.isColor).toList();
case 'All Displays':
default:
return allDisplays;
}
}

// Set the active filter
void setFilter(String filter) {
if (_activeFilter != filter) {
_activeFilter = filter;
// Reset selection when filter changes
_selectedDisplayIndex = -1;
notifyListeners();
}
}

// Select a display
void selectDisplay(int index) {
if (index >= 0 && index < filteredDisplays.length) {
_selectedDisplayIndex = index;
notifyListeners();
}
}

// Clear selection
void clearSelection() {
_selectedDisplayIndex = -1;
notifyListeners();
}
}
Loading
Loading