Skip to content

Commit 0800041

Browse files
committed
refactor: apply SOLID principles and introduce domain layer
Separate concerns by introducing a Pictogram value object and Board entity, extracting business logic from the screen into dedicated services (BoardShareService, RecommendationService), and reducing the screen to presentation-only responsibilities. - Add models/pictogram.dart (value object with parsing and identity) - Add models/board.dart (entity with business rules and ChangeNotifier) - Extract services/board_share_service.dart from screen - Extract services/recommendation_service.dart from utils - Simplify pictogram_utils.dart to delegate to models and services - Add unit tests for Pictogram, Board and RecommendationService - Update README with new project structure and features
1 parent ac046b7 commit 0800041

10 files changed

Lines changed: 557 additions & 207 deletions

File tree

README.md

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ Pictograms are universally easy to identify, making communication simple and int
3636

3737
- **Pictogram keyboard** with categorized icons (descriptive, people, prepositions, determiners, nouns, verbs)
3838
- **Visual board** to compose messages by selecting pictograms
39+
- **Smart recommendations** — contextual next-word suggestions based on communication patterns
3940
- **Share** the board as a 1080×1080 image with text description
40-
- **Installable PWA**add to home screen on Android and iOS
41+
- **Offline-first PWA**works without internet after the first visit, installable on any device
4142
- **Multi-language support**: English, Spanish, Catalan, Basque, French, Galician, Portuguese, Valencian
4243
- **Accessibility-focused** design with semantic labels for screen readers
4344

@@ -70,23 +71,29 @@ flutter test
7071

7172
```
7273
lib/
73-
├── main.dart # App entry point
74+
├── main.dart # App entry point
75+
├── models/
76+
│ ├── pictogram.dart # Pictogram value object
77+
│ └── board.dart # Board entity with business rules
7478
├── data/
75-
│ └── pictogram_data.dart # Pictogram categories and icon data
79+
│ ├── pictogram_data.dart # Pictogram categories and icon data
80+
│ └── pictogram_recommendations.dart # Next-word suggestion graph
7681
├── screens/
77-
│ └── pictotap_screen.dart # Main screen with board and state
82+
│ └── pictotap_screen.dart # Main screen (presentation only)
7883
├── services/
79-
│ ├── image_saver.dart # Platform export selector
80-
│ ├── image_saver_native.dart # Native image sharing (Android/iOS)
81-
│ ├── image_saver_web.dart # Web image download / Web Share API
82-
│ └── image_saver_stub.dart # Stub for unsupported platforms
84+
│ ├── image_saver.dart # Platform export selector
85+
│ ├── image_saver_native.dart # Native image sharing (Android/iOS)
86+
│ ├── image_saver_web.dart # Web image download / Web Share API
87+
│ ├── image_saver_stub.dart # Stub for unsupported platforms
88+
│ ├── board_share_service.dart # Board capture and share pipeline
89+
│ └── recommendation_service.dart # Contextual pictogram suggestions
8390
├── utils/
84-
│ └── pictogram_utils.dart # Icon utility functions
91+
│ └── pictogram_utils.dart # Convenience wrappers
8592
├── widgets/
86-
│ ├── board_empty_hint.dart # Empty board hint animation
87-
│ ├── pictogram_icon.dart # Pictogram icon widget
88-
│ └── pictogram_keyboard.dart # Keyboard widget with categories
89-
└── l10n/ # Localization (ARB files)
93+
│ ├── board_empty_hint.dart # Empty board hint animation
94+
│ ├── pictogram_icon.dart # Pictogram icon widget
95+
│ └── pictogram_keyboard.dart # Keyboard widget with categories
96+
└── l10n/ # Localization (ARB files)
9097
```
9198

9299
## Team

lib/models/board.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:pictotap/data/pictogram_data.dart';
3+
import 'package:pictotap/models/pictogram.dart';
4+
5+
class Board extends ChangeNotifier {
6+
final List<Pictogram> _icons = [];
7+
final int maxIcons;
8+
9+
Board({this.maxIcons = defaultBoardMaxIcons});
10+
11+
List<Pictogram> get icons => List.unmodifiable(_icons);
12+
int get length => _icons.length;
13+
bool get isEmpty => _icons.isEmpty;
14+
bool get isFull => _icons.length >= maxIcons;
15+
16+
List<String> get iconIds => _icons.map((p) => p.id).toList();
17+
18+
String get shareText => _icons.map((p) => p.displayName).join(' ');
19+
20+
bool add(Pictogram pictogram) {
21+
if (isFull) return true;
22+
_icons.add(pictogram);
23+
notifyListeners();
24+
return _icons.length >= maxIcons;
25+
}
26+
27+
bool addSpace() => add(Pictogram.space);
28+
29+
Pictogram? removeLast() {
30+
if (_icons.isEmpty) return null;
31+
final removed = _icons.removeLast();
32+
notifyListeners();
33+
return removed;
34+
}
35+
36+
void clear() {
37+
if (_icons.isEmpty) return;
38+
_icons.clear();
39+
notifyListeners();
40+
}
41+
}

lib/models/pictogram.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'package:pictotap/data/pictogram_data.dart';
2+
3+
const List<String> _knownCategories = [
4+
'descriptive',
5+
'people',
6+
'prepositions',
7+
'some',
8+
'substantive',
9+
'verbs',
10+
];
11+
12+
class Pictogram {
13+
final String category;
14+
final String name;
15+
16+
const Pictogram._({required this.category, required this.name});
17+
18+
static const Pictogram space = Pictogram._(category: '', name: spaceIcon);
19+
20+
factory Pictogram.fromId(String id) {
21+
if (id == spaceIcon) return space;
22+
final sep = id.indexOf(':');
23+
if (sep <= 0) {
24+
return Pictogram._(category: '', name: id);
25+
}
26+
return Pictogram._(
27+
category: id.substring(0, sep),
28+
name: id.substring(sep + 1),
29+
);
30+
}
31+
32+
String get id => isSpace ? spaceIcon : '$category:$name';
33+
34+
bool get isSpace => identical(this, space) || name == spaceIcon;
35+
36+
bool get isAsset =>
37+
category.isNotEmpty && _knownCategories.contains(category);
38+
39+
String get displayName => isSpace ? ' ' : name;
40+
41+
String? get assetPath =>
42+
isAsset ? 'assets/keyboard/$category/$name.png' : null;
43+
44+
@override
45+
bool operator ==(Object other) =>
46+
identical(this, other) || (other is Pictogram && other.id == id);
47+
48+
@override
49+
int get hashCode => id.hashCode;
50+
51+
@override
52+
String toString() => 'Pictogram($id)';
53+
}

0 commit comments

Comments
 (0)