parent
4ff25f24ee
commit
5d3e600645
19 changed files with 875 additions and 55 deletions
@ -0,0 +1,51 @@ |
||||
import 'package:flutter/material.dart'; |
||||
|
||||
class HomeScreenExampleItem extends StatelessWidget { |
||||
const HomeScreenExampleItem({ |
||||
required this.title, |
||||
required this.icon, |
||||
required this.text, |
||||
required this.onPressed, |
||||
super.key, |
||||
}); |
||||
final String title; |
||||
final Widget icon; |
||||
final String text; |
||||
final VoidCallback onPressed; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Column( |
||||
children: [ |
||||
SizedBox( |
||||
height: 200, |
||||
width: double.infinity, |
||||
child: GestureDetector( |
||||
onTap: onPressed, |
||||
child: Card( |
||||
child: Column( |
||||
children: [ |
||||
const SizedBox(height: 2), |
||||
Text( |
||||
title, |
||||
style: Theme.of(context).textTheme.titleLarge, |
||||
), |
||||
const SizedBox(height: 8), |
||||
icon, |
||||
const SizedBox(height: 8), |
||||
Padding( |
||||
padding: const EdgeInsets.all(8), |
||||
child: Text( |
||||
text, |
||||
style: Theme.of(context).textTheme.bodyMedium, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,92 @@ |
||||
import 'package:flutter/material.dart'; |
||||
|
||||
import 'package:flutter_quill/flutter_quill.dart'; |
||||
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
||||
import 'package:share_plus/share_plus.dart' show Share; |
||||
|
||||
class QuillScreen extends StatefulWidget { |
||||
const QuillScreen({ |
||||
required this.document, |
||||
super.key, |
||||
}); |
||||
|
||||
final Document document; |
||||
|
||||
@override |
||||
State<QuillScreen> createState() => _QuillScreenState(); |
||||
} |
||||
|
||||
class _QuillScreenState extends State<QuillScreen> { |
||||
final _controller = QuillController.basic(); |
||||
final _isReadOnly = false; |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
_controller.document = widget.document; |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: const Text('Flutter Quill'), |
||||
actions: [ |
||||
IconButton( |
||||
onPressed: () { |
||||
final plainText = _controller.document.toPlainText( |
||||
FlutterQuillEmbeds.defaultEditorBuilders(), |
||||
); |
||||
if (plainText.trim().isEmpty) { |
||||
ScaffoldMessenger.of(context).showSnackBar( |
||||
const SnackBar( |
||||
content: Text( |
||||
"We can't share empty document, please enter some text first", |
||||
), |
||||
), |
||||
); |
||||
return; |
||||
} |
||||
Share.share(plainText); |
||||
}, |
||||
icon: const Icon(Icons.share), |
||||
), |
||||
], |
||||
), |
||||
body: QuillProvider( |
||||
configurations: QuillConfigurations( |
||||
controller: _controller, |
||||
sharedConfigurations: QuillSharedConfigurations( |
||||
animationConfigurations: QuillAnimationConfigurations.disableAll(), |
||||
extraConfigurations: const { |
||||
QuillSharedExtensionsConfigurations.key: |
||||
QuillSharedExtensionsConfigurations( |
||||
assetsPrefix: 'assets', |
||||
), |
||||
}, |
||||
), |
||||
), |
||||
child: Column( |
||||
children: [ |
||||
QuillToolbar( |
||||
configurations: QuillToolbarConfigurations( |
||||
embedButtons: FlutterQuillEmbeds.toolbarButtons(), |
||||
), |
||||
), |
||||
Expanded( |
||||
child: QuillEditor.basic( |
||||
configurations: QuillEditorConfigurations( |
||||
scrollable: true, |
||||
readOnly: _isReadOnly, |
||||
placeholder: 'Start writting your notes...', |
||||
padding: const EdgeInsets.all(16), |
||||
embedBuilders: FlutterQuillEmbeds.defaultEditorBuilders(), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -1,8 +1,26 @@ |
||||
import 'package:bloc/bloc.dart'; |
||||
import 'package:equatable/equatable.dart'; |
||||
import 'package:flutter/material.dart' show ThemeMode; |
||||
import 'package:freezed_annotation/freezed_annotation.dart'; |
||||
import 'package:hydrated_bloc/hydrated_bloc.dart' show HydratedMixin; |
||||
|
||||
part 'settings_state.dart'; |
||||
part 'settings_cubit.freezed.dart'; |
||||
part 'settings_cubit.g.dart'; |
||||
|
||||
class SettingsCubit extends Cubit<SettingsState> { |
||||
SettingsCubit() : super(SettingsInitial()); |
||||
class SettingsCubit extends Cubit<SettingsState> with HydratedMixin { |
||||
SettingsCubit() : super(const SettingsState()); |
||||
|
||||
void updateSettings(SettingsState newSettingsState) { |
||||
emit(newSettingsState); |
||||
} |
||||
|
||||
@override |
||||
SettingsState? fromJson(Map<String, dynamic> json) { |
||||
return SettingsState.fromJson(json); |
||||
} |
||||
|
||||
@override |
||||
Map<String, dynamic>? toJson(SettingsState state) { |
||||
return state.toJson(); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,175 @@ |
||||
// coverage:ignore-file |
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
// ignore_for_file: type=lint |
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark |
||||
|
||||
part of 'settings_cubit.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// FreezedGenerator |
||||
// ************************************************************************** |
||||
|
||||
T _$identity<T>(T value) => value; |
||||
|
||||
final _privateConstructorUsedError = UnsupportedError( |
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); |
||||
|
||||
SettingsState _$SettingsStateFromJson(Map<String, dynamic> json) { |
||||
return _SettingsState.fromJson(json); |
||||
} |
||||
|
||||
/// @nodoc |
||||
mixin _$SettingsState { |
||||
ThemeMode get themeMode => throw _privateConstructorUsedError; |
||||
DefaultScreen get defaultScreen => throw _privateConstructorUsedError; |
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; |
||||
@JsonKey(ignore: true) |
||||
$SettingsStateCopyWith<SettingsState> get copyWith => |
||||
throw _privateConstructorUsedError; |
||||
} |
||||
|
||||
/// @nodoc |
||||
abstract class $SettingsStateCopyWith<$Res> { |
||||
factory $SettingsStateCopyWith( |
||||
SettingsState value, $Res Function(SettingsState) then) = |
||||
_$SettingsStateCopyWithImpl<$Res, SettingsState>; |
||||
@useResult |
||||
$Res call({ThemeMode themeMode, DefaultScreen defaultScreen}); |
||||
} |
||||
|
||||
/// @nodoc |
||||
class _$SettingsStateCopyWithImpl<$Res, $Val extends SettingsState> |
||||
implements $SettingsStateCopyWith<$Res> { |
||||
_$SettingsStateCopyWithImpl(this._value, this._then); |
||||
|
||||
// ignore: unused_field |
||||
final $Val _value; |
||||
// ignore: unused_field |
||||
final $Res Function($Val) _then; |
||||
|
||||
@pragma('vm:prefer-inline') |
||||
@override |
||||
$Res call({ |
||||
Object? themeMode = null, |
||||
Object? defaultScreen = null, |
||||
}) { |
||||
return _then(_value.copyWith( |
||||
themeMode: null == themeMode |
||||
? _value.themeMode |
||||
: themeMode // ignore: cast_nullable_to_non_nullable |
||||
as ThemeMode, |
||||
defaultScreen: null == defaultScreen |
||||
? _value.defaultScreen |
||||
: defaultScreen // ignore: cast_nullable_to_non_nullable |
||||
as DefaultScreen, |
||||
) as $Val); |
||||
} |
||||
} |
||||
|
||||
/// @nodoc |
||||
abstract class _$$SettingsStateImplCopyWith<$Res> |
||||
implements $SettingsStateCopyWith<$Res> { |
||||
factory _$$SettingsStateImplCopyWith( |
||||
_$SettingsStateImpl value, $Res Function(_$SettingsStateImpl) then) = |
||||
__$$SettingsStateImplCopyWithImpl<$Res>; |
||||
@override |
||||
@useResult |
||||
$Res call({ThemeMode themeMode, DefaultScreen defaultScreen}); |
||||
} |
||||
|
||||
/// @nodoc |
||||
class __$$SettingsStateImplCopyWithImpl<$Res> |
||||
extends _$SettingsStateCopyWithImpl<$Res, _$SettingsStateImpl> |
||||
implements _$$SettingsStateImplCopyWith<$Res> { |
||||
__$$SettingsStateImplCopyWithImpl( |
||||
_$SettingsStateImpl _value, $Res Function(_$SettingsStateImpl) _then) |
||||
: super(_value, _then); |
||||
|
||||
@pragma('vm:prefer-inline') |
||||
@override |
||||
$Res call({ |
||||
Object? themeMode = null, |
||||
Object? defaultScreen = null, |
||||
}) { |
||||
return _then(_$SettingsStateImpl( |
||||
themeMode: null == themeMode |
||||
? _value.themeMode |
||||
: themeMode // ignore: cast_nullable_to_non_nullable |
||||
as ThemeMode, |
||||
defaultScreen: null == defaultScreen |
||||
? _value.defaultScreen |
||||
: defaultScreen // ignore: cast_nullable_to_non_nullable |
||||
as DefaultScreen, |
||||
)); |
||||
} |
||||
} |
||||
|
||||
/// @nodoc |
||||
@JsonSerializable() |
||||
class _$SettingsStateImpl implements _SettingsState { |
||||
const _$SettingsStateImpl( |
||||
{this.themeMode = ThemeMode.system, |
||||
this.defaultScreen = DefaultScreen.home}); |
||||
|
||||
factory _$SettingsStateImpl.fromJson(Map<String, dynamic> json) => |
||||
_$$SettingsStateImplFromJson(json); |
||||
|
||||
@override |
||||
@JsonKey() |
||||
final ThemeMode themeMode; |
||||
@override |
||||
@JsonKey() |
||||
final DefaultScreen defaultScreen; |
||||
|
||||
@override |
||||
String toString() { |
||||
return 'SettingsState(themeMode: $themeMode, defaultScreen: $defaultScreen)'; |
||||
} |
||||
|
||||
@override |
||||
bool operator ==(dynamic other) { |
||||
return identical(this, other) || |
||||
(other.runtimeType == runtimeType && |
||||
other is _$SettingsStateImpl && |
||||
(identical(other.themeMode, themeMode) || |
||||
other.themeMode == themeMode) && |
||||
(identical(other.defaultScreen, defaultScreen) || |
||||
other.defaultScreen == defaultScreen)); |
||||
} |
||||
|
||||
@JsonKey(ignore: true) |
||||
@override |
||||
int get hashCode => Object.hash(runtimeType, themeMode, defaultScreen); |
||||
|
||||
@JsonKey(ignore: true) |
||||
@override |
||||
@pragma('vm:prefer-inline') |
||||
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith => |
||||
__$$SettingsStateImplCopyWithImpl<_$SettingsStateImpl>(this, _$identity); |
||||
|
||||
@override |
||||
Map<String, dynamic> toJson() { |
||||
return _$$SettingsStateImplToJson( |
||||
this, |
||||
); |
||||
} |
||||
} |
||||
|
||||
abstract class _SettingsState implements SettingsState { |
||||
const factory _SettingsState( |
||||
{final ThemeMode themeMode, |
||||
final DefaultScreen defaultScreen}) = _$SettingsStateImpl; |
||||
|
||||
factory _SettingsState.fromJson(Map<String, dynamic> json) = |
||||
_$SettingsStateImpl.fromJson; |
||||
|
||||
@override |
||||
ThemeMode get themeMode; |
||||
@override |
||||
DefaultScreen get defaultScreen; |
||||
@override |
||||
@JsonKey(ignore: true) |
||||
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith => |
||||
throw _privateConstructorUsedError; |
||||
} |
@ -0,0 +1,36 @@ |
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
|
||||
part of 'settings_cubit.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// JsonSerializableGenerator |
||||
// ************************************************************************** |
||||
|
||||
_$SettingsStateImpl _$$SettingsStateImplFromJson(Map<String, dynamic> json) => |
||||
_$SettingsStateImpl( |
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? |
||||
ThemeMode.system, |
||||
defaultScreen: |
||||
$enumDecodeNullable(_$DefaultScreenEnumMap, json['defaultScreen']) ?? |
||||
DefaultScreen.home, |
||||
); |
||||
|
||||
Map<String, dynamic> _$$SettingsStateImplToJson(_$SettingsStateImpl instance) => |
||||
<String, dynamic>{ |
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, |
||||
'defaultScreen': _$DefaultScreenEnumMap[instance.defaultScreen]!, |
||||
}; |
||||
|
||||
const _$ThemeModeEnumMap = { |
||||
ThemeMode.system: 'system', |
||||
ThemeMode.light: 'light', |
||||
ThemeMode.dark: 'dark', |
||||
}; |
||||
|
||||
const _$DefaultScreenEnumMap = { |
||||
DefaultScreen.home: 'home', |
||||
DefaultScreen.settings: 'settings', |
||||
DefaultScreen.images: 'images', |
||||
DefaultScreen.videos: 'videos', |
||||
DefaultScreen.text: 'text', |
||||
}; |
@ -1,10 +1,19 @@ |
||||
part of 'settings_cubit.dart'; |
||||
|
||||
sealed class SettingsState extends Equatable { |
||||
const SettingsState(); |
||||
|
||||
@override |
||||
List<Object> get props => []; |
||||
enum DefaultScreen { |
||||
home, |
||||
settings, |
||||
images, |
||||
videos, |
||||
text, |
||||
} |
||||
|
||||
final class SettingsInitial extends SettingsState {} |
||||
@freezed |
||||
class SettingsState with _$SettingsState { |
||||
const factory SettingsState({ |
||||
@Default(ThemeMode.system) ThemeMode themeMode, |
||||
@Default(DefaultScreen.home) DefaultScreen defaultScreen, |
||||
}) = _SettingsState; |
||||
factory SettingsState.fromJson(Map<String, Object?> json) => |
||||
_$SettingsStateFromJson(json); |
||||
} |
||||
|
@ -0,0 +1,105 @@ |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_bloc/flutter_bloc.dart'; |
||||
import 'package:flutter_quill/translations.dart'; |
||||
|
||||
import '../../shared/widgets/dialog_action.dart'; |
||||
import '../../shared/widgets/home_screen_button.dart'; |
||||
import '../cubit/settings_cubit.dart'; |
||||
|
||||
class SettingsScreen extends StatelessWidget { |
||||
const SettingsScreen({super.key}); |
||||
|
||||
static const routeName = '/settings'; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final materialTheme = Theme.of(context); |
||||
final isDark = materialTheme.brightness == Brightness.dark; |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: const Text('Settings'), |
||||
actions: const [ |
||||
HomeScreenButton(), |
||||
], |
||||
), |
||||
body: BlocBuilder<SettingsCubit, SettingsState>( |
||||
builder: (context, state) { |
||||
return ListView( |
||||
children: [ |
||||
CheckboxListTile.adaptive( |
||||
value: isDark, |
||||
onChanged: (value) { |
||||
final isNewValueDark = value ?? false; |
||||
context.read<SettingsCubit>().updateSettings( |
||||
state.copyWith( |
||||
themeMode: |
||||
isNewValueDark ? ThemeMode.dark : ThemeMode.light, |
||||
), |
||||
); |
||||
}, |
||||
title: const Text('Dark Theme'), |
||||
subtitle: const Text( |
||||
'By default we will use your system theme, but you can set if you want dark or light theme', |
||||
), |
||||
secondary: Icon(isDark ? Icons.nightlight : Icons.sunny), |
||||
), |
||||
ListTile( |
||||
title: const Text('Default screen'), |
||||
subtitle: const Text( |
||||
'Which screen should be used when the flutter app starts?', |
||||
), |
||||
leading: const Icon(Icons.home), |
||||
onTap: () async { |
||||
final settingsBloc = context.read<SettingsCubit>(); |
||||
final newDefaultScreen = |
||||
await showAdaptiveDialog<DefaultScreen>( |
||||
context: context, |
||||
builder: (context) { |
||||
return AlertDialog.adaptive( |
||||
title: const Text('Select default screen'), |
||||
content: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
...DefaultScreen.values.map( |
||||
(e) => ListTile( |
||||
onTap: () { |
||||
Navigator.of(context).pop(e); |
||||
}, |
||||
title: Text(e.name), |
||||
leading: CircleAvatar( |
||||
child: Text((e.index + 1).toString()), |
||||
), |
||||
), |
||||
), |
||||
], |
||||
), |
||||
actions: [ |
||||
AppDialogAction( |
||||
onPressed: () => Navigator.of(context).pop(null), |
||||
options: const DialogActionOptions( |
||||
cupertinoDialogActionOptions: |
||||
CupertinoDialogActionOptions( |
||||
isDefaultAction: true, |
||||
), |
||||
), |
||||
child: const Text('Cancel'), |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
); |
||||
if (newDefaultScreen != null) { |
||||
settingsBloc.updateSettings( |
||||
settingsBloc.state |
||||
.copyWith(defaultScreen: newDefaultScreen), |
||||
); |
||||
} |
||||
}, |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
import 'package:flutter/cupertino.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_quill/extensions.dart'; |
||||
|
||||
@immutable |
||||
final class CupertinoDialogActionOptions { |
||||
const CupertinoDialogActionOptions({ |
||||
this.isDefaultAction = false, |
||||
}); |
||||
|
||||
final bool isDefaultAction; |
||||
} |
||||
|
||||
@immutable |
||||
final class MaterialDialogActionOptions { |
||||
const MaterialDialogActionOptions({ |
||||
this.textStyle, |
||||
}); |
||||
|
||||
final ButtonStyle? textStyle; |
||||
} |
||||
|
||||
@immutable |
||||
class DialogActionOptions { |
||||
const DialogActionOptions({ |
||||
this.cupertinoDialogActionOptions, |
||||
this.materialDialogActionOptions, |
||||
}); |
||||
|
||||
final CupertinoDialogActionOptions? cupertinoDialogActionOptions; |
||||
final MaterialDialogActionOptions? materialDialogActionOptions; |
||||
} |
||||
|
||||
class AppDialogAction extends StatelessWidget { |
||||
const AppDialogAction({ |
||||
required this.child, |
||||
required this.onPressed, |
||||
this.options, |
||||
super.key, |
||||
}); |
||||
|
||||
final VoidCallback? onPressed; |
||||
final Widget child; |
||||
|
||||
final DialogActionOptions? options; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
if (isAppleOS(supportWeb: true)) { |
||||
return CupertinoDialogAction( |
||||
onPressed: onPressed, |
||||
isDefaultAction: |
||||
options?.cupertinoDialogActionOptions?.isDefaultAction ?? false, |
||||
child: child, |
||||
); |
||||
} |
||||
return TextButton( |
||||
onPressed: onPressed, |
||||
style: options?.materialDialogActionOptions?.textStyle, |
||||
child: child, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_bloc/flutter_bloc.dart'; |
||||
|
||||
import '../../settings/cubit/settings_cubit.dart'; |
||||
|
||||
class HomeScreenButton extends StatelessWidget { |
||||
const HomeScreenButton({super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return IconButton( |
||||
onPressed: () { |
||||
final settingsCubit = context.read<SettingsCubit>(); |
||||
settingsCubit.updateSettings( |
||||
settingsCubit.state.copyWith( |
||||
defaultScreen: DefaultScreen.home, |
||||
), |
||||
); |
||||
}, |
||||
icon: const Icon(Icons.home), |
||||
tooltip: 'Set the default to home screen', |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue