App for Remembering Stuff
Design decisions that must be made:
- Should empty Groups be shown or hidden in the list for remembering?
- Advantage: no extra UI needed for adding to hidden groups
- Disadvantage: may be ugly for nested groups
- Workaround? allow groups to also be "resolved"
General
Create single entries
- Simple text, one-line, nothing fancy
- Have a state: open ( ), unanswered (?), answered (/)
Create groupings (all work the same, just icons? Or just use one generic grouping type, like folder?):
- People
- Groups
- Etc.
Groupings may be recursive (how many layers?)
Views
Overview
- Div
- div: (dynamic) of recently answered entries
- div: list of groups and entries
- By default only shows open, but may also show unanswered
Simplified idea of the main area, parantheses are buttons:
(+ Create new)
(v) Person 1 (+)
- Item 1 ( )
- Item 2 ( )
- (v) Grouping (+)
- Subitem 1 ( )
(>) Person 2 (+)
At some point the nesting will be too deep to display on the main window. At this point I would do something like Google? and Reddit, where you actually open a new page starting with the selected entry.
History
Shows everything, and allows filtering by date or state
Settings
- How long to show answered entries?
- Clear old entries (and groupings?)
- idk.
WIP
import 'package:flutter/material.dart';
void main() => runApp(const EntriesApp());
const tempData = <LabeledElement>[
Group(label: "Hello world"),
Entry(label: "Testing"),
Entry(label: "Open", state: Resolution.open),
Entry(label: "Unresolved", state: Resolution.unresolved),
Entry(label: "Resolved", state: Resolution.resolved),
Group(label: "Group with resolved", entries: <LabeledElement>[
Entry(label: "Resolved in group", state: Resolution.resolved),
]),
Group(label: "And a real group!", entries: <LabeledElement>[
Entry(label: "Child 1"),
Group(label: "A SUB GROUP! RECURSION!", entries: [
Entry(label: "And it needed some content..."),
]),
Entry(label: "Child 2"),
]),
];
List<LabeledElement> subQuery({required List<LabeledElement> entries, required List<Resolution> states, bool includeEmpty = true}) {
final newList = <LabeledElement>[];
for (LabeledElement e in entries) {
if (e is Entry && states.contains(e.state)) newList.add(e);
if (e is Group) {
final groupStuff = subQuery(entries: e.entries, states: states, includeEmpty: includeEmpty);
if (groupStuff.isNotEmpty || includeEmpty) newList.add(Group(id: e.id, label: e.label, entries: groupStuff));
}
}
return newList;
}
// todo use DB
List<LabeledElement> query({required List<Resolution> states, bool includeEmpty = true}) {
return subQuery(entries: tempData, states: states, includeEmpty: includeEmpty);
}
class EntriesApp extends StatelessWidget {
const EntriesApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: EntriesHome());
}
}
class EntriesHome extends StatelessWidget {
const EntriesHome();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Demo Entries"),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...query(states: [Resolution.resolved], includeEmpty: false).map((e) => EntryWidget(entry: e)),
const SizedBox(height: 100),
...query(states: [Resolution.open, Resolution.unresolved]).map((e) => EntryWidget(entry: e)),
]
),
);
}
}
class EntryWidget extends StatelessWidget {
const EntryWidget({required this.entry, this.states});
final LabeledElement entry;
final List<Resolution>? states;
@override
Widget build(BuildContext context) {
if (entry is Group) return GroupWidget(group: entry as Group);
return Row(
children: [
Text(entry.label),
(entry as Entry).state.icon,
],
);
}
}
class GroupWidget extends StatelessWidget {
const GroupWidget({required this.group});
final Group group;
@override
Widget build(BuildContext context) {
return ExpansionTile(
title: Text(group.label),
controlAffinity: ListTileControlAffinity.leading,
children: group.entries.map((e) => EntryWidget(entry: e)).toList(),
);
}
}
class Group extends LabeledElement {
const Group({super.id, required super.label, this.entries = const []});
final List<LabeledElement> entries;
}
class Entry extends LabeledElement {
const Entry({super.id, required super.label, this.state = Resolution.open});
final Resolution state;
}
abstract interface class LabeledElement {
const LabeledElement({this.id, required this.label});
// Todo: use
final String? id;
final String label;
}
enum Resolution {
open,
resolved,
unresolved,
}
extension ResolutionIcon on Resolution {
Widget get icon {
final IconData data;
switch (this) {
case Resolution.open: data = Icons.circle;
case Resolution.resolved: data = Icons.check;
case Resolution.unresolved: data = Icons.close;
}
return Icon(data);
}
}
// Entries are just strings (but need a class for persistance)
// Groups are a string, with a list of strings
No comments to display
No comments to display