A framework for building convergent cross-platform Nextcloud clients using Flutter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

177 lines
6.1 KiB

part of '../neon_notes.dart';
class NotesNotePage extends StatefulWidget {
const NotesNotePage({
required this.bloc,
required this.notesBloc,
super.key,
});
final NotesNoteBloc bloc;
final NotesBloc notesBloc;
@override
State<NotesNotePage> createState() => _NotesNotePageState();
}
class _NotesNotePageState extends State<NotesNotePage> {
late final _contentController = TextEditingController()..text = widget.bloc.initialContent;
late final _titleController = TextEditingController()..text = widget.bloc.initialTitle;
final _contentFocusNode = FocusNode();
final _titleFocusNode = FocusNode();
bool _showEditor = false;
final _contentStreamController = StreamController<String>();
final _titleStreamController = StreamController<String>();
void _focusEditor() {
_contentFocusNode.requestFocus();
_contentController.selection = TextSelection.collapsed(offset: _contentController.text.length);
}
@override
void initState() {
super.initState();
widget.bloc.errors.listen((final error) {
handleNotesException(context, error);
});
_contentStreamController.stream.debounceTime(const Duration(seconds: 1)).listen(widget.bloc.updateContent);
_titleStreamController.stream.debounceTime(const Duration(seconds: 1)).listen(widget.bloc.updateTitle);
_contentController.addListener(() => _contentStreamController.add(_contentController.text));
_titleController.addListener(() => _titleStreamController.add(_titleController.text));
WidgetsBinding.instance.addPostFrameCallback((final _) async {
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
await Wakelock.enable();
}
if (widget.bloc.options.defaultNoteViewTypeOption.value == DefaultNoteViewType.edit ||
widget.bloc.initialContent.isEmpty) {
setState(() {
_showEditor = true;
});
_focusEditor();
}
});
}
@override
void dispose() {
unawaited(_contentStreamController.close());
unawaited(_titleStreamController.close());
super.dispose();
}
@override
Widget build(final BuildContext context) => BackButtonListener(
onBackButtonPressed: () async {
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
await Wakelock.disable();
}
return false;
},
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
titleSpacing: 0,
title: TextField(
controller: _titleController,
focusNode: _titleFocusNode,
style: const TextStyle(
fontSize: 22,
),
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: UnderlineInputBorder(),
focusedBorder: UnderlineInputBorder(),
),
),
actions: [
IconButton(
onPressed: () {
setState(() {
_showEditor = !_showEditor;
});
if (_showEditor) {
_focusEditor();
} else {
// Prevent the cursor going back to the title field
_contentFocusNode.unfocus();
_titleFocusNode.unfocus();
}
},
tooltip: _showEditor
? AppLocalizations.of(context).noteShowPreview
: AppLocalizations.of(context).noteShowEditor,
icon: Icon(
_showEditor ? Icons.visibility : Icons.edit,
),
),
StreamBuilder(
stream: widget.bloc.category,
builder: (final context, final categorySnapshot) {
final category = categorySnapshot.data ?? '';
return IconButton(
onPressed: () async {
final result = await showDialog<String>(
context: context,
builder: (final context) => NotesSelectCategoryDialog(
bloc: widget.notesBloc,
initialCategory: category,
),
);
if (result != null) {
widget.bloc.updateCategory(result);
}
},
tooltip: AppLocalizations.of(context).noteChangeCategory,
icon: Icon(
MdiIcons.tag,
color: category.isNotEmpty ? NotesCategoryColor.compute(category) : null,
),
);
},
),
],
),
body: GestureDetector(
onTap: () {
setState(() {
_showEditor = true;
});
},
child: Container(
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: _showEditor ? 20 : 10,
),
color: Colors.transparent,
constraints: const BoxConstraints.expand(),
child: _showEditor
? TextField(
controller: _contentController,
focusNode: _contentFocusNode,
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: const InputDecoration(
border: InputBorder.none,
),
)
: MarkdownBody(
data: _contentController.text,
onTapLink: (final text, final href, final title) async {
if (href != null) {
await launchUrlString(
href,
mode: LaunchMode.externalApplication,
);
}
},
),
),
),
),
);
}