HECKIN CHONKER (null safety migration without scripts)
This commit is contained in:
parent
9d90aa9aeb
commit
83235534f5
|
@ -36,8 +36,6 @@ extension on CommentSortType {
|
||||||
return (b, a) =>
|
return (b, a) =>
|
||||||
a.comment.counts.score.compareTo(b.comment.counts.score);
|
a.comment.counts.score.compareTo(b.comment.counts.score);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Exception('unreachable');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +43,7 @@ class CommentTree {
|
||||||
CommentView comment;
|
CommentView comment;
|
||||||
List<CommentTree> children;
|
List<CommentTree> children;
|
||||||
|
|
||||||
CommentTree(this.comment, [this.children]) {
|
CommentTree(this.comment, [this.children = const []]);
|
||||||
children ??= [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// takes raw linear comments and turns them into a CommentTree
|
/// takes raw linear comments and turns them into a CommentTree
|
||||||
static List<CommentTree> fromList(List<CommentView> comments) {
|
static List<CommentTree> fromList(List<CommentView> comments) {
|
||||||
|
|
|
@ -18,20 +18,20 @@ class AssetGenImage extends AssetImage {
|
||||||
final String _assetName;
|
final String _assetName;
|
||||||
|
|
||||||
Image image({
|
Image image({
|
||||||
Key key,
|
Key? key,
|
||||||
ImageFrameBuilder frameBuilder,
|
ImageFrameBuilder? frameBuilder,
|
||||||
ImageLoadingBuilder loadingBuilder,
|
ImageLoadingBuilder? loadingBuilder,
|
||||||
ImageErrorWidgetBuilder errorBuilder,
|
ImageErrorWidgetBuilder? errorBuilder,
|
||||||
String semanticLabel,
|
String? semanticLabel,
|
||||||
bool excludeFromSemantics = false,
|
bool excludeFromSemantics = false,
|
||||||
double width,
|
double? width,
|
||||||
double height,
|
double? height,
|
||||||
Color color,
|
Color? color,
|
||||||
BlendMode colorBlendMode,
|
BlendMode? colorBlendMode,
|
||||||
BoxFit fit,
|
BoxFit? fit,
|
||||||
AlignmentGeometry alignment = Alignment.center,
|
AlignmentGeometry alignment = Alignment.center,
|
||||||
ImageRepeat repeat = ImageRepeat.noRepeat,
|
ImageRepeat repeat = ImageRepeat.noRepeat,
|
||||||
Rect centerSlice,
|
Rect? centerSlice,
|
||||||
bool matchTextDirection = false,
|
bool matchTextDirection = false,
|
||||||
bool gaplessPlayback = false,
|
bool gaplessPlayback = false,
|
||||||
bool isAntiAlias = false,
|
bool isAntiAlias = false,
|
||||||
|
|
|
@ -10,8 +10,8 @@ class Debounce {
|
||||||
final VoidCallback callback;
|
final VoidCallback callback;
|
||||||
|
|
||||||
const Debounce({
|
const Debounce({
|
||||||
@required this.loading,
|
required this.loading,
|
||||||
@required this.callback,
|
required this.callback,
|
||||||
});
|
});
|
||||||
|
|
||||||
void call() => callback();
|
void call() => callback();
|
||||||
|
@ -24,7 +24,7 @@ Debounce useDebounce(
|
||||||
Duration delayDuration = const Duration(seconds: 1),
|
Duration delayDuration = const Duration(seconds: 1),
|
||||||
]) {
|
]) {
|
||||||
final loading = useState(false);
|
final loading = useState(false);
|
||||||
final timerHandle = useRef<Timer>(null);
|
final timerHandle = useRef<Timer?>(null);
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
timerHandle.current?.cancel();
|
timerHandle.current?.cancel();
|
||||||
|
|
|
@ -12,10 +12,10 @@ class DelayedLoading {
|
||||||
final VoidCallback cancel;
|
final VoidCallback cancel;
|
||||||
|
|
||||||
const DelayedLoading({
|
const DelayedLoading({
|
||||||
@required this.pending,
|
required this.pending,
|
||||||
@required this.loading,
|
required this.loading,
|
||||||
@required this.start,
|
required this.start,
|
||||||
@required this.cancel,
|
required this.cancel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ DelayedLoading useDelayedLoading(
|
||||||
[Duration delayDuration = const Duration(milliseconds: 500)]) {
|
[Duration delayDuration = const Duration(milliseconds: 500)]) {
|
||||||
final loading = useState(false);
|
final loading = useState(false);
|
||||||
final pending = useState(false);
|
final pending = useState(false);
|
||||||
final timerHandle = useRef<Timer>(null);
|
final timerHandle = useRef<Timer?>(null);
|
||||||
|
|
||||||
return DelayedLoading(
|
return DelayedLoading(
|
||||||
loading: loading.value,
|
loading: loading.value,
|
||||||
|
|
|
@ -2,10 +2,5 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
import '../widgets/infinite_scroll.dart';
|
import '../widgets/infinite_scroll.dart';
|
||||||
|
|
||||||
InfiniteScrollController useInfiniteScrollController() {
|
InfiniteScrollController useInfiniteScrollController() =>
|
||||||
final controller = useMemoized(() => InfiniteScrollController());
|
useMemoized(() => InfiniteScrollController());
|
||||||
|
|
||||||
useEffect(() => controller.dispose, []);
|
|
||||||
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,14 +14,13 @@ import 'stores.dart';
|
||||||
|
|
||||||
VoidCallback Function(
|
VoidCallback Function(
|
||||||
void Function(Jwt token) action, [
|
void Function(Jwt token) action, [
|
||||||
String message,
|
String? message,
|
||||||
]) useLoggedInAction(String instanceHost, {bool any = false}) {
|
]) useAnyLoggedInAction() {
|
||||||
final context = useContext();
|
final context = useContext();
|
||||||
final store = useAccountsStore();
|
final store = useAccountsStore();
|
||||||
|
|
||||||
return (action, [message]) {
|
return (action, [message]) {
|
||||||
if (any && store.hasNoAccount ||
|
if (store.hasNoAccount) {
|
||||||
!any && store.isAnonymousFor(instanceHost)) {
|
|
||||||
return () {
|
return () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content: Text(message ?? 'you have to be logged in to do that'),
|
content: Text(message ?? 'you have to be logged in to do that'),
|
||||||
|
@ -31,7 +30,29 @@ VoidCallback Function(
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
final token = store.defaultTokenFor(instanceHost);
|
return () => action(store.defaultToken!);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VoidCallback Function(
|
||||||
|
void Function(Jwt token) action, [
|
||||||
|
String? message,
|
||||||
|
]) useLoggedInAction(String instanceHost) {
|
||||||
|
final context = useContext();
|
||||||
|
final store = useAccountsStore();
|
||||||
|
|
||||||
|
return (action, [message]) {
|
||||||
|
if (store.isAnonymousFor(instanceHost)) {
|
||||||
|
return () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text(message ?? 'you have to be logged in to do that'),
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: 'log in',
|
||||||
|
onPressed: () => goTo(context, (_) => AccountsConfigPage())),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
final token = store.defaultTokenFor(instanceHost)!;
|
||||||
return () => action(token);
|
return () => action(token);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
/// creates an [AsyncSnapshot] from the Future returned from the valueBuilder.
|
/// creates an [AsyncSnapshot] from the Future returned from the valueBuilder.
|
||||||
/// [keys] can be used to rebuild the Future
|
/// [keys] can be used to rebuild the Future
|
||||||
AsyncSnapshot<T> useMemoFuture<T>(Future<T> Function() valueBuilder,
|
AsyncSnapshot<T?> useMemoFuture<T>(Future<T> Function() valueBuilder,
|
||||||
[List<Object> keys = const <dynamic>[]]) =>
|
[List<Object> keys = const <Object>[]]) =>
|
||||||
useFuture(useMemoized<Future<T>>(valueBuilder, keys),
|
useFuture(useMemoized<Future<T>>(valueBuilder, keys),
|
||||||
preserveState: false, initialData: null);
|
preserveState: false, initialData: null);
|
||||||
|
|
|
@ -5,9 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'memo_future.dart';
|
import 'memo_future.dart';
|
||||||
|
|
||||||
class Refreshable<T> {
|
class Refreshable<T> {
|
||||||
const Refreshable({@required this.snapshot, @required this.refresh})
|
const Refreshable({required this.snapshot, required this.refresh});
|
||||||
: assert(snapshot != null),
|
|
||||||
assert(refresh != null);
|
|
||||||
|
|
||||||
final AsyncSnapshot<T> snapshot;
|
final AsyncSnapshot<T> snapshot;
|
||||||
final AsyncCallback refresh;
|
final AsyncCallback refresh;
|
||||||
|
@ -20,9 +18,9 @@ class Refreshable<T> {
|
||||||
///
|
///
|
||||||
/// `keys` will re-run the initial fetching thus yielding a
|
/// `keys` will re-run the initial fetching thus yielding a
|
||||||
/// loading state in the AsyncSnapshot
|
/// loading state in the AsyncSnapshot
|
||||||
Refreshable<T> useRefreshable<T>(AsyncValueGetter<T> fetcher,
|
Refreshable<T?> useRefreshable<T>(AsyncValueGetter<T> fetcher,
|
||||||
[List<Object> keys = const <dynamic>[]]) {
|
[List<Object> keys = const <Object>[]]) {
|
||||||
final newData = useState<T>(null);
|
final newData = useState<T?>(null);
|
||||||
final snapshot = useMemoFuture(() async {
|
final snapshot = useMemoFuture(() async {
|
||||||
newData.value = null;
|
newData.value = null;
|
||||||
return fetcher();
|
return fetcher();
|
||||||
|
|
|
@ -6,7 +6,7 @@ export 'l10n_api.dart';
|
||||||
export 'l10n_from_string.dart';
|
export 'l10n_from_string.dart';
|
||||||
|
|
||||||
abstract class LocaleSerde {
|
abstract class LocaleSerde {
|
||||||
static Locale fromJson(String json) {
|
static Locale? fromJson(String? json) {
|
||||||
if (json == null) return null;
|
if (json == null) return null;
|
||||||
|
|
||||||
final lang = json.split('-');
|
final lang = json.split('-');
|
||||||
|
|
|
@ -6,25 +6,25 @@ extension SortTypeL10n on SortType {
|
||||||
String tr(BuildContext context) {
|
String tr(BuildContext context) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case SortType.hot:
|
case SortType.hot:
|
||||||
return L10n.of(context).hot;
|
return L10n.of(context)!.hot;
|
||||||
case SortType.new_:
|
case SortType.new_:
|
||||||
return L10n.of(context).new_;
|
return L10n.of(context)!.new_;
|
||||||
case SortType.topYear:
|
case SortType.topYear:
|
||||||
return L10n.of(context).top_year;
|
return L10n.of(context)!.top_year;
|
||||||
case SortType.topMonth:
|
case SortType.topMonth:
|
||||||
return L10n.of(context).top_month;
|
return L10n.of(context)!.top_month;
|
||||||
case SortType.topWeek:
|
case SortType.topWeek:
|
||||||
return L10n.of(context).top_week;
|
return L10n.of(context)!.top_week;
|
||||||
case SortType.topDay:
|
case SortType.topDay:
|
||||||
return L10n.of(context).top_day;
|
return L10n.of(context)!.top_day;
|
||||||
case SortType.topAll:
|
case SortType.topAll:
|
||||||
return L10n.of(context).top_all;
|
return L10n.of(context)!.top_all;
|
||||||
case SortType.newComments:
|
case SortType.newComments:
|
||||||
return L10n.of(context).new_comments;
|
return L10n.of(context)!.new_comments;
|
||||||
case SortType.active:
|
case SortType.active:
|
||||||
return L10n.of(context).active;
|
return L10n.of(context)!.active;
|
||||||
case SortType.mostComments:
|
case SortType.mostComments:
|
||||||
return L10n.of(context).most_comments;
|
return L10n.of(context)!.most_comments;
|
||||||
default:
|
default:
|
||||||
throw Exception('unreachable');
|
throw Exception('unreachable');
|
||||||
}
|
}
|
||||||
|
@ -35,13 +35,13 @@ extension PostListingTypeL10n on PostListingType {
|
||||||
String tr(BuildContext context) {
|
String tr(BuildContext context) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case PostListingType.all:
|
case PostListingType.all:
|
||||||
return L10n.of(context).all;
|
return L10n.of(context)!.all;
|
||||||
case PostListingType.community:
|
case PostListingType.community:
|
||||||
return L10n.of(context).community;
|
return L10n.of(context)!.community;
|
||||||
case PostListingType.local:
|
case PostListingType.local:
|
||||||
return L10n.of(context).local;
|
return L10n.of(context)!.local;
|
||||||
case PostListingType.subscribed:
|
case PostListingType.subscribed:
|
||||||
return L10n.of(context).subscribed;
|
return L10n.of(context)!.subscribed;
|
||||||
default:
|
default:
|
||||||
throw Exception('unreachable');
|
throw Exception('unreachable');
|
||||||
}
|
}
|
||||||
|
@ -52,17 +52,17 @@ extension SearchTypeL10n on SearchType {
|
||||||
String tr(BuildContext context) {
|
String tr(BuildContext context) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case SearchType.all:
|
case SearchType.all:
|
||||||
return L10n.of(context).all;
|
return L10n.of(context)!.all;
|
||||||
case SearchType.comments:
|
case SearchType.comments:
|
||||||
return L10n.of(context).comments;
|
return L10n.of(context)!.comments;
|
||||||
case SearchType.communities:
|
case SearchType.communities:
|
||||||
return L10n.of(context).communities;
|
return L10n.of(context)!.communities;
|
||||||
case SearchType.posts:
|
case SearchType.posts:
|
||||||
return L10n.of(context).posts;
|
return L10n.of(context)!.posts;
|
||||||
case SearchType.url:
|
case SearchType.url:
|
||||||
return L10n.of(context).url;
|
return L10n.of(context)!.url;
|
||||||
case SearchType.users:
|
case SearchType.users:
|
||||||
return L10n.of(context).users;
|
return L10n.of(context)!.users;
|
||||||
default:
|
default:
|
||||||
throw Exception('unreachable');
|
throw Exception('unreachable');
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,255 +147,255 @@ extension L10nFromString on String {
|
||||||
String tr(BuildContext context) {
|
String tr(BuildContext context) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case L10nStrings.settings:
|
case L10nStrings.settings:
|
||||||
return L10n.of(context).settings;
|
return L10n.of(context)!.settings;
|
||||||
case L10nStrings.password:
|
case L10nStrings.password:
|
||||||
return L10n.of(context).password;
|
return L10n.of(context)!.password;
|
||||||
case L10nStrings.email_or_username:
|
case L10nStrings.email_or_username:
|
||||||
return L10n.of(context).email_or_username;
|
return L10n.of(context)!.email_or_username;
|
||||||
case L10nStrings.posts:
|
case L10nStrings.posts:
|
||||||
return L10n.of(context).posts;
|
return L10n.of(context)!.posts;
|
||||||
case L10nStrings.comments:
|
case L10nStrings.comments:
|
||||||
return L10n.of(context).comments;
|
return L10n.of(context)!.comments;
|
||||||
case L10nStrings.modlog:
|
case L10nStrings.modlog:
|
||||||
return L10n.of(context).modlog;
|
return L10n.of(context)!.modlog;
|
||||||
case L10nStrings.community:
|
case L10nStrings.community:
|
||||||
return L10n.of(context).community;
|
return L10n.of(context)!.community;
|
||||||
case L10nStrings.url:
|
case L10nStrings.url:
|
||||||
return L10n.of(context).url;
|
return L10n.of(context)!.url;
|
||||||
case L10nStrings.title:
|
case L10nStrings.title:
|
||||||
return L10n.of(context).title;
|
return L10n.of(context)!.title;
|
||||||
case L10nStrings.body:
|
case L10nStrings.body:
|
||||||
return L10n.of(context).body;
|
return L10n.of(context)!.body;
|
||||||
case L10nStrings.nsfw:
|
case L10nStrings.nsfw:
|
||||||
return L10n.of(context).nsfw;
|
return L10n.of(context)!.nsfw;
|
||||||
case L10nStrings.post:
|
case L10nStrings.post:
|
||||||
return L10n.of(context).post;
|
return L10n.of(context)!.post;
|
||||||
case L10nStrings.save:
|
case L10nStrings.save:
|
||||||
return L10n.of(context).save;
|
return L10n.of(context)!.save;
|
||||||
case L10nStrings.subscribed:
|
case L10nStrings.subscribed:
|
||||||
return L10n.of(context).subscribed;
|
return L10n.of(context)!.subscribed;
|
||||||
case L10nStrings.local:
|
case L10nStrings.local:
|
||||||
return L10n.of(context).local;
|
return L10n.of(context)!.local;
|
||||||
case L10nStrings.all:
|
case L10nStrings.all:
|
||||||
return L10n.of(context).all;
|
return L10n.of(context)!.all;
|
||||||
case L10nStrings.replies:
|
case L10nStrings.replies:
|
||||||
return L10n.of(context).replies;
|
return L10n.of(context)!.replies;
|
||||||
case L10nStrings.mentions:
|
case L10nStrings.mentions:
|
||||||
return L10n.of(context).mentions;
|
return L10n.of(context)!.mentions;
|
||||||
case L10nStrings.from:
|
case L10nStrings.from:
|
||||||
return L10n.of(context).from;
|
return L10n.of(context)!.from;
|
||||||
case L10nStrings.to:
|
case L10nStrings.to:
|
||||||
return L10n.of(context).to;
|
return L10n.of(context)!.to;
|
||||||
case L10nStrings.deleted_by_creator:
|
case L10nStrings.deleted_by_creator:
|
||||||
return L10n.of(context).deleted_by_creator;
|
return L10n.of(context)!.deleted_by_creator;
|
||||||
case L10nStrings.more:
|
case L10nStrings.more:
|
||||||
return L10n.of(context).more;
|
return L10n.of(context)!.more;
|
||||||
case L10nStrings.mark_as_read:
|
case L10nStrings.mark_as_read:
|
||||||
return L10n.of(context).mark_as_read;
|
return L10n.of(context)!.mark_as_read;
|
||||||
case L10nStrings.mark_as_unread:
|
case L10nStrings.mark_as_unread:
|
||||||
return L10n.of(context).mark_as_unread;
|
return L10n.of(context)!.mark_as_unread;
|
||||||
case L10nStrings.reply:
|
case L10nStrings.reply:
|
||||||
return L10n.of(context).reply;
|
return L10n.of(context)!.reply;
|
||||||
case L10nStrings.edit:
|
case L10nStrings.edit:
|
||||||
return L10n.of(context).edit;
|
return L10n.of(context)!.edit;
|
||||||
case L10nStrings.delete:
|
case L10nStrings.delete:
|
||||||
return L10n.of(context).delete;
|
return L10n.of(context)!.delete;
|
||||||
case L10nStrings.restore:
|
case L10nStrings.restore:
|
||||||
return L10n.of(context).restore;
|
return L10n.of(context)!.restore;
|
||||||
case L10nStrings.yes:
|
case L10nStrings.yes:
|
||||||
return L10n.of(context).yes;
|
return L10n.of(context)!.yes;
|
||||||
case L10nStrings.no:
|
case L10nStrings.no:
|
||||||
return L10n.of(context).no;
|
return L10n.of(context)!.no;
|
||||||
case L10nStrings.avatar:
|
case L10nStrings.avatar:
|
||||||
return L10n.of(context).avatar;
|
return L10n.of(context)!.avatar;
|
||||||
case L10nStrings.banner:
|
case L10nStrings.banner:
|
||||||
return L10n.of(context).banner;
|
return L10n.of(context)!.banner;
|
||||||
case L10nStrings.display_name:
|
case L10nStrings.display_name:
|
||||||
return L10n.of(context).display_name;
|
return L10n.of(context)!.display_name;
|
||||||
case L10nStrings.bio:
|
case L10nStrings.bio:
|
||||||
return L10n.of(context).bio;
|
return L10n.of(context)!.bio;
|
||||||
case L10nStrings.email:
|
case L10nStrings.email:
|
||||||
return L10n.of(context).email;
|
return L10n.of(context)!.email;
|
||||||
case L10nStrings.matrix_user:
|
case L10nStrings.matrix_user:
|
||||||
return L10n.of(context).matrix_user;
|
return L10n.of(context)!.matrix_user;
|
||||||
case L10nStrings.sort_type:
|
case L10nStrings.sort_type:
|
||||||
return L10n.of(context).sort_type;
|
return L10n.of(context)!.sort_type;
|
||||||
case L10nStrings.type:
|
case L10nStrings.type:
|
||||||
return L10n.of(context).type;
|
return L10n.of(context)!.type;
|
||||||
case L10nStrings.show_nsfw:
|
case L10nStrings.show_nsfw:
|
||||||
return L10n.of(context).show_nsfw;
|
return L10n.of(context)!.show_nsfw;
|
||||||
case L10nStrings.send_notifications_to_email:
|
case L10nStrings.send_notifications_to_email:
|
||||||
return L10n.of(context).send_notifications_to_email;
|
return L10n.of(context)!.send_notifications_to_email;
|
||||||
case L10nStrings.delete_account:
|
case L10nStrings.delete_account:
|
||||||
return L10n.of(context).delete_account;
|
return L10n.of(context)!.delete_account;
|
||||||
case L10nStrings.saved:
|
case L10nStrings.saved:
|
||||||
return L10n.of(context).saved;
|
return L10n.of(context)!.saved;
|
||||||
case L10nStrings.communities:
|
case L10nStrings.communities:
|
||||||
return L10n.of(context).communities;
|
return L10n.of(context)!.communities;
|
||||||
case L10nStrings.users:
|
case L10nStrings.users:
|
||||||
return L10n.of(context).users;
|
return L10n.of(context)!.users;
|
||||||
case L10nStrings.theme:
|
case L10nStrings.theme:
|
||||||
return L10n.of(context).theme;
|
return L10n.of(context)!.theme;
|
||||||
case L10nStrings.language:
|
case L10nStrings.language:
|
||||||
return L10n.of(context).language;
|
return L10n.of(context)!.language;
|
||||||
case L10nStrings.hot:
|
case L10nStrings.hot:
|
||||||
return L10n.of(context).hot;
|
return L10n.of(context)!.hot;
|
||||||
case L10nStrings.new_:
|
case L10nStrings.new_:
|
||||||
return L10n.of(context).new_;
|
return L10n.of(context)!.new_;
|
||||||
case L10nStrings.old:
|
case L10nStrings.old:
|
||||||
return L10n.of(context).old;
|
return L10n.of(context)!.old;
|
||||||
case L10nStrings.top:
|
case L10nStrings.top:
|
||||||
return L10n.of(context).top;
|
return L10n.of(context)!.top;
|
||||||
case L10nStrings.chat:
|
case L10nStrings.chat:
|
||||||
return L10n.of(context).chat;
|
return L10n.of(context)!.chat;
|
||||||
case L10nStrings.admin:
|
case L10nStrings.admin:
|
||||||
return L10n.of(context).admin;
|
return L10n.of(context)!.admin;
|
||||||
case L10nStrings.by:
|
case L10nStrings.by:
|
||||||
return L10n.of(context).by;
|
return L10n.of(context)!.by;
|
||||||
case L10nStrings.not_a_mod_or_admin:
|
case L10nStrings.not_a_mod_or_admin:
|
||||||
return L10n.of(context).not_a_mod_or_admin;
|
return L10n.of(context)!.not_a_mod_or_admin;
|
||||||
case L10nStrings.not_an_admin:
|
case L10nStrings.not_an_admin:
|
||||||
return L10n.of(context).not_an_admin;
|
return L10n.of(context)!.not_an_admin;
|
||||||
case L10nStrings.couldnt_find_post:
|
case L10nStrings.couldnt_find_post:
|
||||||
return L10n.of(context).couldnt_find_post;
|
return L10n.of(context)!.couldnt_find_post;
|
||||||
case L10nStrings.not_logged_in:
|
case L10nStrings.not_logged_in:
|
||||||
return L10n.of(context).not_logged_in;
|
return L10n.of(context)!.not_logged_in;
|
||||||
case L10nStrings.site_ban:
|
case L10nStrings.site_ban:
|
||||||
return L10n.of(context).site_ban;
|
return L10n.of(context)!.site_ban;
|
||||||
case L10nStrings.community_ban:
|
case L10nStrings.community_ban:
|
||||||
return L10n.of(context).community_ban;
|
return L10n.of(context)!.community_ban;
|
||||||
case L10nStrings.downvotes_disabled:
|
case L10nStrings.downvotes_disabled:
|
||||||
return L10n.of(context).downvotes_disabled;
|
return L10n.of(context)!.downvotes_disabled;
|
||||||
case L10nStrings.invalid_url:
|
case L10nStrings.invalid_url:
|
||||||
return L10n.of(context).invalid_url;
|
return L10n.of(context)!.invalid_url;
|
||||||
case L10nStrings.locked:
|
case L10nStrings.locked:
|
||||||
return L10n.of(context).locked;
|
return L10n.of(context)!.locked;
|
||||||
case L10nStrings.couldnt_create_comment:
|
case L10nStrings.couldnt_create_comment:
|
||||||
return L10n.of(context).couldnt_create_comment;
|
return L10n.of(context)!.couldnt_create_comment;
|
||||||
case L10nStrings.couldnt_like_comment:
|
case L10nStrings.couldnt_like_comment:
|
||||||
return L10n.of(context).couldnt_like_comment;
|
return L10n.of(context)!.couldnt_like_comment;
|
||||||
case L10nStrings.couldnt_update_comment:
|
case L10nStrings.couldnt_update_comment:
|
||||||
return L10n.of(context).couldnt_update_comment;
|
return L10n.of(context)!.couldnt_update_comment;
|
||||||
case L10nStrings.no_comment_edit_allowed:
|
case L10nStrings.no_comment_edit_allowed:
|
||||||
return L10n.of(context).no_comment_edit_allowed;
|
return L10n.of(context)!.no_comment_edit_allowed;
|
||||||
case L10nStrings.couldnt_save_comment:
|
case L10nStrings.couldnt_save_comment:
|
||||||
return L10n.of(context).couldnt_save_comment;
|
return L10n.of(context)!.couldnt_save_comment;
|
||||||
case L10nStrings.couldnt_get_comments:
|
case L10nStrings.couldnt_get_comments:
|
||||||
return L10n.of(context).couldnt_get_comments;
|
return L10n.of(context)!.couldnt_get_comments;
|
||||||
case L10nStrings.report_reason_required:
|
case L10nStrings.report_reason_required:
|
||||||
return L10n.of(context).report_reason_required;
|
return L10n.of(context)!.report_reason_required;
|
||||||
case L10nStrings.report_too_long:
|
case L10nStrings.report_too_long:
|
||||||
return L10n.of(context).report_too_long;
|
return L10n.of(context)!.report_too_long;
|
||||||
case L10nStrings.couldnt_create_report:
|
case L10nStrings.couldnt_create_report:
|
||||||
return L10n.of(context).couldnt_create_report;
|
return L10n.of(context)!.couldnt_create_report;
|
||||||
case L10nStrings.couldnt_resolve_report:
|
case L10nStrings.couldnt_resolve_report:
|
||||||
return L10n.of(context).couldnt_resolve_report;
|
return L10n.of(context)!.couldnt_resolve_report;
|
||||||
case L10nStrings.invalid_post_title:
|
case L10nStrings.invalid_post_title:
|
||||||
return L10n.of(context).invalid_post_title;
|
return L10n.of(context)!.invalid_post_title;
|
||||||
case L10nStrings.couldnt_create_post:
|
case L10nStrings.couldnt_create_post:
|
||||||
return L10n.of(context).couldnt_create_post;
|
return L10n.of(context)!.couldnt_create_post;
|
||||||
case L10nStrings.couldnt_like_post:
|
case L10nStrings.couldnt_like_post:
|
||||||
return L10n.of(context).couldnt_like_post;
|
return L10n.of(context)!.couldnt_like_post;
|
||||||
case L10nStrings.couldnt_find_community:
|
case L10nStrings.couldnt_find_community:
|
||||||
return L10n.of(context).couldnt_find_community;
|
return L10n.of(context)!.couldnt_find_community;
|
||||||
case L10nStrings.couldnt_get_posts:
|
case L10nStrings.couldnt_get_posts:
|
||||||
return L10n.of(context).couldnt_get_posts;
|
return L10n.of(context)!.couldnt_get_posts;
|
||||||
case L10nStrings.no_post_edit_allowed:
|
case L10nStrings.no_post_edit_allowed:
|
||||||
return L10n.of(context).no_post_edit_allowed;
|
return L10n.of(context)!.no_post_edit_allowed;
|
||||||
case L10nStrings.couldnt_save_post:
|
case L10nStrings.couldnt_save_post:
|
||||||
return L10n.of(context).couldnt_save_post;
|
return L10n.of(context)!.couldnt_save_post;
|
||||||
case L10nStrings.site_already_exists:
|
case L10nStrings.site_already_exists:
|
||||||
return L10n.of(context).site_already_exists;
|
return L10n.of(context)!.site_already_exists;
|
||||||
case L10nStrings.couldnt_update_site:
|
case L10nStrings.couldnt_update_site:
|
||||||
return L10n.of(context).couldnt_update_site;
|
return L10n.of(context)!.couldnt_update_site;
|
||||||
case L10nStrings.invalid_community_name:
|
case L10nStrings.invalid_community_name:
|
||||||
return L10n.of(context).invalid_community_name;
|
return L10n.of(context)!.invalid_community_name;
|
||||||
case L10nStrings.community_already_exists:
|
case L10nStrings.community_already_exists:
|
||||||
return L10n.of(context).community_already_exists;
|
return L10n.of(context)!.community_already_exists;
|
||||||
case L10nStrings.community_moderator_already_exists:
|
case L10nStrings.community_moderator_already_exists:
|
||||||
return L10n.of(context).community_moderator_already_exists;
|
return L10n.of(context)!.community_moderator_already_exists;
|
||||||
case L10nStrings.community_follower_already_exists:
|
case L10nStrings.community_follower_already_exists:
|
||||||
return L10n.of(context).community_follower_already_exists;
|
return L10n.of(context)!.community_follower_already_exists;
|
||||||
case L10nStrings.not_a_moderator:
|
case L10nStrings.not_a_moderator:
|
||||||
return L10n.of(context).not_a_moderator;
|
return L10n.of(context)!.not_a_moderator;
|
||||||
case L10nStrings.couldnt_update_community:
|
case L10nStrings.couldnt_update_community:
|
||||||
return L10n.of(context).couldnt_update_community;
|
return L10n.of(context)!.couldnt_update_community;
|
||||||
case L10nStrings.no_community_edit_allowed:
|
case L10nStrings.no_community_edit_allowed:
|
||||||
return L10n.of(context).no_community_edit_allowed;
|
return L10n.of(context)!.no_community_edit_allowed;
|
||||||
case L10nStrings.system_err_login:
|
case L10nStrings.system_err_login:
|
||||||
return L10n.of(context).system_err_login;
|
return L10n.of(context)!.system_err_login;
|
||||||
case L10nStrings.community_user_already_banned:
|
case L10nStrings.community_user_already_banned:
|
||||||
return L10n.of(context).community_user_already_banned;
|
return L10n.of(context)!.community_user_already_banned;
|
||||||
case L10nStrings.couldnt_find_that_username_or_email:
|
case L10nStrings.couldnt_find_that_username_or_email:
|
||||||
return L10n.of(context).couldnt_find_that_username_or_email;
|
return L10n.of(context)!.couldnt_find_that_username_or_email;
|
||||||
case L10nStrings.password_incorrect:
|
case L10nStrings.password_incorrect:
|
||||||
return L10n.of(context).password_incorrect;
|
return L10n.of(context)!.password_incorrect;
|
||||||
case L10nStrings.registration_closed:
|
case L10nStrings.registration_closed:
|
||||||
return L10n.of(context).registration_closed;
|
return L10n.of(context)!.registration_closed;
|
||||||
case L10nStrings.invalid_password:
|
case L10nStrings.invalid_password:
|
||||||
return L10n.of(context).invalid_password;
|
return L10n.of(context)!.invalid_password;
|
||||||
case L10nStrings.passwords_dont_match:
|
case L10nStrings.passwords_dont_match:
|
||||||
return L10n.of(context).passwords_dont_match;
|
return L10n.of(context)!.passwords_dont_match;
|
||||||
case L10nStrings.captcha_incorrect:
|
case L10nStrings.captcha_incorrect:
|
||||||
return L10n.of(context).captcha_incorrect;
|
return L10n.of(context)!.captcha_incorrect;
|
||||||
case L10nStrings.invalid_username:
|
case L10nStrings.invalid_username:
|
||||||
return L10n.of(context).invalid_username;
|
return L10n.of(context)!.invalid_username;
|
||||||
case L10nStrings.bio_length_overflow:
|
case L10nStrings.bio_length_overflow:
|
||||||
return L10n.of(context).bio_length_overflow;
|
return L10n.of(context)!.bio_length_overflow;
|
||||||
case L10nStrings.couldnt_update_user:
|
case L10nStrings.couldnt_update_user:
|
||||||
return L10n.of(context).couldnt_update_user;
|
return L10n.of(context)!.couldnt_update_user;
|
||||||
case L10nStrings.couldnt_update_private_message:
|
case L10nStrings.couldnt_update_private_message:
|
||||||
return L10n.of(context).couldnt_update_private_message;
|
return L10n.of(context)!.couldnt_update_private_message;
|
||||||
case L10nStrings.couldnt_update_post:
|
case L10nStrings.couldnt_update_post:
|
||||||
return L10n.of(context).couldnt_update_post;
|
return L10n.of(context)!.couldnt_update_post;
|
||||||
case L10nStrings.couldnt_create_private_message:
|
case L10nStrings.couldnt_create_private_message:
|
||||||
return L10n.of(context).couldnt_create_private_message;
|
return L10n.of(context)!.couldnt_create_private_message;
|
||||||
case L10nStrings.no_private_message_edit_allowed:
|
case L10nStrings.no_private_message_edit_allowed:
|
||||||
return L10n.of(context).no_private_message_edit_allowed;
|
return L10n.of(context)!.no_private_message_edit_allowed;
|
||||||
case L10nStrings.post_title_too_long:
|
case L10nStrings.post_title_too_long:
|
||||||
return L10n.of(context).post_title_too_long;
|
return L10n.of(context)!.post_title_too_long;
|
||||||
case L10nStrings.email_already_exists:
|
case L10nStrings.email_already_exists:
|
||||||
return L10n.of(context).email_already_exists;
|
return L10n.of(context)!.email_already_exists;
|
||||||
case L10nStrings.user_already_exists:
|
case L10nStrings.user_already_exists:
|
||||||
return L10n.of(context).user_already_exists;
|
return L10n.of(context)!.user_already_exists;
|
||||||
case L10nStrings.unsubscribe:
|
case L10nStrings.unsubscribe:
|
||||||
return L10n.of(context).unsubscribe;
|
return L10n.of(context)!.unsubscribe;
|
||||||
case L10nStrings.subscribe:
|
case L10nStrings.subscribe:
|
||||||
return L10n.of(context).subscribe;
|
return L10n.of(context)!.subscribe;
|
||||||
case L10nStrings.messages:
|
case L10nStrings.messages:
|
||||||
return L10n.of(context).messages;
|
return L10n.of(context)!.messages;
|
||||||
case L10nStrings.banned_users:
|
case L10nStrings.banned_users:
|
||||||
return L10n.of(context).banned_users;
|
return L10n.of(context)!.banned_users;
|
||||||
case L10nStrings.delete_account_confirm:
|
case L10nStrings.delete_account_confirm:
|
||||||
return L10n.of(context).delete_account_confirm;
|
return L10n.of(context)!.delete_account_confirm;
|
||||||
case L10nStrings.new_password:
|
case L10nStrings.new_password:
|
||||||
return L10n.of(context).new_password;
|
return L10n.of(context)!.new_password;
|
||||||
case L10nStrings.verify_password:
|
case L10nStrings.verify_password:
|
||||||
return L10n.of(context).verify_password;
|
return L10n.of(context)!.verify_password;
|
||||||
case L10nStrings.old_password:
|
case L10nStrings.old_password:
|
||||||
return L10n.of(context).old_password;
|
return L10n.of(context)!.old_password;
|
||||||
case L10nStrings.show_avatars:
|
case L10nStrings.show_avatars:
|
||||||
return L10n.of(context).show_avatars;
|
return L10n.of(context)!.show_avatars;
|
||||||
case L10nStrings.search:
|
case L10nStrings.search:
|
||||||
return L10n.of(context).search;
|
return L10n.of(context)!.search;
|
||||||
case L10nStrings.send_message:
|
case L10nStrings.send_message:
|
||||||
return L10n.of(context).send_message;
|
return L10n.of(context)!.send_message;
|
||||||
case L10nStrings.top_day:
|
case L10nStrings.top_day:
|
||||||
return L10n.of(context).top_day;
|
return L10n.of(context)!.top_day;
|
||||||
case L10nStrings.top_week:
|
case L10nStrings.top_week:
|
||||||
return L10n.of(context).top_week;
|
return L10n.of(context)!.top_week;
|
||||||
case L10nStrings.top_month:
|
case L10nStrings.top_month:
|
||||||
return L10n.of(context).top_month;
|
return L10n.of(context)!.top_month;
|
||||||
case L10nStrings.top_year:
|
case L10nStrings.top_year:
|
||||||
return L10n.of(context).top_year;
|
return L10n.of(context)!.top_year;
|
||||||
case L10nStrings.top_all:
|
case L10nStrings.top_all:
|
||||||
return L10n.of(context).top_all;
|
return L10n.of(context)!.top_all;
|
||||||
case L10nStrings.most_comments:
|
case L10nStrings.most_comments:
|
||||||
return L10n.of(context).most_comments;
|
return L10n.of(context)!.most_comments;
|
||||||
case L10nStrings.new_comments:
|
case L10nStrings.new_comments:
|
||||||
return L10n.of(context).new_comments;
|
return L10n.of(context)!.new_comments;
|
||||||
case L10nStrings.active:
|
case L10nStrings.active:
|
||||||
return L10n.of(context).active;
|
return L10n.of(context)!.active;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -16,8 +16,7 @@ import 'add_instance.dart';
|
||||||
class AddAccountPage extends HookWidget {
|
class AddAccountPage extends HookWidget {
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
|
|
||||||
const AddAccountPage({@required this.instanceHost})
|
const AddAccountPage({required this.instanceHost});
|
||||||
: assert(instanceHost != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -29,12 +28,12 @@ class AddAccountPage extends HookWidget {
|
||||||
|
|
||||||
final loading = useDelayedLoading();
|
final loading = useDelayedLoading();
|
||||||
final selectedInstance = useState(instanceHost);
|
final selectedInstance = useState(instanceHost);
|
||||||
final icon = useState<String>(null);
|
final icon = useState<String?>(null);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
LemmyApiV3(selectedInstance.value)
|
LemmyApiV3(selectedInstance.value)
|
||||||
.run(const GetSite())
|
.run(const GetSite())
|
||||||
.then((site) => icon.value = site.siteView.site.icon);
|
.then((site) => icon.value = site.siteView?.site.icon);
|
||||||
return null;
|
return null;
|
||||||
}, [selectedInstance.value]);
|
}, [selectedInstance.value]);
|
||||||
|
|
||||||
|
@ -69,9 +68,9 @@ class AddAccountPage extends HookWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 150,
|
height: 150,
|
||||||
child: FullscreenableImage(
|
child: FullscreenableImage(
|
||||||
url: icon.value,
|
url: icon.value!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: icon.value,
|
imageUrl: icon.value!,
|
||||||
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -111,13 +110,13 @@ class AddAccountPage extends HookWidget {
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: usernameController,
|
controller: usernameController,
|
||||||
decoration:
|
decoration:
|
||||||
InputDecoration(labelText: L10n.of(context).email_or_username),
|
InputDecoration(labelText: L10n.of(context)!.email_or_username),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
TextField(
|
TextField(
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: InputDecoration(labelText: L10n.of(context).password),
|
decoration: InputDecoration(labelText: L10n.of(context)!.password),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: usernameController.text.isEmpty ||
|
onPressed: usernameController.text.isEmpty ||
|
||||||
|
|
|
@ -19,8 +19,8 @@ class AddInstancePage extends HookWidget {
|
||||||
useValueListenable(instanceController);
|
useValueListenable(instanceController);
|
||||||
final accountsStore = useAccountsStore();
|
final accountsStore = useAccountsStore();
|
||||||
|
|
||||||
final isSite = useState<bool>(null);
|
final isSite = useState<bool?>(null);
|
||||||
final icon = useState<String>(null);
|
final icon = useState<String?>(null);
|
||||||
final prevInput = usePrevious(instanceController.text);
|
final prevInput = usePrevious(instanceController.text);
|
||||||
final debounce = useDebounce(() async {
|
final debounce = useDebounce(() async {
|
||||||
if (prevInput == instanceController.text) return;
|
if (prevInput == instanceController.text) return;
|
||||||
|
@ -32,7 +32,7 @@ class AddInstancePage extends HookWidget {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
icon.value =
|
icon.value =
|
||||||
(await LemmyApiV3(inst).run(const GetSite())).siteView.site.icon;
|
(await LemmyApiV3(inst).run(const GetSite())).siteView?.site.icon;
|
||||||
isSite.value = true;
|
isSite.value = true;
|
||||||
// ignore: avoid_catches_without_on_clauses
|
// ignore: avoid_catches_without_on_clauses
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -70,9 +70,9 @@ class AddInstancePage extends HookWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 150,
|
height: 150,
|
||||||
child: FullscreenableImage(
|
child: FullscreenableImage(
|
||||||
url: icon.value,
|
url: icon.value!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: icon.value,
|
imageUrl: icon.value!,
|
||||||
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
|
@ -15,10 +15,8 @@ class CommunitiesListPage extends StatelessWidget {
|
||||||
SortType sortType,
|
SortType sortType,
|
||||||
) fetcher;
|
) fetcher;
|
||||||
|
|
||||||
const CommunitiesListPage({Key key, @required this.fetcher, this.title = ''})
|
const CommunitiesListPage({Key? key, required this.fetcher, this.title = ''})
|
||||||
: assert(fetcher != null),
|
: super(key: key);
|
||||||
assert(title != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -46,9 +44,8 @@ class CommunitiesListPage extends StatelessWidget {
|
||||||
class CommunitiesListItem extends StatelessWidget {
|
class CommunitiesListItem extends StatelessWidget {
|
||||||
final CommunityView community;
|
final CommunityView community;
|
||||||
|
|
||||||
const CommunitiesListItem({Key key, @required this.community})
|
const CommunitiesListItem({Key? key, required this.community})
|
||||||
: assert(community != null),
|
: super(key: key);
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ListTile(
|
Widget build(BuildContext context) => ListTile(
|
||||||
|
@ -57,7 +54,7 @@ class CommunitiesListItem extends StatelessWidget {
|
||||||
? Opacity(
|
? Opacity(
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
child: MarkdownText(
|
child: MarkdownText(
|
||||||
community.community.description,
|
community.community.description!,
|
||||||
instanceHost: community.instanceHost,
|
instanceHost: community.instanceHost,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:fuzzy/fuzzy.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
|
|
||||||
import '../hooks/delayed_loading.dart';
|
import '../hooks/delayed_loading.dart';
|
||||||
|
import '../hooks/logged_in_action.dart';
|
||||||
import '../hooks/refreshable.dart';
|
import '../hooks/refreshable.dart';
|
||||||
import '../hooks/stores.dart';
|
import '../hooks/stores.dart';
|
||||||
import '../util/extensions/api.dart';
|
import '../util/extensions/api.dart';
|
||||||
|
@ -37,7 +38,7 @@ class CommunitiesTab extends HookWidget {
|
||||||
.map(
|
.map(
|
||||||
(instanceHost) => LemmyApiV3(instanceHost)
|
(instanceHost) => LemmyApiV3(instanceHost)
|
||||||
.run(const GetSite())
|
.run(const GetSite())
|
||||||
.then((e) => e.siteView.site),
|
.then((e) => e.siteView!.site),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
@ -52,8 +53,8 @@ class CommunitiesTab extends HookWidget {
|
||||||
sort: SortType.active,
|
sort: SortType.active,
|
||||||
savedOnly: false,
|
savedOnly: false,
|
||||||
personId:
|
personId:
|
||||||
accountsStore.defaultTokenFor(instanceHost).payload.sub,
|
accountsStore.defaultTokenFor(instanceHost)!.payload.sub,
|
||||||
auth: accountsStore.defaultTokenFor(instanceHost).raw,
|
auth: accountsStore.defaultTokenFor(instanceHost)!.raw,
|
||||||
))
|
))
|
||||||
.then((e) => e.follows),
|
.then((e) => e.follows),
|
||||||
)
|
)
|
||||||
|
@ -84,7 +85,7 @@ class CommunitiesTab extends HookWidget {
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(
|
child: Text(
|
||||||
communitiesRefreshable.snapshot.error?.toString() ??
|
communitiesRefreshable.snapshot.error?.toString() ??
|
||||||
instancesRefreshable.snapshot.error?.toString(),
|
instancesRefreshable.snapshot.error!.toString(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -115,8 +116,8 @@ class CommunitiesTab extends HookWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final instances = instancesRefreshable.snapshot.data;
|
final instances = instancesRefreshable.snapshot.data!;
|
||||||
final communities = communitiesRefreshable.snapshot.data
|
final communities = communitiesRefreshable.snapshot.data!
|
||||||
..forEach((e) =>
|
..forEach((e) =>
|
||||||
e.sort((a, b) => a.community.name.compareTo(b.community.name)));
|
e.sort((a, b) => a.community.name.compareTo(b.community.name)));
|
||||||
|
|
||||||
|
@ -128,7 +129,7 @@ class CommunitiesTab extends HookWidget {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
filterController.clear();
|
filterController.clear();
|
||||||
primaryFocus.unfocus();
|
primaryFocus?.unfocus();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
);
|
);
|
||||||
|
@ -236,26 +237,24 @@ class _CommunitySubscribeToggle extends HookWidget {
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
|
|
||||||
const _CommunitySubscribeToggle(
|
const _CommunitySubscribeToggle(
|
||||||
{@required this.instanceHost, @required this.communityId, Key key})
|
{required this.instanceHost, required this.communityId, Key? key})
|
||||||
: assert(instanceHost != null),
|
: super(key: key);
|
||||||
assert(communityId != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final subbed = useState(true);
|
final subbed = useState(true);
|
||||||
final delayed = useDelayedLoading();
|
final delayed = useDelayedLoading();
|
||||||
final accountsStore = useAccountsStore();
|
final loggedInAction = useLoggedInAction(instanceHost);
|
||||||
|
|
||||||
handleTap() async {
|
handleTap(Jwt token) async {
|
||||||
delayed.start();
|
delayed.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await LemmyApiV3(instanceHost).run(FollowCommunity(
|
await LemmyApiV3(instanceHost).run(FollowCommunity(
|
||||||
communityId: communityId,
|
communityId: communityId,
|
||||||
follow: !subbed.value,
|
follow: !subbed.value,
|
||||||
auth: accountsStore.defaultTokenFor(instanceHost).raw,
|
auth: token.raw,
|
||||||
));
|
));
|
||||||
subbed.value = !subbed.value;
|
subbed.value = !subbed.value;
|
||||||
} on Exception catch (err) {
|
} on Exception catch (err) {
|
||||||
|
@ -268,7 +267,7 @@ class _CommunitySubscribeToggle extends HookWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: delayed.pending ? () {} : handleTap,
|
onTap: delayed.pending ? () {} : loggedInAction(handleTap),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: delayed.loading
|
decoration: delayed.loading
|
||||||
? null
|
? null
|
||||||
|
|
|
@ -28,26 +28,22 @@ import 'modlog_page.dart';
|
||||||
|
|
||||||
/// Displays posts, comments, and general info about the given community
|
/// Displays posts, comments, and general info about the given community
|
||||||
class CommunityPage extends HookWidget {
|
class CommunityPage extends HookWidget {
|
||||||
final CommunityView _community;
|
final CommunityView? _community;
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
final String communityName;
|
final String? communityName;
|
||||||
final int communityId;
|
final int? communityId;
|
||||||
|
|
||||||
const CommunityPage.fromName({
|
const CommunityPage.fromName({
|
||||||
@required this.communityName,
|
required String this.communityName,
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
}) : assert(communityName != null),
|
}) : communityId = null,
|
||||||
assert(instanceHost != null),
|
|
||||||
communityId = null,
|
|
||||||
_community = null;
|
_community = null;
|
||||||
const CommunityPage.fromId({
|
const CommunityPage.fromId({
|
||||||
@required this.communityId,
|
required int this.communityId,
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
}) : assert(communityId != null),
|
}) : communityName = null,
|
||||||
assert(instanceHost != null),
|
|
||||||
communityName = null,
|
|
||||||
_community = null;
|
_community = null;
|
||||||
CommunityPage.fromCommunityView(this._community)
|
CommunityPage.fromCommunityView(CommunityView this._community)
|
||||||
: instanceHost = _community.instanceHost,
|
: instanceHost = _community.instanceHost,
|
||||||
communityId = _community.community.id,
|
communityId = _community.community.id,
|
||||||
communityName = _community.community.name;
|
communityName = _community.community.name;
|
||||||
|
@ -76,7 +72,7 @@ class CommunityPage extends HookWidget {
|
||||||
|
|
||||||
final community = () {
|
final community = () {
|
||||||
if (fullCommunitySnap.hasData) {
|
if (fullCommunitySnap.hasData) {
|
||||||
return fullCommunitySnap.data.communityView;
|
return fullCommunitySnap.data!.communityView;
|
||||||
} else if (_community != null) {
|
} else if (_community != null) {
|
||||||
return _community;
|
return _community;
|
||||||
} else {
|
} else {
|
||||||
|
@ -173,8 +169,8 @@ class CommunityPage extends HookWidget {
|
||||||
color: theme.cardColor,
|
color: theme.cardColor,
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: L10n.of(context).posts),
|
Tab(text: L10n.of(context)!.posts),
|
||||||
Tab(text: L10n.of(context).comments),
|
Tab(text: L10n.of(context)!.comments),
|
||||||
const Tab(text: 'About'),
|
const Tab(text: 'About'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -224,14 +220,13 @@ class CommunityPage extends HookWidget {
|
||||||
class _CommunityOverview extends StatelessWidget {
|
class _CommunityOverview extends StatelessWidget {
|
||||||
final CommunityView community;
|
final CommunityView community;
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
final int onlineUsers;
|
final int? onlineUsers;
|
||||||
|
|
||||||
const _CommunityOverview({
|
const _CommunityOverview({
|
||||||
@required this.community,
|
required this.community,
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
@required this.onlineUsers,
|
required this.onlineUsers,
|
||||||
}) : assert(instanceHost != null),
|
});
|
||||||
assert(goToInstance != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -257,7 +252,7 @@ class _CommunityOverview extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FullscreenableImage(
|
FullscreenableImage(
|
||||||
url: community.community.icon,
|
url: community.community.icon!,
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
url: community.community.icon,
|
url: community.community.icon,
|
||||||
radius: 83 / 2,
|
radius: 83 / 2,
|
||||||
|
@ -270,9 +265,9 @@ class _CommunityOverview extends StatelessWidget {
|
||||||
return Stack(children: [
|
return Stack(children: [
|
||||||
if (community.community.banner != null)
|
if (community.community.banner != null)
|
||||||
FullscreenableImage(
|
FullscreenableImage(
|
||||||
url: community.community.banner,
|
url: community.community.banner!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: community.community.banner,
|
imageUrl: community.community.banner!,
|
||||||
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -280,7 +275,7 @@ class _CommunityOverview extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 45),
|
padding: const EdgeInsets.only(top: 45),
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
if (community.community.icon != null) icon,
|
if (icon != null) icon,
|
||||||
// NAME
|
// NAME
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -289,7 +284,7 @@ class _CommunityOverview extends StatelessWidget {
|
||||||
overflow: TextOverflow.ellipsis, // TODO: fix overflowing
|
overflow: TextOverflow.ellipsis, // TODO: fix overflowing
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style:
|
style:
|
||||||
theme.textTheme.subtitle1.copyWith(shadows: [shadow]),
|
theme.textTheme.subtitle1?.copyWith(shadows: [shadow]),
|
||||||
children: [
|
children: [
|
||||||
const TextSpan(
|
const TextSpan(
|
||||||
text: '!',
|
text: '!',
|
||||||
|
@ -346,7 +341,7 @@ class _CommunityOverview extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Text(onlineUsers == null
|
Text(onlineUsers == null
|
||||||
? 'xx'
|
? 'xx'
|
||||||
: compactNumber(onlineUsers)),
|
: compactNumber(onlineUsers!)),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -364,14 +359,14 @@ class _CommunityOverview extends StatelessWidget {
|
||||||
|
|
||||||
class _AboutTab extends StatelessWidget {
|
class _AboutTab extends StatelessWidget {
|
||||||
final CommunityView community;
|
final CommunityView community;
|
||||||
final List<CommunityModeratorView> moderators;
|
final List<CommunityModeratorView>? moderators;
|
||||||
final int onlineUsers;
|
final int? onlineUsers;
|
||||||
|
|
||||||
const _AboutTab({
|
const _AboutTab({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.community,
|
required this.community,
|
||||||
@required this.moderators,
|
required this.moderators,
|
||||||
@required this.onlineUsers,
|
required this.onlineUsers,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -384,7 +379,7 @@ class _AboutTab extends StatelessWidget {
|
||||||
if (community.community.description != null) ...[
|
if (community.community.description != null) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
child: MarkdownText(community.community.description,
|
child: MarkdownText(community.community.description!,
|
||||||
instanceHost: community.instanceHost),
|
instanceHost: community.instanceHost),
|
||||||
),
|
),
|
||||||
const _Divider(),
|
const _Divider(),
|
||||||
|
@ -396,10 +391,10 @@ class _AboutTab extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
children: [
|
children: [
|
||||||
Chip(
|
Chip(
|
||||||
label: Text(L10n.of(context)
|
label: Text(L10n.of(context)!
|
||||||
.number_of_users_online(onlineUsers ?? 0))),
|
.number_of_users_online(onlineUsers ?? 0))),
|
||||||
Chip(
|
Chip(
|
||||||
label: Text(L10n.of(context)
|
label: Text(L10n.of(context)!
|
||||||
.number_of_subscribers(community.counts.subscribers))),
|
.number_of_subscribers(community.counts.subscribers))),
|
||||||
Chip(
|
Chip(
|
||||||
label: Text(
|
label: Text(
|
||||||
|
@ -422,16 +417,16 @@ class _AboutTab extends StatelessWidget {
|
||||||
communityName: community.community.name,
|
communityName: community.community.name,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(L10n.of(context).modlog),
|
child: Text(L10n.of(context)!.modlog),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const _Divider(),
|
const _Divider(),
|
||||||
if (moderators != null && moderators.isNotEmpty) ...[
|
if (moderators != null && moderators!.isNotEmpty) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
child: Text('Mods:', style: theme.textTheme.subtitle2),
|
child: Text('Mods:', style: theme.textTheme.subtitle2),
|
||||||
),
|
),
|
||||||
for (final mod in moderators)
|
for (final mod in moderators!)
|
||||||
// TODO: add user picture, maybe make it into reusable component
|
// TODO: add user picture, maybe make it into reusable component
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
|
@ -463,7 +458,7 @@ class _FollowButton extends HookWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final isSubbed = useState(community.subscribed ?? false);
|
final isSubbed = useState(community.subscribed);
|
||||||
final delayed = useDelayedLoading(Duration.zero);
|
final delayed = useDelayedLoading(Duration.zero);
|
||||||
final loggedInAction = useLoggedInAction(community.instanceHost);
|
final loggedInAction = useLoggedInAction(community.instanceHost);
|
||||||
|
|
||||||
|
@ -493,7 +488,7 @@ class _FollowButton extends HookWidget {
|
||||||
|
|
||||||
return ElevatedButtonTheme(
|
return ElevatedButtonTheme(
|
||||||
data: ElevatedButtonThemeData(
|
data: ElevatedButtonThemeData(
|
||||||
style: theme.elevatedButtonTheme.style.copyWith(
|
style: theme.elevatedButtonTheme.style?.copyWith(
|
||||||
shape: MaterialStateProperty.all(const StadiumBorder()),
|
shape: MaterialStateProperty.all(const StadiumBorder()),
|
||||||
textStyle: MaterialStateProperty.all(theme.textTheme.subtitle1),
|
textStyle: MaterialStateProperty.all(theme.textTheme.subtitle1),
|
||||||
),
|
),
|
||||||
|
@ -518,8 +513,8 @@ class _FollowButton extends HookWidget {
|
||||||
? const Icon(Icons.remove, size: 18)
|
? const Icon(Icons.remove, size: 18)
|
||||||
: const Icon(Icons.add, size: 18),
|
: const Icon(Icons.add, size: 18),
|
||||||
label: Text(isSubbed.value
|
label: Text(isSubbed.value
|
||||||
? L10n.of(context).unsubscribe
|
? L10n.of(context)!.unsubscribe
|
||||||
: L10n.of(context).subscribe),
|
: L10n.of(context)!.subscribe),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -23,18 +23,20 @@ import 'full_post.dart';
|
||||||
|
|
||||||
/// Fab that triggers the [CreatePost] modal
|
/// Fab that triggers the [CreatePost] modal
|
||||||
class CreatePostFab extends HookWidget {
|
class CreatePostFab extends HookWidget {
|
||||||
final CommunityView community;
|
final CommunityView? community;
|
||||||
|
|
||||||
const CreatePostFab({this.community});
|
const CreatePostFab({this.community});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loggedInAction = useLoggedInAction(null, any: true);
|
final loggedInAction = useAnyLoggedInAction();
|
||||||
|
|
||||||
return FloatingActionButton(
|
return FloatingActionButton(
|
||||||
onPressed: loggedInAction((_) => showCupertinoModalPopup(
|
onPressed: loggedInAction((_) => showCupertinoModalPopup(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => CreatePostPage.toCommunity(community))),
|
builder: (_) => community == null
|
||||||
|
? const CreatePostPage()
|
||||||
|
: CreatePostPage.toCommunity(community!))),
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,10 +44,10 @@ class CreatePostFab extends HookWidget {
|
||||||
|
|
||||||
/// Modal for creating a post to some community in some instance
|
/// Modal for creating a post to some community in some instance
|
||||||
class CreatePostPage extends HookWidget {
|
class CreatePostPage extends HookWidget {
|
||||||
final CommunityView community;
|
final CommunityView? community;
|
||||||
|
|
||||||
const CreatePostPage() : community = null;
|
const CreatePostPage() : community = null;
|
||||||
const CreatePostPage.toCommunity(this.community);
|
const CreatePostPage.toCommunity(CommunityView this.community);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -61,7 +63,8 @@ class CreatePostPage extends HookWidget {
|
||||||
final delayed = useDelayedLoading();
|
final delayed = useDelayedLoading();
|
||||||
final imagePicker = useImagePicker();
|
final imagePicker = useImagePicker();
|
||||||
final imageUploadLoading = useState(false);
|
final imageUploadLoading = useState(false);
|
||||||
final pictrsDeleteToken = useState<PictrsUploadFile>(null);
|
final pictrsDeleteToken = useState<PictrsUploadFile?>(null);
|
||||||
|
final loggedInAction = useLoggedInAction(selectedInstance.value);
|
||||||
|
|
||||||
final allCommunitiesSnap = useMemoFuture(
|
final allCommunitiesSnap = useMemoFuture(
|
||||||
() => LemmyApiV3(selectedInstance.value)
|
() => LemmyApiV3(selectedInstance.value)
|
||||||
|
@ -69,7 +72,7 @@ class CreatePostPage extends HookWidget {
|
||||||
type: PostListingType.all,
|
type: PostListingType.all,
|
||||||
sort: SortType.hot,
|
sort: SortType.hot,
|
||||||
limit: 9999,
|
limit: 9999,
|
||||||
auth: accStore.defaultTokenFor(selectedInstance.value).raw,
|
auth: accStore.defaultTokenFor(selectedInstance.value)?.raw,
|
||||||
))
|
))
|
||||||
.then(
|
.then(
|
||||||
(value) {
|
(value) {
|
||||||
|
@ -80,14 +83,13 @@ class CreatePostPage extends HookWidget {
|
||||||
[selectedInstance.value],
|
[selectedInstance.value],
|
||||||
);
|
);
|
||||||
|
|
||||||
uploadPicture() async {
|
uploadPicture(Jwt token) async {
|
||||||
try {
|
try {
|
||||||
final pic = await imagePicker.getImage(source: ImageSource.gallery);
|
final pic = await imagePicker.getImage(source: ImageSource.gallery);
|
||||||
// pic is null when the picker was cancelled
|
// pic is null when the picker was cancelled
|
||||||
if (pic != null) {
|
if (pic != null) {
|
||||||
imageUploadLoading.value = true;
|
imageUploadLoading.value = true;
|
||||||
|
|
||||||
final token = accStore.defaultTokenFor(selectedInstance.value);
|
|
||||||
final pictrs = PictrsApi(selectedInstance.value);
|
final pictrs = PictrsApi(selectedInstance.value);
|
||||||
final upload =
|
final upload =
|
||||||
await pictrs.upload(filePath: pic.path, auth: token.raw);
|
await pictrs.upload(filePath: pic.path, auth: token.raw);
|
||||||
|
@ -105,10 +107,8 @@ class CreatePostPage extends HookWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removePicture() {
|
removePicture(PictrsUploadFile deleteToken) {
|
||||||
PictrsApi(selectedInstance.value)
|
PictrsApi(selectedInstance.value).delete(deleteToken).catchError((_) {});
|
||||||
.delete(pictrsDeleteToken.value)
|
|
||||||
.catchError((_) {});
|
|
||||||
|
|
||||||
pictrsDeleteToken.value = null;
|
pictrsDeleteToken.value = null;
|
||||||
urlController.text = '';
|
urlController.text = '';
|
||||||
|
@ -140,10 +140,10 @@ class CreatePostPage extends HookWidget {
|
||||||
|
|
||||||
List<DropdownMenuItem<int>> communitiesList() {
|
List<DropdownMenuItem<int>> communitiesList() {
|
||||||
if (allCommunitiesSnap.hasData) {
|
if (allCommunitiesSnap.hasData) {
|
||||||
return allCommunitiesSnap.data.map(communityDropDownItem).toList();
|
return allCommunitiesSnap.data!.map(communityDropDownItem).toList();
|
||||||
} else {
|
} else {
|
||||||
if (selectedCommunity.value != null) {
|
if (selectedCommunity.value != null) {
|
||||||
return [communityDropDownItem(selectedCommunity.value)];
|
return [communityDropDownItem(selectedCommunity.value!)];
|
||||||
} else {
|
} else {
|
||||||
return const [
|
return const [
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
|
@ -163,8 +163,8 @@ class CreatePostPage extends HookWidget {
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)))),
|
borderRadius: BorderRadius.all(Radius.circular(10)))),
|
||||||
child: DropdownButtonHideUnderline(
|
child: DropdownButtonHideUnderline(
|
||||||
child: DropdownButton<int>(
|
child: DropdownButton<int>(
|
||||||
value: selectedCommunity.value?.community?.id,
|
value: selectedCommunity.value?.community.id,
|
||||||
hint: Text(L10n.of(context).community),
|
hint: Text(L10n.of(context)!.community),
|
||||||
onChanged: (communityId) => selectedCommunity.value =
|
onChanged: (communityId) => selectedCommunity.value =
|
||||||
allCommunitiesSnap.data
|
allCommunitiesSnap.data
|
||||||
?.firstWhere((e) => e.community.id == communityId),
|
?.firstWhere((e) => e.community.id == communityId),
|
||||||
|
@ -179,7 +179,7 @@ class CreatePostPage extends HookWidget {
|
||||||
enabled: pictrsDeleteToken.value == null,
|
enabled: pictrsDeleteToken.value == null,
|
||||||
controller: urlController,
|
controller: urlController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: L10n.of(context).url,
|
labelText: L10n.of(context)!.url,
|
||||||
suffixIcon: const Icon(Icons.link),
|
suffixIcon: const Icon(Icons.link),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -191,8 +191,9 @@ class CreatePostPage extends HookWidget {
|
||||||
: Icon(pictrsDeleteToken.value == null
|
: Icon(pictrsDeleteToken.value == null
|
||||||
? Icons.add_photo_alternate
|
? Icons.add_photo_alternate
|
||||||
: Icons.close),
|
: Icons.close),
|
||||||
onPressed:
|
onPressed: pictrsDeleteToken.value == null
|
||||||
pictrsDeleteToken.value == null ? uploadPicture : removePicture,
|
? loggedInAction(uploadPicture)
|
||||||
|
: () => removePicture(pictrsDeleteToken.value!),
|
||||||
tooltip:
|
tooltip:
|
||||||
pictrsDeleteToken.value == null ? 'Add picture' : 'Delete picture',
|
pictrsDeleteToken.value == null ? 'Add picture' : 'Delete picture',
|
||||||
)
|
)
|
||||||
|
@ -202,7 +203,7 @@ class CreatePostPage extends HookWidget {
|
||||||
controller: titleController,
|
controller: titleController,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
decoration: InputDecoration(labelText: L10n.of(context).title),
|
decoration: InputDecoration(labelText: L10n.of(context)!.title),
|
||||||
);
|
);
|
||||||
|
|
||||||
final body = IndexedStack(
|
final body = IndexedStack(
|
||||||
|
@ -213,7 +214,7 @@ class CreatePostPage extends HookWidget {
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
minLines: 5,
|
minLines: 5,
|
||||||
decoration: InputDecoration(labelText: L10n.of(context).body),
|
decoration: InputDecoration(labelText: L10n.of(context)!.body),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
@ -225,7 +226,7 @@ class CreatePostPage extends HookWidget {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
handleSubmit() async {
|
handleSubmit(Jwt token) async {
|
||||||
if (selectedCommunity.value == null || titleController.text.isEmpty) {
|
if (selectedCommunity.value == null || titleController.text.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||||
content: Text('Choosing a community and a title is required'),
|
content: Text('Choosing a community and a title is required'),
|
||||||
|
@ -235,8 +236,6 @@ class CreatePostPage extends HookWidget {
|
||||||
|
|
||||||
final api = LemmyApiV3(selectedInstance.value);
|
final api = LemmyApiV3(selectedInstance.value);
|
||||||
|
|
||||||
final token = accStore.defaultTokenFor(selectedInstance.value);
|
|
||||||
|
|
||||||
delayed.start();
|
delayed.start();
|
||||||
try {
|
try {
|
||||||
final res = await api.run(CreatePost(
|
final res = await api.run(CreatePost(
|
||||||
|
@ -244,7 +243,7 @@ class CreatePostPage extends HookWidget {
|
||||||
body: bodyController.text.isEmpty ? null : bodyController.text,
|
body: bodyController.text.isEmpty ? null : bodyController.text,
|
||||||
nsfw: nsfw.value,
|
nsfw: nsfw.value,
|
||||||
name: titleController.text,
|
name: titleController.text,
|
||||||
communityId: selectedCommunity.value.community.id,
|
communityId: selectedCommunity.value!.community.id,
|
||||||
auth: token.raw,
|
auth: token.raw,
|
||||||
));
|
));
|
||||||
unawaited(goToReplace(context, (_) => FullPostPage.fromPostView(res)));
|
unawaited(goToReplace(context, (_) => FullPostPage.fromPostView(res)));
|
||||||
|
@ -285,17 +284,20 @@ class CreatePostPage extends HookWidget {
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: nsfw.value,
|
value: nsfw.value,
|
||||||
onChanged: (val) => nsfw.value = val,
|
onChanged: (val) {
|
||||||
|
if (val != null) nsfw.value = val;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Text(L10n.of(context).nsfw)
|
Text(L10n.of(context)!.nsfw)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: delayed.pending ? () {} : handleSubmit,
|
onPressed:
|
||||||
|
delayed.pending ? () {} : loggedInAction(handleSubmit),
|
||||||
child: delayed.loading
|
child: delayed.loading
|
||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: Text(L10n.of(context).post),
|
: Text(L10n.of(context)!.post),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -20,13 +20,11 @@ import '../widgets/write_comment.dart';
|
||||||
class FullPostPage extends HookWidget {
|
class FullPostPage extends HookWidget {
|
||||||
final int id;
|
final int id;
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
final PostView post;
|
final PostView? post;
|
||||||
|
|
||||||
const FullPostPage({@required this.id, @required this.instanceHost})
|
const FullPostPage({required this.id, required this.instanceHost})
|
||||||
: assert(id != null),
|
: post = null;
|
||||||
assert(instanceHost != null),
|
FullPostPage.fromPostView(PostView this.post)
|
||||||
post = null;
|
|
||||||
FullPostPage.fromPostView(this.post)
|
|
||||||
: id = post.post.id,
|
: id = post.post.id,
|
||||||
instanceHost = post.instanceHost;
|
instanceHost = post.instanceHost;
|
||||||
|
|
||||||
|
@ -64,8 +62,8 @@ class FullPostPage extends HookWidget {
|
||||||
// VARIABLES
|
// VARIABLES
|
||||||
|
|
||||||
final post = fullPostRefreshable.snapshot.hasData
|
final post = fullPostRefreshable.snapshot.hasData
|
||||||
? fullPostRefreshable.snapshot.data.postView
|
? fullPostRefreshable.snapshot.data!.postView
|
||||||
: this.post;
|
: this.post!;
|
||||||
|
|
||||||
final fullPost = fullPostRefreshable.snapshot.data;
|
final fullPost = fullPostRefreshable.snapshot.data;
|
||||||
|
|
||||||
|
@ -129,7 +127,7 @@ class FullPostPage extends HookWidget {
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
PostWidget(post, fullPost: true),
|
PostWidget(post, fullPost: true),
|
||||||
if (fullPostRefreshable.snapshot.hasData)
|
if (fullPost != null)
|
||||||
CommentSection(
|
CommentSection(
|
||||||
newComments.value.followedBy(fullPost.comments).toList(),
|
newComments.value.followedBy(fullPost.comments).toList(),
|
||||||
postCreatorId: fullPost.postView.creator.id)
|
postCreatorId: fullPost.postView.creator.id)
|
||||||
|
|
|
@ -32,13 +32,14 @@ class HomeTab extends HookWidget {
|
||||||
final isc = useInfiniteScrollController();
|
final isc = useInfiniteScrollController();
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final instancesIcons = useMemoFuture(() async {
|
final instancesIcons = useMemoFuture(() async {
|
||||||
final instances = accStore.instances.toList(growable: false);
|
final sites = await Future.wait(accStore.instances.map((e) =>
|
||||||
final sites = await Future.wait(instances.map(
|
LemmyApiV3(e)
|
||||||
(e) => LemmyApiV3(e).run(const GetSite()).catchError((e) => null)));
|
.run<FullSiteView?>(const GetSite())
|
||||||
|
.catchError((e) => null)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
for (var i = 0; i < sites.length; i++)
|
for (final site in sites)
|
||||||
instances[i]: sites[i].siteView.site.icon
|
if (site != null) site.instanceHost: site.siteView?.site.icon
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -48,7 +49,8 @@ class HomeTab extends HookWidget {
|
||||||
// - listingType == subscribed on an instance that has no longer a logged in account
|
// - listingType == subscribed on an instance that has no longer a logged in account
|
||||||
// - instanceHost of a removed instance
|
// - instanceHost of a removed instance
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (accStore.isAnonymousFor(selectedList.value.instanceHost) &&
|
if ((selectedList.value.instanceHost == null ||
|
||||||
|
accStore.isAnonymousFor(selectedList.value.instanceHost!)) &&
|
||||||
selectedList.value.listingType == PostListingType.subscribed ||
|
selectedList.value.listingType == PostListingType.subscribed ||
|
||||||
!accStore.instances.contains(selectedList.value.instanceHost)) {
|
!accStore.instances.contains(selectedList.value.instanceHost)) {
|
||||||
selectedList.value = _SelectedList(
|
selectedList.value = _SelectedList(
|
||||||
|
@ -60,7 +62,8 @@ class HomeTab extends HookWidget {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [
|
}, [
|
||||||
accStore.isAnonymousFor(selectedList.value.instanceHost),
|
selectedList.value.instanceHost == null ||
|
||||||
|
accStore.isAnonymousFor(selectedList.value.instanceHost!),
|
||||||
accStore.hasNoAccount,
|
accStore.hasNoAccount,
|
||||||
accStore.instances.length,
|
accStore.instances.length,
|
||||||
]);
|
]);
|
||||||
|
@ -84,10 +87,10 @@ class HomeTab extends HookWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).subscribed,
|
L10n.of(context)!.subscribed,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: accStore.hasNoAccount
|
color: accStore.hasNoAccount
|
||||||
? theme.textTheme.bodyText1.color.withOpacity(0.4)
|
? theme.textTheme.bodyText1?.color?.withOpacity(0.4)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -119,7 +122,7 @@ class HomeTab extends HookWidget {
|
||||||
instance.toUpperCase(),
|
instance.toUpperCase(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color:
|
||||||
theme.textTheme.bodyText1.color.withOpacity(0.7)),
|
theme.textTheme.bodyText1?.color?.withOpacity(0.7)),
|
||||||
),
|
),
|
||||||
onTap: () => goToInstance(context, instance),
|
onTap: () => goToInstance(context, instance),
|
||||||
dense: true,
|
dense: true,
|
||||||
|
@ -127,14 +130,14 @@ class HomeTab extends HookWidget {
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(
|
||||||
vertical: VisualDensity.minimumDensity),
|
vertical: VisualDensity.minimumDensity),
|
||||||
leading: (instancesIcons.hasData &&
|
leading: (instancesIcons.hasData &&
|
||||||
instancesIcons.data[instance] != null)
|
instancesIcons.data![instance] != null)
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(left: 20),
|
padding: const EdgeInsets.only(left: 20),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 25,
|
width: 25,
|
||||||
height: 25,
|
height: 25,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: instancesIcons.data[instance],
|
imageUrl: instancesIcons.data![instance]!,
|
||||||
height: 25,
|
height: 25,
|
||||||
width: 25,
|
width: 25,
|
||||||
),
|
),
|
||||||
|
@ -144,10 +147,10 @@ class HomeTab extends HookWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).subscribed,
|
L10n.of(context)!.subscribed,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: accStore.isAnonymousFor(instance)
|
color: accStore.isAnonymousFor(instance)
|
||||||
? theme.textTheme.bodyText1.color.withOpacity(0.4)
|
? theme.textTheme.bodyText1?.color?.withOpacity(0.4)
|
||||||
: null),
|
: null),
|
||||||
),
|
),
|
||||||
onTap: accStore.isAnonymousFor(instance)
|
onTap: accStore.isAnonymousFor(instance)
|
||||||
|
@ -162,7 +165,7 @@ class HomeTab extends HookWidget {
|
||||||
leading: const SizedBox(width: 20),
|
leading: const SizedBox(width: 20),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.of(context).local),
|
title: Text(L10n.of(context)!.local),
|
||||||
onTap: () => pop(_SelectedList(
|
onTap: () => pop(_SelectedList(
|
||||||
listingType: PostListingType.local,
|
listingType: PostListingType.local,
|
||||||
instanceHost: instance,
|
instanceHost: instance,
|
||||||
|
@ -170,7 +173,7 @@ class HomeTab extends HookWidget {
|
||||||
leading: const SizedBox(width: 20),
|
leading: const SizedBox(width: 20),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.of(context).all),
|
title: Text(L10n.of(context)!.all),
|
||||||
onTap: () => pop(_SelectedList(
|
onTap: () => pop(_SelectedList(
|
||||||
listingType: PostListingType.all,
|
listingType: PostListingType.all,
|
||||||
instanceHost: instance,
|
instanceHost: instance,
|
||||||
|
@ -229,7 +232,7 @@ class HomeTab extends HookWidget {
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: theme.appBarTheme.textTheme.headline6,
|
style: theme.appBarTheme.textTheme?.headline6,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
),
|
),
|
||||||
|
@ -249,15 +252,13 @@ class HomeTab extends HookWidget {
|
||||||
|
|
||||||
/// Infinite list of posts
|
/// Infinite list of posts
|
||||||
class InfiniteHomeList extends HookWidget {
|
class InfiniteHomeList extends HookWidget {
|
||||||
final Function onStyleChange;
|
|
||||||
final InfiniteScrollController controller;
|
final InfiniteScrollController controller;
|
||||||
final _SelectedList selectedList;
|
final _SelectedList selectedList;
|
||||||
|
|
||||||
const InfiniteHomeList({
|
const InfiniteHomeList({
|
||||||
@required this.selectedList,
|
required this.selectedList,
|
||||||
this.onStyleChange,
|
required this.controller,
|
||||||
this.controller,
|
});
|
||||||
}) : assert(selectedList != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -323,7 +324,7 @@ class InfiniteHomeList extends HookWidget {
|
||||||
? (page, limit, sort) =>
|
? (page, limit, sort) =>
|
||||||
generalFetcher(page, limit, sort, selectedList.listingType)
|
generalFetcher(page, limit, sort, selectedList.listingType)
|
||||||
: fetcherFromInstance(
|
: fetcherFromInstance(
|
||||||
selectedList.instanceHost, selectedList.listingType),
|
selectedList.instanceHost!, selectedList.listingType),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -331,13 +332,13 @@ class InfiniteHomeList extends HookWidget {
|
||||||
|
|
||||||
class _SelectedList {
|
class _SelectedList {
|
||||||
/// when null it implies the 'EVERYTHING' mode
|
/// when null it implies the 'EVERYTHING' mode
|
||||||
final String instanceHost;
|
final String? instanceHost;
|
||||||
final PostListingType listingType;
|
final PostListingType listingType;
|
||||||
|
|
||||||
const _SelectedList({
|
const _SelectedList({
|
||||||
@required this.listingType,
|
required this.listingType,
|
||||||
this.instanceHost,
|
this.instanceHost,
|
||||||
}) : assert(listingType != null);
|
});
|
||||||
|
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'SelectedList(instanceHost: $instanceHost, listingType: $listingType)';
|
'SelectedList(instanceHost: $instanceHost, listingType: $listingType)';
|
||||||
|
|
|
@ -45,6 +45,8 @@ class InboxPage extends HookWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final selectedInstance = selected.value!;
|
||||||
|
|
||||||
toggleUnreadOnly() {
|
toggleUnreadOnly() {
|
||||||
unreadOnly.value = !unreadOnly.value;
|
unreadOnly.value = !unreadOnly.value;
|
||||||
isc.clear();
|
isc.clear();
|
||||||
|
@ -60,7 +62,7 @@ class InboxPage extends HookWidget {
|
||||||
isc.clear();
|
isc.clear();
|
||||||
},
|
},
|
||||||
title: 'select instance',
|
title: 'select instance',
|
||||||
groupValue: selected.value,
|
groupValue: selectedInstance,
|
||||||
buttonBuilder: (context, displayString, onPressed) => TextButton(
|
buttonBuilder: (context, displayString, onPressed) => TextButton(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
@ -73,7 +75,7 @@ class InboxPage extends HookWidget {
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
displayString,
|
displayString,
|
||||||
style: theme.appBarTheme.textTheme.headline6,
|
style: theme.appBarTheme.textTheme?.headline6,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
),
|
),
|
||||||
|
@ -93,9 +95,9 @@ class InboxPage extends HookWidget {
|
||||||
],
|
],
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: L10n.of(context).replies),
|
Tab(text: L10n.of(context)!.replies),
|
||||||
Tab(text: L10n.of(context).mentions),
|
Tab(text: L10n.of(context)!.mentions),
|
||||||
Tab(text: L10n.of(context).messages),
|
Tab(text: L10n.of(context)!.messages),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -106,8 +108,8 @@ class InboxPage extends HookWidget {
|
||||||
controller: isc,
|
controller: isc,
|
||||||
defaultSort: SortType.new_,
|
defaultSort: SortType.new_,
|
||||||
fetcher: (page, batchSize, sortType) =>
|
fetcher: (page, batchSize, sortType) =>
|
||||||
LemmyApiV3(selected.value).run(GetReplies(
|
LemmyApiV3(selectedInstance).run(GetReplies(
|
||||||
auth: accStore.defaultTokenFor(selected.value).raw,
|
auth: accStore.defaultTokenFor(selectedInstance)!.raw,
|
||||||
sort: sortType,
|
sort: sortType,
|
||||||
limit: batchSize,
|
limit: batchSize,
|
||||||
page: page,
|
page: page,
|
||||||
|
@ -124,8 +126,8 @@ class InboxPage extends HookWidget {
|
||||||
controller: isc,
|
controller: isc,
|
||||||
defaultSort: SortType.new_,
|
defaultSort: SortType.new_,
|
||||||
fetcher: (page, batchSize, sortType) =>
|
fetcher: (page, batchSize, sortType) =>
|
||||||
LemmyApiV3(selected.value).run(GetPersonMentions(
|
LemmyApiV3(selectedInstance).run(GetPersonMentions(
|
||||||
auth: accStore.defaultTokenFor(selected.value).raw,
|
auth: accStore.defaultTokenFor(selectedInstance)!.raw,
|
||||||
sort: sortType,
|
sort: sortType,
|
||||||
limit: batchSize,
|
limit: batchSize,
|
||||||
page: page,
|
page: page,
|
||||||
|
@ -142,9 +144,9 @@ class InboxPage extends HookWidget {
|
||||||
child: Text('no messages'),
|
child: Text('no messages'),
|
||||||
),
|
),
|
||||||
controller: isc,
|
controller: isc,
|
||||||
fetcher: (page, batchSize) => LemmyApiV3(selected.value).run(
|
fetcher: (page, batchSize) => LemmyApiV3(selectedInstance).run(
|
||||||
GetPrivateMessages(
|
GetPrivateMessages(
|
||||||
auth: accStore.defaultTokenFor(selected.value).raw,
|
auth: accStore.defaultTokenFor(selectedInstance)!.raw,
|
||||||
limit: batchSize,
|
limit: batchSize,
|
||||||
page: page,
|
page: page,
|
||||||
unreadOnly: unreadOnly.value,
|
unreadOnly: unreadOnly.value,
|
||||||
|
@ -167,10 +169,9 @@ class PrivateMessageTile extends HookWidget {
|
||||||
final bool hideOnRead;
|
final bool hideOnRead;
|
||||||
|
|
||||||
const PrivateMessageTile({
|
const PrivateMessageTile({
|
||||||
@required this.privateMessageView,
|
required this.privateMessageView,
|
||||||
this.hideOnRead = false,
|
this.hideOnRead = false,
|
||||||
}) : assert(privateMessageView != null),
|
});
|
||||||
assert(hideOnRead != null);
|
|
||||||
static const double _iconSize = 16;
|
static const double _iconSize = 16;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -189,7 +190,7 @@ class PrivateMessageTile extends HookWidget {
|
||||||
final toMe = useMemoized(() =>
|
final toMe = useMemoized(() =>
|
||||||
pmv.value.recipient.originInstanceHost == pmv.value.instanceHost &&
|
pmv.value.recipient.originInstanceHost == pmv.value.instanceHost &&
|
||||||
pmv.value.recipient.id ==
|
pmv.value.recipient.id ==
|
||||||
accStore.defaultTokenFor(pmv.value.instanceHost)?.payload?.sub);
|
accStore.defaultTokenFor(pmv.value.instanceHost)?.payload.sub);
|
||||||
|
|
||||||
final otherSide =
|
final otherSide =
|
||||||
useMemoized(() => toMe ? pmv.value.creator : pmv.value.recipient);
|
useMemoized(() => toMe ? pmv.value.creator : pmv.value.recipient);
|
||||||
|
@ -239,7 +240,7 @@ class PrivateMessageTile extends HookWidget {
|
||||||
instanceHost: pmv.value.instanceHost,
|
instanceHost: pmv.value.instanceHost,
|
||||||
query: DeletePrivateMessage(
|
query: DeletePrivateMessage(
|
||||||
privateMessageId: pmv.value.privateMessage.id,
|
privateMessageId: pmv.value.privateMessage.id,
|
||||||
auth: accStore.defaultTokenFor(pmv.value.instanceHost)?.raw,
|
auth: accStore.defaultTokenFor(pmv.value.instanceHost)!.raw,
|
||||||
deleted: !deleted.value,
|
deleted: !deleted.value,
|
||||||
),
|
),
|
||||||
onSuccess: (val) => deleted.value = val.privateMessage.deleted,
|
onSuccess: (val) => deleted.value = val.privateMessage.deleted,
|
||||||
|
@ -251,7 +252,7 @@ class PrivateMessageTile extends HookWidget {
|
||||||
instanceHost: pmv.value.instanceHost,
|
instanceHost: pmv.value.instanceHost,
|
||||||
query: MarkPrivateMessageAsRead(
|
query: MarkPrivateMessageAsRead(
|
||||||
privateMessageId: pmv.value.privateMessage.id,
|
privateMessageId: pmv.value.privateMessage.id,
|
||||||
auth: accStore.defaultTokenFor(pmv.value.instanceHost)?.raw,
|
auth: accStore.defaultTokenFor(pmv.value.instanceHost)!.raw,
|
||||||
read: !read.value,
|
read: !read.value,
|
||||||
),
|
),
|
||||||
// TODO: add notification for notifying parent list
|
// TODO: add notification for notifying parent list
|
||||||
|
@ -280,8 +281,8 @@ class PrivateMessageTile extends HookWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${toMe ? L10n.of(context).from : L10n.of(context).to} ',
|
'${toMe ? L10n.of(context)!.from : L10n.of(context)!.to} ',
|
||||||
style: TextStyle(color: theme.textTheme.caption.color),
|
style: TextStyle(color: theme.textTheme.caption?.color),
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
@ -292,7 +293,7 @@ class PrivateMessageTile extends HookWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 5),
|
padding: const EdgeInsets.only(right: 5),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: otherSide.avatar,
|
imageUrl: otherSide.avatar!,
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
imageBuilder: (context, imageProvider) => Container(
|
imageBuilder: (context, imageProvider) => Container(
|
||||||
|
@ -336,7 +337,7 @@ class PrivateMessageTile extends HookWidget {
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
if (pmv.value.privateMessage.deleted)
|
if (pmv.value.privateMessage.deleted)
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context).deleted_by_creator,
|
L10n.of(context)!.deleted_by_creator,
|
||||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
@ -346,19 +347,19 @@ class PrivateMessageTile extends HookWidget {
|
||||||
TileAction(
|
TileAction(
|
||||||
icon: moreIcon,
|
icon: moreIcon,
|
||||||
onPressed: showMoreMenu,
|
onPressed: showMoreMenu,
|
||||||
tooltip: L10n.of(context).more,
|
tooltip: L10n.of(context)!.more,
|
||||||
),
|
),
|
||||||
if (toMe) ...[
|
if (toMe) ...[
|
||||||
TileAction(
|
TileAction(
|
||||||
iconColor: read.value ? theme.accentColor : null,
|
iconColor: read.value ? theme.accentColor : null,
|
||||||
icon: Icons.check,
|
icon: Icons.check,
|
||||||
tooltip: L10n.of(context).mark_as_read,
|
tooltip: L10n.of(context)!.mark_as_read,
|
||||||
onPressed: handleRead,
|
onPressed: handleRead,
|
||||||
delayedLoading: readDelayed,
|
delayedLoading: readDelayed,
|
||||||
),
|
),
|
||||||
TileAction(
|
TileAction(
|
||||||
icon: Icons.reply,
|
icon: Icons.reply,
|
||||||
tooltip: L10n.of(context).reply,
|
tooltip: L10n.of(context)!.reply,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showCupertinoModalPopup(
|
showCupertinoModalPopup(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -371,7 +372,7 @@ class PrivateMessageTile extends HookWidget {
|
||||||
] else ...[
|
] else ...[
|
||||||
TileAction(
|
TileAction(
|
||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
tooltip: L10n.of(context).edit,
|
tooltip: L10n.of(context)!.edit,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final val = await showCupertinoModalPopup<PrivateMessageView>(
|
final val = await showCupertinoModalPopup<PrivateMessageView>(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -383,8 +384,8 @@ class PrivateMessageTile extends HookWidget {
|
||||||
delayedLoading: deleteDelayed,
|
delayedLoading: deleteDelayed,
|
||||||
icon: deleted.value ? Icons.restore : Icons.delete,
|
icon: deleted.value ? Icons.restore : Icons.delete,
|
||||||
tooltip: deleted.value
|
tooltip: deleted.value
|
||||||
? L10n.of(context).restore
|
? L10n.of(context)!.restore
|
||||||
: L10n.of(context).delete,
|
: L10n.of(context)!.delete,
|
||||||
onPressed: handleDelete,
|
onPressed: handleDelete,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -30,9 +30,8 @@ class InstancePage extends HookWidget {
|
||||||
final Future<FullSiteView> siteFuture;
|
final Future<FullSiteView> siteFuture;
|
||||||
final Future<List<CommunityView>> communitiesFuture;
|
final Future<List<CommunityView>> communitiesFuture;
|
||||||
|
|
||||||
InstancePage({@required this.instanceHost})
|
InstancePage({required this.instanceHost})
|
||||||
: assert(instanceHost != null),
|
: siteFuture = LemmyApiV3(instanceHost).run(const GetSite()),
|
||||||
siteFuture = LemmyApiV3(instanceHost).run(const GetSite()),
|
|
||||||
communitiesFuture = LemmyApiV3(instanceHost).run(const ListCommunities(
|
communitiesFuture = LemmyApiV3(instanceHost).run(const ListCommunities(
|
||||||
type: PostListingType.local, sort: SortType.hot, limit: 6));
|
type: PostListingType.local, sort: SortType.hot, limit: 6));
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ class InstancePage extends HookWidget {
|
||||||
final accStore = useAccountsStore();
|
final accStore = useAccountsStore();
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
|
|
||||||
if (!siteSnap.hasData) {
|
if (!siteSnap.hasData || siteSnap.data!.siteView == null) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(),
|
appBar: AppBar(),
|
||||||
body: Center(
|
body: Center(
|
||||||
|
@ -57,7 +56,9 @@ class InstancePage extends HookWidget {
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text('ERROR: ${siteSnap.error}'),
|
child: Text('ERROR: ${siteSnap.error}'),
|
||||||
)
|
)
|
||||||
] else
|
] else if (siteSnap.data!.siteView == null)
|
||||||
|
const Text('ERROR')
|
||||||
|
else
|
||||||
const CircularProgressIndicator(semanticsLabel: 'loading')
|
const CircularProgressIndicator(semanticsLabel: 'loading')
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -65,7 +66,8 @@ class InstancePage extends HookWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final site = siteSnap.data;
|
final site = siteSnap.data!;
|
||||||
|
final siteView = site.siteView!;
|
||||||
|
|
||||||
void _share() => share('https://$instanceHost', context: context);
|
void _share() => share('https://$instanceHost', context: context);
|
||||||
|
|
||||||
|
@ -110,7 +112,7 @@ class InstancePage extends HookWidget {
|
||||||
fade: true,
|
fade: true,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
child: Text(
|
child: Text(
|
||||||
site.siteView.site.name,
|
siteView.site.name,
|
||||||
style: TextStyle(color: colorOnCard),
|
style: TextStyle(color: colorOnCard),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -122,11 +124,11 @@ class InstancePage extends HookWidget {
|
||||||
],
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Stack(children: [
|
background: Stack(children: [
|
||||||
if (site.siteView.site.banner != null)
|
if (siteView.site.banner != null)
|
||||||
FullscreenableImage(
|
FullscreenableImage(
|
||||||
url: site.siteView.site.banner,
|
url: siteView.site.banner!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: site.siteView.site.banner,
|
imageUrl: siteView.site.banner!,
|
||||||
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -136,20 +138,20 @@ class InstancePage extends HookWidget {
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 40),
|
padding: const EdgeInsets.only(top: 40),
|
||||||
child: site.siteView.site.icon == null
|
child: siteView.site.icon == null
|
||||||
? const SizedBox(height: 100, width: 100)
|
? const SizedBox(height: 100, width: 100)
|
||||||
: FullscreenableImage(
|
: FullscreenableImage(
|
||||||
url: site.siteView.site.icon,
|
url: siteView.site.icon!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
imageUrl: site.siteView.site.icon,
|
imageUrl: siteView.site.icon!,
|
||||||
errorWidget: (_, __, ___) =>
|
errorWidget: (_, __, ___) =>
|
||||||
const Icon(Icons.warning),
|
const Icon(Icons.warning),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(site.siteView.site.name,
|
Text(siteView.site.name,
|
||||||
style: theme.textTheme.headline6),
|
style: theme.textTheme.headline6),
|
||||||
Text(instanceHost, style: theme.textTheme.caption)
|
Text(instanceHost, style: theme.textTheme.caption)
|
||||||
],
|
],
|
||||||
|
@ -164,8 +166,8 @@ class InstancePage extends HookWidget {
|
||||||
color: theme.cardColor,
|
color: theme.cardColor,
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: L10n.of(context).posts),
|
Tab(text: L10n.of(context)!.posts),
|
||||||
Tab(text: L10n.of(context).comments),
|
Tab(text: L10n.of(context)!.comments),
|
||||||
const Tab(text: 'About'),
|
const Tab(text: 'About'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -212,17 +214,18 @@ class _AboutTab extends HookWidget {
|
||||||
final Future<List<CommunityView>> communitiesFuture;
|
final Future<List<CommunityView>> communitiesFuture;
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
|
|
||||||
const _AboutTab(this.site,
|
const _AboutTab(
|
||||||
{@required this.communitiesFuture, @required this.instanceHost})
|
this.site, {
|
||||||
: assert(communitiesFuture != null),
|
required this.communitiesFuture,
|
||||||
assert(instanceHost != null);
|
required this.instanceHost,
|
||||||
|
});
|
||||||
|
|
||||||
void goToBannedUsers(BuildContext context) {
|
void goToBannedUsers(BuildContext context) {
|
||||||
goTo(
|
goTo(
|
||||||
context,
|
context,
|
||||||
(_) => UsersListPage(
|
(_) => UsersListPage(
|
||||||
users: site.banned.reversed.toList(),
|
users: site.banned.reversed.toList(),
|
||||||
title: L10n.of(context).banned_users,
|
title: L10n.of(context)!.banned_users,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -246,24 +249,38 @@ class _AboutTab extends HookWidget {
|
||||||
auth: accStore.defaultTokenFor(instanceHost)?.raw,
|
auth: accStore.defaultTokenFor(instanceHost)?.raw,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: 'Communities of ${site.siteView.site.name}',
|
title: 'Communities of ${site.siteView?.site.name}',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final siteView = site.siteView;
|
||||||
|
|
||||||
|
if (siteView == null) {
|
||||||
|
return const SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
child: Text('error'),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
if (siteView.site.description != null) ...[
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
|
Padding(
|
||||||
child: MarkdownText(
|
padding:
|
||||||
site.siteView.site.description,
|
const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
|
||||||
instanceHost: instanceHost,
|
child: MarkdownText(
|
||||||
|
siteView.site.description!,
|
||||||
|
instanceHost: instanceHost,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const _Divider(),
|
||||||
const _Divider(),
|
],
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 32,
|
height: 32,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
@ -271,17 +288,18 @@ class _AboutTab extends HookWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
children: [
|
children: [
|
||||||
Chip(
|
Chip(
|
||||||
label: Text(L10n.of(context)
|
label: Text(L10n.of(context)!
|
||||||
.number_of_users_online(site.online))),
|
.number_of_users_online(site.online))),
|
||||||
Chip(
|
Chip(
|
||||||
label: Text(L10n.of(context)
|
label: Text(L10n.of(context)!
|
||||||
.number_of_users(site.siteView.counts.users))),
|
.number_of_users(site.siteView!.counts.users))),
|
||||||
Chip(
|
Chip(
|
||||||
label: Text(
|
label: Text(
|
||||||
'${site.siteView.counts.communities} communities')),
|
'${site.siteView!.counts.communities} communities')),
|
||||||
Chip(label: Text('${site.siteView.counts.posts} posts')),
|
Chip(label: Text('${site.siteView!.counts.posts} posts')),
|
||||||
Chip(
|
Chip(
|
||||||
label: Text('${site.siteView.counts.comments} comments')),
|
label:
|
||||||
|
Text('${site.siteView!.counts.comments} comments')),
|
||||||
].spaced(8),
|
].spaced(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -290,12 +308,12 @@ class _AboutTab extends HookWidget {
|
||||||
title: Center(
|
title: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Trending communities:',
|
'Trending communities:',
|
||||||
style: theme.textTheme.headline6.copyWith(fontSize: 18),
|
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (commSnap.hasData)
|
if (commSnap.hasData)
|
||||||
for (final c in commSnap.data)
|
for (final c in commSnap.data!)
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => goToCommunity.byId(
|
onTap: () => goToCommunity.byId(
|
||||||
context, c.instanceHost, c.community.id),
|
context, c.instanceHost, c.community.id),
|
||||||
|
@ -321,7 +339,7 @@ class _AboutTab extends HookWidget {
|
||||||
title: Center(
|
title: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Admins:',
|
'Admins:',
|
||||||
style: theme.textTheme.headline6.copyWith(fontSize: 18),
|
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -329,18 +347,18 @@ class _AboutTab extends HookWidget {
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(u.person.originDisplayName),
|
title: Text(u.person.originDisplayName),
|
||||||
subtitle: u.person.bio != null
|
subtitle: u.person.bio != null
|
||||||
? MarkdownText(u.person.bio, instanceHost: instanceHost)
|
? MarkdownText(u.person.bio!, instanceHost: instanceHost)
|
||||||
: null,
|
: null,
|
||||||
onTap: () => goToUser.fromPersonSafe(context, u.person),
|
onTap: () => goToUser.fromPersonSafe(context, u.person),
|
||||||
leading: Avatar(url: u.person.avatar),
|
leading: Avatar(url: u.person.avatar),
|
||||||
),
|
),
|
||||||
const _Divider(),
|
const _Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Center(child: Text(L10n.of(context).banned_users)),
|
title: Center(child: Text(L10n.of(context)!.banned_users)),
|
||||||
onTap: () => goToBannedUsers(context),
|
onTap: () => goToBannedUsers(context),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Center(child: Text(L10n.of(context).modlog)),
|
title: Center(child: Text(L10n.of(context)!.modlog)),
|
||||||
onTap: () => goTo(
|
onTap: () => goTo(
|
||||||
context,
|
context,
|
||||||
(context) => ModlogPage.forInstance(instanceHost: instanceHost),
|
(context) => ModlogPage.forInstance(instanceHost: instanceHost),
|
||||||
|
|
|
@ -21,10 +21,7 @@ class ManageAccountPage extends HookWidget {
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
final String username;
|
final String username;
|
||||||
|
|
||||||
const ManageAccountPage(
|
const ManageAccountPage({required this.instanceHost, required this.username});
|
||||||
{@required this.instanceHost, @required this.username})
|
|
||||||
: assert(instanceHost != null),
|
|
||||||
assert(username != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -32,9 +29,9 @@ class ManageAccountPage extends HookWidget {
|
||||||
|
|
||||||
final userFuture = useMemoized(() async {
|
final userFuture = useMemoized(() async {
|
||||||
final site = await LemmyApiV3(instanceHost).run(
|
final site = await LemmyApiV3(instanceHost).run(
|
||||||
GetSite(auth: accountStore.tokenFor(instanceHost, username).raw));
|
GetSite(auth: accountStore.tokenFor(instanceHost, username)!.raw));
|
||||||
|
|
||||||
return site.myUser;
|
return site.myUser!;
|
||||||
});
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -51,7 +48,7 @@ class ManageAccountPage extends HookWidget {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
return _ManageAccount(user: userSnap.data);
|
return _ManageAccount(user: userSnap.data!);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -59,9 +56,7 @@ class ManageAccountPage extends HookWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ManageAccount extends HookWidget {
|
class _ManageAccount extends HookWidget {
|
||||||
const _ManageAccount({Key key, @required this.user})
|
const _ManageAccount({Key? key, required this.user}) : super(key: key);
|
||||||
: assert(user != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final LocalUserSettingsView user;
|
final LocalUserSettingsView user;
|
||||||
|
|
||||||
|
@ -91,12 +86,12 @@ class _ManageAccount extends HookWidget {
|
||||||
final newPasswordVerifyController = useTextEditingController();
|
final newPasswordVerifyController = useTextEditingController();
|
||||||
final oldPasswordController = useTextEditingController();
|
final oldPasswordController = useTextEditingController();
|
||||||
|
|
||||||
final informAcceptedAvatarRef = useRef<VoidCallback>(null);
|
final informAcceptedAvatarRef = useRef<VoidCallback?>(null);
|
||||||
final informAcceptedBannerRef = useRef<VoidCallback>(null);
|
final informAcceptedBannerRef = useRef<VoidCallback?>(null);
|
||||||
|
|
||||||
final deleteAccountPasswordController = useTextEditingController();
|
final deleteAccountPasswordController = useTextEditingController();
|
||||||
|
|
||||||
final token = accountsStore.tokenFor(user.instanceHost, user.person.name);
|
final token = accountsStore.tokenFor(user.instanceHost, user.person.name)!;
|
||||||
|
|
||||||
handleSubmit() async {
|
handleSubmit() async {
|
||||||
saveDelayedLoading.start();
|
saveDelayedLoading.start();
|
||||||
|
@ -132,8 +127,8 @@ class _ManageAccount extends HookWidget {
|
||||||
email: emailController.text.isEmpty ? null : emailController.text,
|
email: emailController.text.isEmpty ? null : emailController.text,
|
||||||
));
|
));
|
||||||
|
|
||||||
informAcceptedAvatarRef.current();
|
informAcceptedAvatarRef.current?.call();
|
||||||
informAcceptedBannerRef.current();
|
informAcceptedBannerRef.current?.call();
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||||
content: Text('User settings saved'),
|
content: Text('User settings saved'),
|
||||||
|
@ -152,28 +147,28 @@ class _ManageAccount extends HookWidget {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: Text(
|
title: Text(
|
||||||
'${L10n.of(context).delete_account} @${user.instanceHost}@${user.person.name}'),
|
'${L10n.of(context)!.delete_account} @${user.instanceHost}@${user.person.name}'),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(L10n.of(context).delete_account_confirm),
|
Text(L10n.of(context)!.delete_account_confirm),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
TextField(
|
TextField(
|
||||||
controller: deleteAccountPasswordController,
|
controller: deleteAccountPasswordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration:
|
decoration:
|
||||||
InputDecoration(hintText: L10n.of(context).password),
|
InputDecoration(hintText: L10n.of(context)!.password),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
child: Text(L10n.of(context).no),
|
child: Text(L10n.of(context)!.no),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: Text(L10n.of(context).yes),
|
child: Text(L10n.of(context)!.yes),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -209,7 +204,7 @@ class _ManageAccount extends HookWidget {
|
||||||
children: [
|
children: [
|
||||||
_ImagePicker(
|
_ImagePicker(
|
||||||
user: user,
|
user: user,
|
||||||
name: L10n.of(context).avatar,
|
name: L10n.of(context)!.avatar,
|
||||||
initialUrl: avatar.current,
|
initialUrl: avatar.current,
|
||||||
onChange: (value) => avatar.current = value,
|
onChange: (value) => avatar.current = value,
|
||||||
informAcceptedRef: informAcceptedAvatarRef,
|
informAcceptedRef: informAcceptedAvatarRef,
|
||||||
|
@ -217,42 +212,42 @@ class _ManageAccount extends HookWidget {
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_ImagePicker(
|
_ImagePicker(
|
||||||
user: user,
|
user: user,
|
||||||
name: L10n.of(context).banner,
|
name: L10n.of(context)!.banner,
|
||||||
initialUrl: banner.current,
|
initialUrl: banner.current,
|
||||||
onChange: (value) => banner.current = value,
|
onChange: (value) => banner.current = value,
|
||||||
informAcceptedRef: informAcceptedBannerRef,
|
informAcceptedRef: informAcceptedBannerRef,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(L10n.of(context).display_name, style: theme.textTheme.headline6),
|
Text(L10n.of(context)!.display_name, style: theme.textTheme.headline6),
|
||||||
TextField(controller: displayNameController),
|
TextField(controller: displayNameController),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(L10n.of(context).bio, style: theme.textTheme.headline6),
|
Text(L10n.of(context)!.bio, style: theme.textTheme.headline6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: bioController,
|
controller: bioController,
|
||||||
minLines: 4,
|
minLines: 4,
|
||||||
maxLines: 10,
|
maxLines: 10,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(L10n.of(context).email, style: theme.textTheme.headline6),
|
Text(L10n.of(context)!.email, style: theme.textTheme.headline6),
|
||||||
TextField(controller: emailController),
|
TextField(controller: emailController),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(L10n.of(context).matrix_user, style: theme.textTheme.headline6),
|
Text(L10n.of(context)!.matrix_user, style: theme.textTheme.headline6),
|
||||||
TextField(controller: matrixUserController),
|
TextField(controller: matrixUserController),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(L10n.of(context).new_password, style: theme.textTheme.headline6),
|
Text(L10n.of(context)!.new_password, style: theme.textTheme.headline6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: newPasswordController,
|
controller: newPasswordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(L10n.of(context).verify_password,
|
Text(L10n.of(context)!.verify_password,
|
||||||
style: theme.textTheme.headline6),
|
style: theme.textTheme.headline6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: newPasswordVerifyController,
|
controller: newPasswordVerifyController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(L10n.of(context).old_password, style: theme.textTheme.headline6),
|
Text(L10n.of(context)!.old_password, style: theme.textTheme.headline6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: oldPasswordController,
|
controller: oldPasswordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
|
@ -264,7 +259,7 @@ class _ManageAccount extends HookWidget {
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(L10n.of(context).type),
|
Text(L10n.of(context)!.type),
|
||||||
const Text(
|
const Text(
|
||||||
'This has currently no effect on lemmur',
|
'This has currently no effect on lemmur',
|
||||||
style: TextStyle(fontSize: 10),
|
style: TextStyle(fontSize: 10),
|
||||||
|
@ -290,7 +285,7 @@ class _ManageAccount extends HookWidget {
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(L10n.of(context).sort_type),
|
Text(L10n.of(context)!.sort_type),
|
||||||
const Text(
|
const Text(
|
||||||
'This has currently no effect on lemmur',
|
'This has currently no effect on lemmur',
|
||||||
style: TextStyle(fontSize: 10),
|
style: TextStyle(fontSize: 10),
|
||||||
|
@ -308,24 +303,30 @@ class _ManageAccount extends HookWidget {
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
value: showAvatars.value,
|
value: showAvatars.value,
|
||||||
onChanged: (checked) => showAvatars.value = checked,
|
onChanged: (checked) {
|
||||||
title: Text(L10n.of(context).show_avatars),
|
if (checked != null) showAvatars.value = checked;
|
||||||
|
},
|
||||||
|
title: Text(L10n.of(context)!.show_avatars),
|
||||||
subtitle: const Text('This has currently no effect on lemmur'),
|
subtitle: const Text('This has currently no effect on lemmur'),
|
||||||
dense: true,
|
dense: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
value: showNsfw.value,
|
value: showNsfw.value,
|
||||||
onChanged: (checked) => showNsfw.value = checked,
|
onChanged: (checked) {
|
||||||
title: Text(L10n.of(context).show_nsfw),
|
if (checked != null) showNsfw.value = checked;
|
||||||
|
},
|
||||||
|
title: Text(L10n.of(context)!.show_nsfw),
|
||||||
subtitle: const Text('This has currently no effect on lemmur'),
|
subtitle: const Text('This has currently no effect on lemmur'),
|
||||||
dense: true,
|
dense: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
value: sendNotificationsToEmail.value,
|
value: sendNotificationsToEmail.value,
|
||||||
onChanged: (checked) => sendNotificationsToEmail.value = checked,
|
onChanged: (checked) {
|
||||||
title: Text(L10n.of(context).send_notifications_to_email),
|
if (checked != null) sendNotificationsToEmail.value = checked;
|
||||||
|
},
|
||||||
|
title: Text(L10n.of(context)!.send_notifications_to_email),
|
||||||
dense: true,
|
dense: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
@ -337,7 +338,7 @@ class _ManageAccount extends HookWidget {
|
||||||
height: 20,
|
height: 20,
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
: Text(L10n.of(context).save),
|
: Text(L10n.of(context)!.save),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
@ -345,7 +346,7 @@ class _ManageAccount extends HookWidget {
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
primary: Colors.red,
|
primary: Colors.red,
|
||||||
),
|
),
|
||||||
child: Text(L10n.of(context).delete_account.toUpperCase()),
|
child: Text(L10n.of(context)!.delete_account.toUpperCase()),
|
||||||
),
|
),
|
||||||
const BottomSafe(),
|
const BottomSafe(),
|
||||||
],
|
],
|
||||||
|
@ -356,25 +357,23 @@ class _ManageAccount extends HookWidget {
|
||||||
/// Picker and cleanuper for local images uploaded to pictrs
|
/// Picker and cleanuper for local images uploaded to pictrs
|
||||||
class _ImagePicker extends HookWidget {
|
class _ImagePicker extends HookWidget {
|
||||||
final String name;
|
final String name;
|
||||||
final String initialUrl;
|
final String? initialUrl;
|
||||||
final LocalUserSettingsView user;
|
final LocalUserSettingsView user;
|
||||||
final ValueChanged<String> onChange;
|
final ValueChanged<String?>? onChange;
|
||||||
|
|
||||||
/// _ImagePicker will set the ref to a callback that can inform _ImagePicker
|
/// _ImagePicker will set the ref to a callback that can inform _ImagePicker
|
||||||
/// that the current picture is accepted
|
/// that the current picture is accepted
|
||||||
/// and should no longer allow for deletion of it
|
/// and should no longer allow for deletion of it
|
||||||
final Ref<VoidCallback> informAcceptedRef;
|
final Ref<VoidCallback?> informAcceptedRef;
|
||||||
|
|
||||||
const _ImagePicker({
|
const _ImagePicker({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.initialUrl,
|
required this.initialUrl,
|
||||||
@required this.name,
|
required this.name,
|
||||||
@required this.user,
|
required this.user,
|
||||||
@required this.onChange,
|
required this.onChange,
|
||||||
@required this.informAcceptedRef,
|
required this.informAcceptedRef,
|
||||||
}) : assert(name != null),
|
}) : super(key: key);
|
||||||
assert(user != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -383,7 +382,7 @@ class _ImagePicker extends HookWidget {
|
||||||
final initialUrl = useRef(this.initialUrl);
|
final initialUrl = useRef(this.initialUrl);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final url = useState(initialUrl.current);
|
final url = useState(initialUrl.current);
|
||||||
final pictrsDeleteToken = useState<PictrsUploadFile>(null);
|
final pictrsDeleteToken = useState<PictrsUploadFile?>(null);
|
||||||
|
|
||||||
final imagePicker = useImagePicker();
|
final imagePicker = useImagePicker();
|
||||||
final accountsStore = useAccountsStore();
|
final accountsStore = useAccountsStore();
|
||||||
|
@ -398,14 +397,15 @@ class _ImagePicker extends HookWidget {
|
||||||
|
|
||||||
final upload = await PictrsApi(user.instanceHost).upload(
|
final upload = await PictrsApi(user.instanceHost).upload(
|
||||||
filePath: pic.path,
|
filePath: pic.path,
|
||||||
auth:
|
auth: accountsStore
|
||||||
accountsStore.tokenFor(user.instanceHost, user.person.name).raw,
|
.tokenFor(user.instanceHost, user.person.name)!
|
||||||
|
.raw,
|
||||||
);
|
);
|
||||||
pictrsDeleteToken.value = upload.files[0];
|
pictrsDeleteToken.value = upload.files[0];
|
||||||
url.value =
|
url.value =
|
||||||
pathToPictrs(user.instanceHost, pictrsDeleteToken.value.file);
|
pathToPictrs(user.instanceHost, pictrsDeleteToken.value!.file);
|
||||||
|
|
||||||
onChange?.call(url.value);
|
onChange?.call(url.value!);
|
||||||
}
|
}
|
||||||
} on Exception catch (_) {
|
} on Exception catch (_) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
@ -415,10 +415,11 @@ class _ImagePicker extends HookWidget {
|
||||||
delayedLoading.cancel();
|
delayedLoading.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
removePicture({bool updateState = true}) {
|
removePicture({
|
||||||
PictrsApi(user.instanceHost)
|
bool updateState = true,
|
||||||
.delete(pictrsDeleteToken.value)
|
required PictrsUploadFile pictrsToken,
|
||||||
.catchError((_) {});
|
}) {
|
||||||
|
PictrsApi(user.instanceHost).delete(pictrsToken).catchError((_) {});
|
||||||
|
|
||||||
if (updateState) {
|
if (updateState) {
|
||||||
pictrsDeleteToken.value = null;
|
pictrsDeleteToken.value = null;
|
||||||
|
@ -436,7 +437,10 @@ class _ImagePicker extends HookWidget {
|
||||||
return () {
|
return () {
|
||||||
// remove picture from pictrs when exiting
|
// remove picture from pictrs when exiting
|
||||||
if (pictrsDeleteToken.value != null) {
|
if (pictrsDeleteToken.value != null) {
|
||||||
removePicture(updateState: false);
|
removePicture(
|
||||||
|
updateState: false,
|
||||||
|
pictrsToken: pictrsDeleteToken.value!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -462,13 +466,14 @@ class _ImagePicker extends HookWidget {
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: removePicture,
|
onPressed: () =>
|
||||||
|
removePicture(pictrsToken: pictrsDeleteToken.value!),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (url.value != null)
|
if (url.value != null)
|
||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: url.value,
|
imageUrl: url.value!,
|
||||||
errorWidget: (_, __, ___) => const Icon(Icons.error),
|
errorWidget: (_, __, ___) => const Icon(Icons.error),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -26,7 +26,7 @@ class MediaViewPage extends HookWidget {
|
||||||
final isDragging = useState(false);
|
final isDragging = useState(false);
|
||||||
|
|
||||||
final offset = useState(Offset.zero);
|
final offset = useState(Offset.zero);
|
||||||
final prevOffset = usePrevious(offset.value);
|
final prevOffset = usePrevious(offset.value) ?? Offset.zero;
|
||||||
|
|
||||||
notImplemented() {
|
notImplemented() {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||||
|
|
|
@ -12,22 +12,18 @@ import '../widgets/bottom_safe.dart';
|
||||||
class ModlogPage extends HookWidget {
|
class ModlogPage extends HookWidget {
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
final String name;
|
final String name;
|
||||||
final int communityId;
|
final int? communityId;
|
||||||
|
|
||||||
const ModlogPage.forInstance({
|
const ModlogPage.forInstance({
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
}) : assert(instanceHost != null),
|
}) : communityId = null,
|
||||||
communityId = null,
|
|
||||||
name = instanceHost;
|
name = instanceHost;
|
||||||
|
|
||||||
const ModlogPage.forCommunity({
|
const ModlogPage.forCommunity({
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
@required this.communityId,
|
required int this.communityId,
|
||||||
@required String communityName,
|
required String communityName,
|
||||||
}) : assert(instanceHost != null),
|
}) : name = '!$communityName';
|
||||||
assert(communityId != null),
|
|
||||||
assert(communityName != null),
|
|
||||||
name = '!$communityName';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -65,7 +61,7 @@ class ModlogPage extends HookWidget {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text('Error: ${snapshot.error?.toString()}'));
|
child: Text('Error: ${snapshot.error?.toString()}'));
|
||||||
}
|
}
|
||||||
final modlog = snapshot.data;
|
final modlog = snapshot.requireData;
|
||||||
|
|
||||||
if (modlog.added.length +
|
if (modlog.added.length +
|
||||||
modlog.addedToCommunity.length +
|
modlog.addedToCommunity.length +
|
||||||
|
@ -78,7 +74,7 @@ class ModlogPage extends HookWidget {
|
||||||
modlog.stickiedPosts.length ==
|
modlog.stickiedPosts.length ==
|
||||||
0) {
|
0) {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance
|
||||||
.addPostFrameCallback((_) => isDone.value = true);
|
?.addPostFrameCallback((_) => isDone.value = true);
|
||||||
|
|
||||||
return const Center(child: Text('no more logs to show'));
|
return const Center(child: Text('no more logs to show'));
|
||||||
}
|
}
|
||||||
|
@ -118,9 +114,7 @@ class ModlogPage extends HookWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ModlogTable extends StatelessWidget {
|
class _ModlogTable extends StatelessWidget {
|
||||||
const _ModlogTable({Key key, @required this.modlog})
|
const _ModlogTable({Key? key, required this.modlog}) : super(key: key);
|
||||||
: assert(modlog != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final Modlog modlog;
|
final Modlog modlog;
|
||||||
|
|
||||||
|
@ -179,7 +173,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (removedPost.modRemovePost.removed)
|
if (removedPost.modRemovePost.removed ?? false)
|
||||||
const TextSpan(text: 'removed')
|
const TextSpan(text: 'removed')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'restored'),
|
const TextSpan(text: 'restored'),
|
||||||
|
@ -205,7 +199,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (lockedPost.modLockPost.locked)
|
if (lockedPost.modLockPost.locked ?? false)
|
||||||
const TextSpan(text: 'locked')
|
const TextSpan(text: 'locked')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'unlocked'),
|
const TextSpan(text: 'unlocked'),
|
||||||
|
@ -231,7 +225,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (stickiedPost.modStickyPost.stickied)
|
if (stickiedPost.modStickyPost.stickied ?? false)
|
||||||
const TextSpan(text: 'stickied')
|
const TextSpan(text: 'stickied')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'unstickied'),
|
const TextSpan(text: 'unstickied'),
|
||||||
|
@ -257,7 +251,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (removedComment.modRemoveComment.removed)
|
if (removedComment.modRemoveComment.removed ?? false)
|
||||||
const TextSpan(text: 'removed')
|
const TextSpan(text: 'removed')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'restored'),
|
const TextSpan(text: 'restored'),
|
||||||
|
@ -286,7 +280,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (removedCommunity.modRemoveCommunity.removed)
|
if (removedCommunity.modRemoveCommunity.removed ?? false)
|
||||||
const TextSpan(text: 'removed')
|
const TextSpan(text: 'removed')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'restored'),
|
const TextSpan(text: 'restored'),
|
||||||
|
@ -303,7 +297,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (bannedFromCommunity.modBanFromCommunity.banned)
|
if (bannedFromCommunity.modBanFromCommunity.banned ?? false)
|
||||||
const TextSpan(text: 'banned ')
|
const TextSpan(text: 'banned ')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'unbanned '),
|
const TextSpan(text: 'unbanned '),
|
||||||
|
@ -321,7 +315,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (banned.modBan.banned)
|
if (banned.modBan.banned ?? false)
|
||||||
const TextSpan(text: 'banned ')
|
const TextSpan(text: 'banned ')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'unbanned '),
|
const TextSpan(text: 'unbanned '),
|
||||||
|
@ -337,7 +331,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (addedToCommunity.modAddCommunity.removed)
|
if (addedToCommunity.modAddCommunity.removed ?? false)
|
||||||
const TextSpan(text: 'removed ')
|
const TextSpan(text: 'removed ')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'appointed '),
|
const TextSpan(text: 'appointed '),
|
||||||
|
@ -355,7 +349,7 @@ class _ModlogTable extends StatelessWidget {
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (added.modAdd.removed)
|
if (added.modAdd.removed ?? false)
|
||||||
const TextSpan(text: 'removed ')
|
const TextSpan(text: 'removed ')
|
||||||
else
|
else
|
||||||
const TextSpan(text: 'apointed '),
|
const TextSpan(text: 'apointed '),
|
||||||
|
@ -402,16 +396,14 @@ class _ModlogEntry {
|
||||||
final DateTime when;
|
final DateTime when;
|
||||||
final PersonSafe mod;
|
final PersonSafe mod;
|
||||||
final Widget action;
|
final Widget action;
|
||||||
final String reason;
|
final String? reason;
|
||||||
|
|
||||||
const _ModlogEntry({
|
const _ModlogEntry({
|
||||||
@required this.when,
|
required this.when,
|
||||||
@required this.mod,
|
required this.mod,
|
||||||
@required this.action,
|
required this.action,
|
||||||
this.reason,
|
this.reason,
|
||||||
}) : assert(when != null),
|
});
|
||||||
assert(mod != null),
|
|
||||||
assert(action != null);
|
|
||||||
|
|
||||||
_ModlogEntry.fromModRemovePostView(
|
_ModlogEntry.fromModRemovePostView(
|
||||||
ModRemovePostView removedPost,
|
ModRemovePostView removedPost,
|
||||||
|
@ -539,7 +531,7 @@ class _ModlogEntry {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
action,
|
action,
|
||||||
if (reason == null) const Center(child: Text('-')) else Text(reason),
|
if (reason == null) const Center(child: Text('-')) else Text(reason!),
|
||||||
]
|
]
|
||||||
.map(
|
.map(
|
||||||
(widget) => Padding(
|
(widget) => Padding(
|
||||||
|
|
|
@ -80,7 +80,7 @@ class UserProfileTab extends HookWidget {
|
||||||
Text(
|
Text(
|
||||||
// TODO: fix overflow issues
|
// TODO: fix overflow issues
|
||||||
displayValue,
|
displayValue,
|
||||||
style: theme.appBarTheme.textTheme.headline6,
|
style: theme.appBarTheme.textTheme?.headline6,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
),
|
),
|
||||||
const Icon(Icons.expand_more),
|
const Icon(Icons.expand_more),
|
||||||
|
@ -91,8 +91,8 @@ class UserProfileTab extends HookWidget {
|
||||||
actions: actions,
|
actions: actions,
|
||||||
),
|
),
|
||||||
body: UserProfile(
|
body: UserProfile(
|
||||||
userId: accountsStore.defaultToken.payload.sub,
|
userId: accountsStore.defaultToken!.payload.sub,
|
||||||
instanceHost: accountsStore.defaultInstanceHost,
|
instanceHost: accountsStore.defaultInstanceHost!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,24 @@ class SavedPage extends HookWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accountStore = useAccountsStore();
|
final accountStore = useAccountsStore();
|
||||||
|
|
||||||
|
if (accountStore.hasNoAccount) {
|
||||||
|
Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: const Center(
|
||||||
|
child: Text('no account found'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 2,
|
length: 2,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(L10n.of(context).saved),
|
title: Text(L10n.of(context)!.saved),
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: L10n.of(context).posts),
|
Tab(text: L10n.of(context)!.posts),
|
||||||
Tab(text: L10n.of(context).comments),
|
Tab(text: L10n.of(context)!.comments),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -29,27 +38,27 @@ class SavedPage extends HookWidget {
|
||||||
children: [
|
children: [
|
||||||
InfinitePostList(
|
InfinitePostList(
|
||||||
fetcher: (page, batchSize, sortType) =>
|
fetcher: (page, batchSize, sortType) =>
|
||||||
LemmyApiV3(accountStore.defaultInstanceHost).run(
|
LemmyApiV3(accountStore.defaultInstanceHost!).run(
|
||||||
GetPosts(
|
GetPosts(
|
||||||
type: PostListingType.all,
|
type: PostListingType.all,
|
||||||
sort: sortType,
|
sort: sortType,
|
||||||
savedOnly: true,
|
savedOnly: true,
|
||||||
page: page,
|
page: page,
|
||||||
limit: batchSize,
|
limit: batchSize,
|
||||||
auth: accountStore.defaultToken.raw,
|
auth: accountStore.defaultToken!.raw,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
InfiniteCommentList(
|
InfiniteCommentList(
|
||||||
fetcher: (page, batchSize, sortType) =>
|
fetcher: (page, batchSize, sortType) =>
|
||||||
LemmyApiV3(accountStore.defaultInstanceHost).run(
|
LemmyApiV3(accountStore.defaultInstanceHost!).run(
|
||||||
GetComments(
|
GetComments(
|
||||||
type: CommentListingType.all,
|
type: CommentListingType.all,
|
||||||
sort: sortType,
|
sort: sortType,
|
||||||
savedOnly: true,
|
savedOnly: true,
|
||||||
page: page,
|
page: page,
|
||||||
limit: batchSize,
|
limit: batchSize,
|
||||||
auth: accountStore.defaultToken.raw,
|
auth: accountStore.defaultToken!.raw,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -15,11 +15,9 @@ class SearchResultsPage extends HookWidget {
|
||||||
final String query;
|
final String query;
|
||||||
|
|
||||||
SearchResultsPage({
|
SearchResultsPage({
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
@required this.query,
|
required this.query,
|
||||||
}) : assert(instanceHost != null),
|
}) : assert(instanceHost.isNotEmpty),
|
||||||
assert(query != null),
|
|
||||||
assert(instanceHost.isNotEmpty),
|
|
||||||
assert(query.isNotEmpty);
|
assert(query.isNotEmpty);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,10 +29,10 @@ class SearchResultsPage extends HookWidget {
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: L10n.of(context).posts),
|
Tab(text: L10n.of(context)!.posts),
|
||||||
Tab(text: L10n.of(context).comments),
|
Tab(text: L10n.of(context)!.comments),
|
||||||
Tab(text: L10n.of(context).users),
|
Tab(text: L10n.of(context)!.users),
|
||||||
Tab(text: L10n.of(context).communities),
|
Tab(text: L10n.of(context)!.communities),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -68,18 +66,16 @@ class _SearchResultsList extends HookWidget {
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
|
|
||||||
const _SearchResultsList({
|
const _SearchResultsList({
|
||||||
@required this.type,
|
required this.type,
|
||||||
@required this.query,
|
required this.query,
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
}) : assert(type != null),
|
});
|
||||||
assert(query != null),
|
|
||||||
assert(instanceHost != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accStore = useAccountsStore();
|
final accStore = useAccountsStore();
|
||||||
|
|
||||||
return SortableInfiniteList(
|
return SortableInfiniteList<Object>(
|
||||||
fetcher: (page, batchSize, sort) async {
|
fetcher: (page, batchSize, sort) async {
|
||||||
final s = await LemmyApiV3(instanceHost).run(Search(
|
final s = await LemmyApiV3(instanceHost).run(Search(
|
||||||
q: query,
|
q: query,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
@ -18,7 +19,7 @@ class SearchTab extends HookWidget {
|
||||||
final accStore = useAccountsStore();
|
final accStore = useAccountsStore();
|
||||||
// null if there are no added instances
|
// null if there are no added instances
|
||||||
final instanceHost = useState(
|
final instanceHost = useState(
|
||||||
accStore.instances.firstWhere((_) => true, orElse: () => null),
|
accStore.instances.firstWhereOrNull((_) => true),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (instanceHost.value == null) {
|
if (instanceHost.value == null) {
|
||||||
|
@ -29,17 +30,18 @@ class SearchTab extends HookWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(),
|
appBar: AppBar(),
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onTapDown: (_) => primaryFocus.unfocus(),
|
onTapDown: (_) => primaryFocus?.unfocus(),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: searchInputController,
|
controller: searchInputController,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
decoration: InputDecoration(hintText: L10n.of(context).search),
|
decoration: InputDecoration(hintText: L10n.of(context)!.search),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Row(
|
Row(
|
||||||
|
@ -52,7 +54,7 @@ class SearchTab extends HookWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RadioPicker<String>(
|
child: RadioPicker<String>(
|
||||||
values: accStore.instances.toList(),
|
values: accStore.instances.toList(),
|
||||||
groupValue: instanceHost.value,
|
groupValue: instanceHost.value!,
|
||||||
onChanged: (value) => instanceHost.value = value,
|
onChanged: (value) => instanceHost.value = value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -63,10 +65,10 @@ class SearchTab extends HookWidget {
|
||||||
onPressed: () => goTo(
|
onPressed: () => goTo(
|
||||||
context,
|
context,
|
||||||
(c) => SearchResultsPage(
|
(c) => SearchResultsPage(
|
||||||
instanceHost: instanceHost.value,
|
instanceHost: instanceHost.value!,
|
||||||
query: searchInputController.text,
|
query: searchInputController.text,
|
||||||
)),
|
)),
|
||||||
child: Text(L10n.of(context).search),
|
child: Text(L10n.of(context)!.search),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -21,7 +21,7 @@ class SettingsPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) => Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(L10n.of(context).settings),
|
title: Text(L10n.of(context)!.settings),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
@ -66,7 +66,7 @@ class AppearanceConfigPage extends HookWidget {
|
||||||
title: Text(describeEnum(theme)),
|
title: Text(describeEnum(theme)),
|
||||||
groupValue: configStore.theme,
|
groupValue: configStore.theme,
|
||||||
onChanged: (selected) {
|
onChanged: (selected) {
|
||||||
configStore.theme = selected;
|
if (selected != null) configStore.theme = selected;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
|
@ -79,7 +79,7 @@ class AppearanceConfigPage extends HookWidget {
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const _SectionHeading('General'),
|
const _SectionHeading('General'),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.of(context).language),
|
title: Text(L10n.of(context)!.language),
|
||||||
trailing: SizedBox(
|
trailing: SizedBox(
|
||||||
width: 120,
|
width: 120,
|
||||||
child: RadioPicker<Locale>(
|
child: RadioPicker<Locale>(
|
||||||
|
@ -117,11 +117,11 @@ class AccountsConfigPage extends HookWidget {
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
child: Text(L10n.of(context).no),
|
child: Text(L10n.of(context)!.no),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: Text(L10n.of(context).yes),
|
child: Text(L10n.of(context)!.yes),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -142,11 +142,11 @@ class AccountsConfigPage extends HookWidget {
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
child: Text(L10n.of(context).no),
|
child: Text(L10n.of(context)!.no),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: Text(L10n.of(context).yes),
|
child: Text(L10n.of(context)!.yes),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -301,7 +301,7 @@ class _SectionHeading extends StatelessWidget {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 20),
|
padding: const EdgeInsets.only(left: 20),
|
||||||
child: Text(text.toUpperCase(),
|
child: Text(text.toUpperCase(),
|
||||||
style: theme.textTheme.subtitle2.copyWith(color: theme.accentColor)),
|
style: theme.textTheme.subtitle2?.copyWith(color: theme.accentColor)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,20 +10,16 @@ import 'write_message.dart';
|
||||||
|
|
||||||
/// Page showing posts, comments, and general info about a user.
|
/// Page showing posts, comments, and general info about a user.
|
||||||
class UserPage extends HookWidget {
|
class UserPage extends HookWidget {
|
||||||
final int userId;
|
final int? userId;
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
final Future<FullPersonView> _userDetails;
|
final Future<FullPersonView> _userDetails;
|
||||||
|
|
||||||
UserPage({@required this.userId, @required this.instanceHost})
|
UserPage({required this.userId, required this.instanceHost})
|
||||||
: assert(userId != null),
|
: _userDetails = LemmyApiV3(instanceHost).run(GetPersonDetails(
|
||||||
assert(instanceHost != null),
|
|
||||||
_userDetails = LemmyApiV3(instanceHost).run(GetPersonDetails(
|
|
||||||
personId: userId, savedOnly: true, sort: SortType.active));
|
personId: userId, savedOnly: true, sort: SortType.active));
|
||||||
|
|
||||||
UserPage.fromName({@required this.instanceHost, @required String username})
|
UserPage.fromName({required this.instanceHost, required String username})
|
||||||
: assert(instanceHost != null),
|
: userId = null,
|
||||||
assert(username != null),
|
|
||||||
userId = null,
|
|
||||||
_userDetails = LemmyApiV3(instanceHost).run(GetPersonDetails(
|
_userDetails = LemmyApiV3(instanceHost).run(GetPersonDetails(
|
||||||
username: username, savedOnly: true, sort: SortType.active));
|
username: username, savedOnly: true, sort: SortType.active));
|
||||||
|
|
||||||
|
@ -33,7 +29,7 @@ class UserPage extends HookWidget {
|
||||||
|
|
||||||
final body = () {
|
final body = () {
|
||||||
if (userDetailsSnap.hasData) {
|
if (userDetailsSnap.hasData) {
|
||||||
return UserProfile.fromFullPersonView(userDetailsSnap.data);
|
return UserProfile.fromFullPersonView(userDetailsSnap.data!);
|
||||||
} else if (userDetailsSnap.hasError) {
|
} else if (userDetailsSnap.hasError) {
|
||||||
return const Center(child: Text('Could not find that user.'));
|
return const Center(child: Text('Could not find that user.'));
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,11 +42,11 @@ class UserPage extends HookWidget {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
actions: [
|
actions: [
|
||||||
if (userDetailsSnap.hasData) ...[
|
if (userDetailsSnap.hasData) ...[
|
||||||
SendMessageButton(userDetailsSnap.data.personView.person),
|
SendMessageButton(userDetailsSnap.data!.personView.person),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.share),
|
icon: const Icon(Icons.share),
|
||||||
onPressed: () => share(
|
onPressed: () => share(
|
||||||
userDetailsSnap.data.personView.person.actorId,
|
userDetailsSnap.data!.personView.person.actorId,
|
||||||
context: context,
|
context: context,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -11,9 +11,8 @@ class UsersListPage extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final List<PersonViewSafe> users;
|
final List<PersonViewSafe> users;
|
||||||
|
|
||||||
const UsersListPage({Key key, @required this.users, this.title})
|
const UsersListPage({Key? key, required this.users, this.title = ''})
|
||||||
: assert(users != null),
|
: super(key: key);
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -23,7 +22,7 @@ class UsersListPage extends StatelessWidget {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: theme.cardColor,
|
backgroundColor: theme.cardColor,
|
||||||
title: Text(title ?? ''),
|
title: Text(title),
|
||||||
),
|
),
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemBuilder: (context, i) => UsersListItem(user: users[i]),
|
itemBuilder: (context, i) => UsersListItem(user: users[i]),
|
||||||
|
@ -36,9 +35,7 @@ class UsersListPage extends StatelessWidget {
|
||||||
class UsersListItem extends StatelessWidget {
|
class UsersListItem extends StatelessWidget {
|
||||||
final PersonViewSafe user;
|
final PersonViewSafe user;
|
||||||
|
|
||||||
const UsersListItem({Key key, @required this.user})
|
const UsersListItem({Key? key, required this.user}) : super(key: key);
|
||||||
: assert(user != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ListTile(
|
Widget build(BuildContext context) => ListTile(
|
||||||
|
@ -47,7 +44,7 @@ class UsersListItem extends StatelessWidget {
|
||||||
? Opacity(
|
? Opacity(
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
child: MarkdownText(
|
child: MarkdownText(
|
||||||
user.person.bio,
|
user.person.bio!,
|
||||||
instanceHost: user.instanceHost,
|
instanceHost: user.instanceHost,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
|
|
||||||
import '../hooks/stores.dart';
|
import '../hooks/logged_in_action.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import '../util/extensions/api.dart';
|
import '../util/extensions/api.dart';
|
||||||
import '../widgets/markdown_mode_icon.dart';
|
import '../widgets/markdown_mode_icon.dart';
|
||||||
|
@ -14,16 +14,14 @@ class WriteMessagePage extends HookWidget {
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
|
|
||||||
/// if it's non null then this page is used for edit
|
/// if it's non null then this page is used for edit
|
||||||
final PrivateMessage privateMessage;
|
final PrivateMessage? privateMessage;
|
||||||
|
|
||||||
final bool _isEdit;
|
final bool _isEdit;
|
||||||
|
|
||||||
const WriteMessagePage.send({
|
const WriteMessagePage.send({
|
||||||
@required this.recipient,
|
required this.recipient,
|
||||||
@required this.instanceHost,
|
required this.instanceHost,
|
||||||
}) : assert(recipient != null),
|
}) : privateMessage = null,
|
||||||
assert(instanceHost != null),
|
|
||||||
privateMessage = null,
|
|
||||||
_isEdit = false;
|
_isEdit = false;
|
||||||
|
|
||||||
WriteMessagePage.edit(PrivateMessageView pmv)
|
WriteMessagePage.edit(PrivateMessageView pmv)
|
||||||
|
@ -34,22 +32,21 @@ class WriteMessagePage extends HookWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accStore = useAccountsStore();
|
|
||||||
final showFancy = useState(false);
|
final showFancy = useState(false);
|
||||||
final bodyController =
|
final bodyController =
|
||||||
useTextEditingController(text: privateMessage?.content);
|
useTextEditingController(text: privateMessage?.content);
|
||||||
final loading = useState(false);
|
final loading = useState(false);
|
||||||
|
final loggedInAction = useLoggedInAction(instanceHost);
|
||||||
|
final submit = _isEdit ? L10n.of(context)!.save : 'send';
|
||||||
|
final title = _isEdit ? 'Edit message' : L10n.of(context)!.send_message;
|
||||||
|
|
||||||
final submit = _isEdit ? L10n.of(context).save : 'send';
|
handleSubmit(Jwt token) async {
|
||||||
final title = _isEdit ? 'Edit message' : L10n.of(context).send_message;
|
|
||||||
|
|
||||||
handleSubmit() async {
|
|
||||||
if (_isEdit) {
|
if (_isEdit) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
final msg = await LemmyApiV3(instanceHost).run(EditPrivateMessage(
|
final msg = await LemmyApiV3(instanceHost).run(EditPrivateMessage(
|
||||||
auth: accStore.defaultTokenFor(instanceHost)?.raw,
|
auth: token.raw,
|
||||||
privateMessageId: privateMessage.id,
|
privateMessageId: privateMessage!.id,
|
||||||
content: bodyController.text,
|
content: bodyController.text,
|
||||||
));
|
));
|
||||||
Navigator.of(context).pop(msg);
|
Navigator.of(context).pop(msg);
|
||||||
|
@ -65,7 +62,7 @@ class WriteMessagePage extends HookWidget {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
await LemmyApiV3(instanceHost).run(CreatePrivateMessage(
|
await LemmyApiV3(instanceHost).run(CreatePrivateMessage(
|
||||||
auth: accStore.defaultTokenFor(instanceHost)?.raw,
|
auth: token.raw,
|
||||||
content: bodyController.text,
|
content: bodyController.text,
|
||||||
recipientId: recipient.id,
|
recipientId: recipient.id,
|
||||||
));
|
));
|
||||||
|
@ -124,7 +121,7 @@ class WriteMessagePage extends HookWidget {
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: loading.value ? () {} : handleSubmit,
|
onPressed: loading.value ? () {} : loggedInAction(handleSubmit),
|
||||||
child: loading.value
|
child: loading.value
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
|
|
|
@ -20,18 +20,18 @@ class AccountsStore extends ChangeNotifier {
|
||||||
/// `tokens['instanceHost']['username']`
|
/// `tokens['instanceHost']['username']`
|
||||||
@protected
|
@protected
|
||||||
@JsonKey(defaultValue: {'lemmy.ml': {}})
|
@JsonKey(defaultValue: {'lemmy.ml': {}})
|
||||||
Map<String, Map<String, Jwt>> tokens;
|
late Map<String, Map<String, Jwt>> tokens;
|
||||||
|
|
||||||
/// default account for a given instance
|
/// default account for a given instance
|
||||||
/// map where keys are instanceHosts and values are usernames
|
/// map where keys are instanceHosts and values are usernames
|
||||||
@protected
|
@protected
|
||||||
@JsonKey(defaultValue: {})
|
@JsonKey(defaultValue: {})
|
||||||
Map<String, String> defaultAccounts;
|
late Map<String, String> defaultAccounts;
|
||||||
|
|
||||||
/// default account for the app
|
/// default account for the app
|
||||||
/// It is in a form of `username@instanceHost`
|
/// It is in a form of `username@instanceHost`
|
||||||
@protected
|
@protected
|
||||||
String defaultAccount;
|
String? defaultAccount;
|
||||||
|
|
||||||
static Future<AccountsStore> load() async {
|
static Future<AccountsStore> load() async {
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
|
@ -63,8 +63,8 @@ class AccountsStore extends ChangeNotifier {
|
||||||
.toList()
|
.toList()
|
||||||
.forEach(defaultAccounts.remove);
|
.forEach(defaultAccounts.remove);
|
||||||
if (defaultAccount != null) {
|
if (defaultAccount != null) {
|
||||||
final instance = defaultAccount.split('@')[1];
|
final instance = defaultAccount!.split('@')[1];
|
||||||
final username = defaultAccount.split('@')[0];
|
final username = defaultAccount!.split('@')[0];
|
||||||
// if instance or username doesn't exist, remove
|
// if instance or username doesn't exist, remove
|
||||||
if (!instances.contains(instance) ||
|
if (!instances.contains(instance) ||
|
||||||
!usernamesFor(instance).contains(username)) {
|
!usernamesFor(instance).contains(username)) {
|
||||||
|
@ -97,23 +97,23 @@ class AccountsStore extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String get defaultUsername {
|
String? get defaultUsername {
|
||||||
if (defaultAccount == null) {
|
// if (defaultAccount == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return defaultAccount.split('@')[0];
|
return defaultAccount?.split('@')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
String get defaultInstanceHost {
|
String? get defaultInstanceHost {
|
||||||
if (defaultAccount == null) {
|
// if (defaultAccount == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return defaultAccount.split('@')[1];
|
return defaultAccount?.split('@')[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
String defaultUsernameFor(String instanceHost) {
|
String? defaultUsernameFor(String instanceHost) {
|
||||||
if (isAnonymousFor(instanceHost)) {
|
if (isAnonymousFor(instanceHost)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -121,29 +121,29 @@ class AccountsStore extends ChangeNotifier {
|
||||||
return defaultAccounts[instanceHost];
|
return defaultAccounts[instanceHost];
|
||||||
}
|
}
|
||||||
|
|
||||||
Jwt get defaultToken {
|
Jwt? get defaultToken {
|
||||||
if (defaultAccount == null) {
|
if (defaultAccount == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final userTag = defaultAccount.split('@');
|
final userTag = defaultAccount!.split('@');
|
||||||
return tokens[userTag[1]][userTag[0]];
|
return tokens[userTag[1]]?[userTag[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
Jwt defaultTokenFor(String instanceHost) {
|
Jwt? defaultTokenFor(String instanceHost) {
|
||||||
if (isAnonymousFor(instanceHost)) {
|
if (isAnonymousFor(instanceHost)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokens[instanceHost][defaultAccounts[instanceHost]];
|
return tokens[instanceHost]?[defaultAccounts[instanceHost]];
|
||||||
}
|
}
|
||||||
|
|
||||||
Jwt tokenFor(String instanceHost, String username) {
|
Jwt? tokenFor(String instanceHost, String username) {
|
||||||
if (!usernamesFor(instanceHost).contains(username)) {
|
if (!usernamesFor(instanceHost).contains(username)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokens[instanceHost][username];
|
return tokens[instanceHost]?[username];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// sets globally default account
|
/// sets globally default account
|
||||||
|
@ -169,7 +169,7 @@ class AccountsStore extends ChangeNotifier {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokens[instanceHost].isEmpty;
|
return tokens[instanceHost]!.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `true` if no added instance has an account assigned to it
|
/// `true` if no added instance has an account assigned to it
|
||||||
|
@ -182,7 +182,7 @@ class AccountsStore extends ChangeNotifier {
|
||||||
|
|
||||||
/// Usernames that are assigned to a given instance
|
/// Usernames that are assigned to a given instance
|
||||||
Iterable<String> usernamesFor(String instanceHost) =>
|
Iterable<String> usernamesFor(String instanceHost) =>
|
||||||
tokens[instanceHost].keys;
|
tokens[instanceHost]?.keys ?? const Iterable.empty();
|
||||||
|
|
||||||
/// adds a new account
|
/// adds a new account
|
||||||
/// if it's the first account ever the account is
|
/// if it's the first account ever the account is
|
||||||
|
@ -203,10 +203,11 @@ class AccountsStore extends ChangeNotifier {
|
||||||
usernameOrEmail: usernameOrEmail,
|
usernameOrEmail: usernameOrEmail,
|
||||||
password: password,
|
password: password,
|
||||||
));
|
));
|
||||||
final userData =
|
final userData = await lemmy
|
||||||
await lemmy.run(GetSite(auth: token.raw)).then((value) => value.myUser);
|
.run(GetSite(auth: token.raw))
|
||||||
|
.then((value) => value.myUser!);
|
||||||
|
|
||||||
tokens[instanceHost][userData.person.name] = token.copyWith(
|
tokens[instanceHost]![userData.person.name] = token.copyWith(
|
||||||
payload: token.payload.copyWith(sub: userData.person.id),
|
payload: token.payload.copyWith(sub: userData.person.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -252,7 +253,11 @@ class AccountsStore extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeAccount(String instanceHost, String username) async {
|
Future<void> removeAccount(String instanceHost, String username) async {
|
||||||
tokens[instanceHost].remove(username);
|
if (!tokens.containsKey(instanceHost)) {
|
||||||
|
throw Exception("instance doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens[instanceHost]!.remove(username);
|
||||||
|
|
||||||
await _assignDefaultAccounts();
|
await _assignDefaultAccounts();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
@ -15,7 +15,7 @@ class ConfigStore extends ChangeNotifier {
|
||||||
static const prefsKey = 'v1:ConfigStore';
|
static const prefsKey = 'v1:ConfigStore';
|
||||||
static final _prefs = SharedPreferences.getInstance();
|
static final _prefs = SharedPreferences.getInstance();
|
||||||
|
|
||||||
ThemeMode _theme;
|
late ThemeMode _theme;
|
||||||
@JsonKey(defaultValue: ThemeMode.system)
|
@JsonKey(defaultValue: ThemeMode.system)
|
||||||
ThemeMode get theme => _theme;
|
ThemeMode get theme => _theme;
|
||||||
set theme(ThemeMode theme) {
|
set theme(ThemeMode theme) {
|
||||||
|
@ -24,7 +24,7 @@ class ConfigStore extends ChangeNotifier {
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _amoledDarkMode;
|
late bool _amoledDarkMode;
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool get amoledDarkMode => _amoledDarkMode;
|
bool get amoledDarkMode => _amoledDarkMode;
|
||||||
set amoledDarkMode(bool amoledDarkMode) {
|
set amoledDarkMode(bool amoledDarkMode) {
|
||||||
|
@ -33,11 +33,11 @@ class ConfigStore extends ChangeNotifier {
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Locale _locale;
|
Locale? _locale;
|
||||||
// default value is set in the `load` method because json_serializable does
|
// default value is set in the `load` method because json_serializable does
|
||||||
// not accept non-literals as constant values
|
// not accept non-literals as constant values
|
||||||
@JsonKey(fromJson: LocaleSerde.fromJson, toJson: LocaleSerde.toJson)
|
@JsonKey(fromJson: LocaleSerde.fromJson, toJson: LocaleSerde.toJson)
|
||||||
Locale get locale => _locale;
|
Locale get locale => _locale ?? const Locale('en');
|
||||||
set locale(Locale locale) {
|
set locale(Locale locale) {
|
||||||
_locale = locale;
|
_locale = locale;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -49,7 +49,7 @@ class ConfigStore extends ChangeNotifier {
|
||||||
|
|
||||||
return _$ConfigStoreFromJson(
|
return _$ConfigStoreFromJson(
|
||||||
jsonDecode(prefs.getString(prefsKey) ?? '{}') as Map<String, dynamic>,
|
jsonDecode(prefs.getString(prefsKey) ?? '{}') as Map<String, dynamic>,
|
||||||
).._locale ??= const Locale('en');
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
|
|
@ -24,7 +24,7 @@ ThemeData _themeFactory({bool dark = false, bool amoled = false}) {
|
||||||
iconTheme: IconThemeData(color: theme.colorScheme.onSurface),
|
iconTheme: IconThemeData(color: theme.colorScheme.onSurface),
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
headline6: theme.textTheme.headline6
|
headline6: theme.textTheme.headline6
|
||||||
.copyWith(fontSize: 20, fontWeight: FontWeight.w500),
|
?.copyWith(fontSize: 20, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tabBarTheme: TabBarTheme(
|
tabBarTheme: TabBarTheme(
|
||||||
|
|
|
@ -13,9 +13,9 @@ import 'util/goto.dart';
|
||||||
/// Decides where does a link link to. Either somewhere in-app:
|
/// Decides where does a link link to. Either somewhere in-app:
|
||||||
/// opens the correct page, or outside of the app: opens in a browser
|
/// opens the correct page, or outside of the app: opens in a browser
|
||||||
Future<void> linkLauncher({
|
Future<void> linkLauncher({
|
||||||
@required BuildContext context,
|
required BuildContext context,
|
||||||
@required String url,
|
required String url,
|
||||||
@required String instanceHost,
|
required String instanceHost,
|
||||||
}) async {
|
}) async {
|
||||||
push(Widget Function() builder) {
|
push(Widget Function() builder) {
|
||||||
goTo(context, (c) => builder());
|
goTo(context, (c) => builder());
|
||||||
|
@ -46,8 +46,8 @@ Future<void> linkLauncher({
|
||||||
final matchedInstance = match?.group(1);
|
final matchedInstance = match?.group(1);
|
||||||
final rest = match?.group(2);
|
final rest = match?.group(2);
|
||||||
|
|
||||||
if (matchedInstance != null && instances.any((e) => e == match.group(1))) {
|
if (matchedInstance != null && instances.any((e) => e == match?.group(1))) {
|
||||||
if (rest.isEmpty || rest == '/') {
|
if (rest == null || rest.isEmpty || rest == '/') {
|
||||||
return push(() => InstancePage(instanceHost: matchedInstance));
|
return push(() => InstancePage(instanceHost: matchedInstance));
|
||||||
}
|
}
|
||||||
final split = rest.split('/');
|
final split = rest.split('/');
|
||||||
|
@ -107,7 +107,7 @@ Future<void> linkLauncher({
|
||||||
|
|
||||||
Future<void> openInBrowser(String url) async {
|
Future<void> openInBrowser(String url) async {
|
||||||
if (await ul.canLaunch(url)) {
|
if (await ul.canLaunch(url)) {
|
||||||
return await ul.launch(url);
|
await ul.launch(url);
|
||||||
} else {
|
} else {
|
||||||
throw Exception();
|
throw Exception();
|
||||||
// TODO: handle opening links to stuff in app
|
// TODO: handle opening links to stuff in app
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
|
|
||||||
|
@ -7,28 +6,24 @@ import '../hooks/delayed_loading.dart';
|
||||||
/// Executes an API action that uses [DelayedLoading], has a try-catch
|
/// Executes an API action that uses [DelayedLoading], has a try-catch
|
||||||
/// that displays a [SnackBar] when the action fails
|
/// that displays a [SnackBar] when the action fails
|
||||||
Future<void> delayedAction<T>({
|
Future<void> delayedAction<T>({
|
||||||
@required BuildContext context,
|
required BuildContext context,
|
||||||
@required DelayedLoading delayedLoading,
|
required DelayedLoading delayedLoading,
|
||||||
@required String instanceHost,
|
required String instanceHost,
|
||||||
@required LemmyApiQuery<T> query,
|
required LemmyApiQuery<T> query,
|
||||||
Function(T) onSuccess,
|
void Function(T)? onSuccess,
|
||||||
Function(T) cleanup,
|
void Function(T?)? cleanup,
|
||||||
}) async {
|
}) async {
|
||||||
assert(delayedLoading != null);
|
T? val;
|
||||||
assert(instanceHost != null);
|
|
||||||
assert(query != null);
|
|
||||||
assert(context != null);
|
|
||||||
|
|
||||||
T val;
|
|
||||||
try {
|
try {
|
||||||
delayedLoading.start();
|
delayedLoading.start();
|
||||||
val = await LemmyApiV3(instanceHost).run<T>(query);
|
val = await LemmyApiV3(instanceHost).run<T>(query);
|
||||||
onSuccess?.call(val);
|
onSuccess?.call(val as T);
|
||||||
// ignore: avoid_catches_without_on_clauses
|
// ignore: avoid_catches_without_on_clauses
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.showSnackBar(SnackBar(content: Text(e.toString())));
|
.showSnackBar(SnackBar(content: Text(e.toString())));
|
||||||
|
} finally {
|
||||||
|
cleanup?.call(val);
|
||||||
|
delayedLoading.cancel();
|
||||||
}
|
}
|
||||||
cleanup?.call(val);
|
|
||||||
delayedLoading.cancel();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,9 @@ extension CommunityDisplayNames on CommunitySafe {
|
||||||
|
|
||||||
extension UserDisplayNames on PersonSafe {
|
extension UserDisplayNames on PersonSafe {
|
||||||
String get displayName {
|
String get displayName {
|
||||||
if (preferredUsername != null && preferredUsername.isNotEmpty) {
|
final prefName = preferredUsername;
|
||||||
return preferredUsername;
|
if (prefName != null && prefName.isNotEmpty) {
|
||||||
|
return prefName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '@$name';
|
return '@$name';
|
||||||
|
|
|
@ -6,11 +6,10 @@ import 'package:share/share.dart';
|
||||||
/// on platforms that do not support native sharing
|
/// on platforms that do not support native sharing
|
||||||
Future<void> share(
|
Future<void> share(
|
||||||
String text, {
|
String text, {
|
||||||
String subject,
|
String? subject,
|
||||||
Rect sharePositionOrigin,
|
Rect? sharePositionOrigin,
|
||||||
@required BuildContext context,
|
required BuildContext context,
|
||||||
}) async {
|
}) async {
|
||||||
assert(context != null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await Share.share(
|
return await Share.share(
|
||||||
|
|
|
@ -23,7 +23,12 @@ class AboutTile extends HookWidget {
|
||||||
return await PackageInfo.fromPlatform();
|
return await PackageInfo.fromPlatform();
|
||||||
} on MissingPluginException {
|
} on MissingPluginException {
|
||||||
// when we get here it means PackageInfo does not support this platform
|
// when we get here it means PackageInfo does not support this platform
|
||||||
return PackageInfo(version: '');
|
return PackageInfo(
|
||||||
|
appName: 'lemmur',
|
||||||
|
packageName: '',
|
||||||
|
version: '',
|
||||||
|
buildNumber: '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -31,13 +36,16 @@ class AboutTile extends HookWidget {
|
||||||
final changelogSnap =
|
final changelogSnap =
|
||||||
useMemoFuture(() => assetBundle.loadString('CHANGELOG.md'));
|
useMemoFuture(() => assetBundle.loadString('CHANGELOG.md'));
|
||||||
|
|
||||||
if (!packageInfoSnap.hasData || !changelogSnap.hasData) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
final packageInfo = packageInfoSnap.data;
|
final packageInfo = packageInfoSnap.data;
|
||||||
final changelog = changelogSnap.data;
|
final changelog = changelogSnap.data;
|
||||||
|
|
||||||
|
if (!packageInfoSnap.hasData ||
|
||||||
|
!changelogSnap.hasData ||
|
||||||
|
packageInfo == null ||
|
||||||
|
changelog == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
return AboutListTile(
|
return AboutListTile(
|
||||||
icon: const Icon(Icons.info),
|
icon: const Icon(Icons.info),
|
||||||
aboutBoxChildren: [
|
aboutBoxChildren: [
|
||||||
|
|
|
@ -6,13 +6,13 @@ import 'package:flutter/material.dart';
|
||||||
/// Can be disabled with `noBlank`
|
/// Can be disabled with `noBlank`
|
||||||
class Avatar extends StatelessWidget {
|
class Avatar extends StatelessWidget {
|
||||||
const Avatar({
|
const Avatar({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.url,
|
required this.url,
|
||||||
this.radius = 25,
|
this.radius = 25,
|
||||||
this.noBlank = false,
|
this.noBlank = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String url;
|
final String? url;
|
||||||
final double radius;
|
final double radius;
|
||||||
final bool noBlank;
|
final bool noBlank;
|
||||||
|
|
||||||
|
@ -27,7 +27,9 @@ class Avatar extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
if (url == null) {
|
final imageUrl = url;
|
||||||
|
|
||||||
|
if (imageUrl == null) {
|
||||||
return blankWidget;
|
return blankWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ class Avatar extends StatelessWidget {
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
height: radius * 2,
|
height: radius * 2,
|
||||||
width: radius * 2,
|
width: radius * 2,
|
||||||
imageUrl: url,
|
imageUrl: imageUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorWidget: (_, __, ___) => blankWidget,
|
errorWidget: (_, __, ___) => blankWidget,
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,16 +3,15 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||||
|
|
||||||
/// Should be spawned with a [showBottomModal], not routed to.
|
/// Should be spawned with a [showBottomModal], not routed to.
|
||||||
class BottomModal extends StatelessWidget {
|
class BottomModal extends StatelessWidget {
|
||||||
final String title;
|
final String? title;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const BottomModal({
|
const BottomModal({
|
||||||
this.title,
|
this.title,
|
||||||
this.padding = EdgeInsets.zero,
|
this.padding = EdgeInsets.zero,
|
||||||
@required this.child,
|
required this.child,
|
||||||
}) : assert(padding != null),
|
});
|
||||||
assert(child != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -40,7 +39,7 @@ class BottomModal extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 70),
|
padding: const EdgeInsets.only(left: 70),
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title!,
|
||||||
style: theme.textTheme.subtitle2,
|
style: theme.textTheme.subtitle2,
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
||||||
|
@ -65,10 +64,10 @@ class BottomModal extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for showing a [BottomModal]
|
/// Helper function for showing a [BottomModal]
|
||||||
Future<T> showBottomModal<T>({
|
Future<T?> showBottomModal<T>({
|
||||||
@required BuildContext context,
|
required BuildContext context,
|
||||||
@required WidgetBuilder builder,
|
required WidgetBuilder builder,
|
||||||
String title,
|
String? title,
|
||||||
EdgeInsets padding = EdgeInsets.zero,
|
EdgeInsets padding = EdgeInsets.zero,
|
||||||
}) =>
|
}) =>
|
||||||
showCustomModalBottomSheet<T>(
|
showCustomModalBottomSheet<T>(
|
||||||
|
|
|
@ -33,7 +33,7 @@ class CommentWidget extends HookWidget {
|
||||||
final bool wasVoted;
|
final bool wasVoted;
|
||||||
final bool canBeMarkedAsRead;
|
final bool canBeMarkedAsRead;
|
||||||
final bool hideOnRead;
|
final bool hideOnRead;
|
||||||
final int userMentionId;
|
final int? userMentionId;
|
||||||
|
|
||||||
static const colors = [
|
static const colors = [
|
||||||
Colors.pink,
|
Colors.pink,
|
||||||
|
@ -95,10 +95,7 @@ class CommentWidget extends HookWidget {
|
||||||
final accStore = useAccountsStore();
|
final accStore = useAccountsStore();
|
||||||
|
|
||||||
final isMine = commentTree.comment.comment.creatorId ==
|
final isMine = commentTree.comment.comment.creatorId ==
|
||||||
accStore
|
accStore.defaultTokenFor(commentTree.comment.instanceHost)?.payload.sub;
|
||||||
.defaultTokenFor(commentTree.comment.instanceHost)
|
|
||||||
?.payload
|
|
||||||
?.sub;
|
|
||||||
final selectable = useState(false);
|
final selectable = useState(false);
|
||||||
final showRaw = useState(false);
|
final showRaw = useState(false);
|
||||||
final collapsed = useState(false);
|
final collapsed = useState(false);
|
||||||
|
@ -221,7 +218,7 @@ class CommentWidget extends HookWidget {
|
||||||
if (isDeleted.value) {
|
if (isDeleted.value) {
|
||||||
return Flexible(
|
return Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context).deleted_by_creator,
|
L10n.of(context)!.deleted_by_creator,
|
||||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -294,7 +291,7 @@ class CommentWidget extends HookWidget {
|
||||||
icon: Icons.more_horiz,
|
icon: Icons.more_horiz,
|
||||||
onPressed: () => _openMoreMenu(context),
|
onPressed: () => _openMoreMenu(context),
|
||||||
delayedLoading: delayedDeletion,
|
delayedLoading: delayedDeletion,
|
||||||
tooltip: L10n.of(context).more,
|
tooltip: L10n.of(context)!.more,
|
||||||
),
|
),
|
||||||
_SaveComment(commentTree.comment),
|
_SaveComment(commentTree.comment),
|
||||||
if (!isDeleted.value &&
|
if (!isDeleted.value &&
|
||||||
|
@ -303,7 +300,7 @@ class CommentWidget extends HookWidget {
|
||||||
TileAction(
|
TileAction(
|
||||||
icon: Icons.reply,
|
icon: Icons.reply,
|
||||||
onPressed: loggedInAction((_) => reply()),
|
onPressed: loggedInAction((_) => reply()),
|
||||||
tooltip: L10n.of(context).reply,
|
tooltip: L10n.of(context)!.reply,
|
||||||
),
|
),
|
||||||
TileAction(
|
TileAction(
|
||||||
icon: Icons.arrow_upward,
|
icon: Icons.arrow_upward,
|
||||||
|
@ -369,7 +366,7 @@ class CommentWidget extends HookWidget {
|
||||||
if (isOP) _CommentTag('OP', theme.accentColor),
|
if (isOP) _CommentTag('OP', theme.accentColor),
|
||||||
if (comment.creator.admin)
|
if (comment.creator.admin)
|
||||||
_CommentTag(
|
_CommentTag(
|
||||||
L10n.of(context).admin.toUpperCase(),
|
L10n.of(context)!.admin.toUpperCase(),
|
||||||
theme.accentColor,
|
theme.accentColor,
|
||||||
),
|
),
|
||||||
if (comment.creator.banned)
|
if (comment.creator.banned)
|
||||||
|
@ -417,14 +414,14 @@ class CommentWidget extends HookWidget {
|
||||||
|
|
||||||
class _MarkAsRead extends HookWidget {
|
class _MarkAsRead extends HookWidget {
|
||||||
final CommentView commentView;
|
final CommentView commentView;
|
||||||
final ValueChanged<bool> onChanged;
|
final ValueChanged<bool>? onChanged;
|
||||||
final int userMentionId;
|
final int? userMentionId;
|
||||||
|
|
||||||
const _MarkAsRead(
|
const _MarkAsRead(
|
||||||
this.commentView, {
|
this.commentView, {
|
||||||
@required this.onChanged,
|
required this.onChanged,
|
||||||
@required this.userMentionId,
|
required this.userMentionId,
|
||||||
}) : assert(commentView != null);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -432,18 +429,19 @@ class _MarkAsRead extends HookWidget {
|
||||||
|
|
||||||
final comment = commentView.comment;
|
final comment = commentView.comment;
|
||||||
final instanceHost = commentView.instanceHost;
|
final instanceHost = commentView.instanceHost;
|
||||||
|
final loggedInAction = useLoggedInAction(instanceHost);
|
||||||
|
|
||||||
final isRead = useState(comment.read);
|
final isRead = useState(comment.read);
|
||||||
final delayedRead = useDelayedLoading();
|
final delayedRead = useDelayedLoading();
|
||||||
|
|
||||||
Future<void> handleMarkAsSeen() => delayedAction<FullCommentView>(
|
Future<void> handleMarkAsSeen(Jwt token) => delayedAction<FullCommentView>(
|
||||||
context: context,
|
context: context,
|
||||||
delayedLoading: delayedRead,
|
delayedLoading: delayedRead,
|
||||||
instanceHost: instanceHost,
|
instanceHost: instanceHost,
|
||||||
query: MarkCommentAsRead(
|
query: MarkCommentAsRead(
|
||||||
commentId: comment.id,
|
commentId: comment.id,
|
||||||
read: !isRead.value,
|
read: !isRead.value,
|
||||||
auth: accStore.defaultTokenFor(instanceHost)?.raw,
|
auth: token.raw,
|
||||||
),
|
),
|
||||||
onSuccess: (val) {
|
onSuccess: (val) {
|
||||||
isRead.value = val.commentView.comment.read;
|
isRead.value = val.commentView.comment.read;
|
||||||
|
@ -451,14 +449,15 @@ class _MarkAsRead extends HookWidget {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> handleMarkMentionAsSeen() => delayedAction<PersonMentionView>(
|
Future<void> handleMarkMentionAsSeen(Jwt token) =>
|
||||||
|
delayedAction<PersonMentionView>(
|
||||||
context: context,
|
context: context,
|
||||||
delayedLoading: delayedRead,
|
delayedLoading: delayedRead,
|
||||||
instanceHost: instanceHost,
|
instanceHost: instanceHost,
|
||||||
query: MarkPersonMentionAsRead(
|
query: MarkPersonMentionAsRead(
|
||||||
personMentionId: userMentionId,
|
personMentionId: userMentionId!,
|
||||||
read: !isRead.value,
|
read: !isRead.value,
|
||||||
auth: accStore.defaultTokenFor(instanceHost)?.raw,
|
auth: token.raw,
|
||||||
),
|
),
|
||||||
onSuccess: (val) {
|
onSuccess: (val) {
|
||||||
isRead.value = val.personMention.read;
|
isRead.value = val.personMention.read;
|
||||||
|
@ -469,12 +468,13 @@ class _MarkAsRead extends HookWidget {
|
||||||
return TileAction(
|
return TileAction(
|
||||||
icon: Icons.check,
|
icon: Icons.check,
|
||||||
delayedLoading: delayedRead,
|
delayedLoading: delayedRead,
|
||||||
onPressed:
|
onPressed: userMentionId != null
|
||||||
userMentionId != null ? handleMarkMentionAsSeen : handleMarkAsSeen,
|
? loggedInAction(handleMarkMentionAsSeen)
|
||||||
|
: loggedInAction(handleMarkAsSeen),
|
||||||
iconColor: isRead.value ? Theme.of(context).accentColor : null,
|
iconColor: isRead.value ? Theme.of(context).accentColor : null,
|
||||||
tooltip: isRead.value
|
tooltip: isRead.value
|
||||||
? L10n.of(context).mark_as_unread
|
? L10n.of(context)!.mark_as_unread
|
||||||
: L10n.of(context).mark_as_read,
|
: L10n.of(context)!.mark_as_read,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,7 +487,7 @@ class _SaveComment extends HookWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loggedInAction = useLoggedInAction(comment.instanceHost);
|
final loggedInAction = useLoggedInAction(comment.instanceHost);
|
||||||
final isSaved = useState(comment.saved ?? false);
|
final isSaved = useState(comment.saved);
|
||||||
final delayed = useDelayedLoading();
|
final delayed = useDelayedLoading();
|
||||||
|
|
||||||
handleSave(Jwt token) => delayedAction<FullCommentView>(
|
handleSave(Jwt token) => delayedAction<FullCommentView>(
|
||||||
|
@ -529,7 +529,7 @@ class _CommentTag extends StatelessWidget {
|
||||||
child: Text(text,
|
child: Text(text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: textColorBasedOnBackground(bgColor),
|
color: textColorBasedOnBackground(bgColor),
|
||||||
fontSize: Theme.of(context).textTheme.bodyText1.fontSize - 5,
|
fontSize: Theme.of(context).textTheme.bodyText1!.fontSize! - 5,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
|
|
@ -26,7 +26,7 @@ class CommentSection extends HookWidget {
|
||||||
|
|
||||||
CommentSection(
|
CommentSection(
|
||||||
List<CommentView> rawComments, {
|
List<CommentView> rawComments, {
|
||||||
@required this.postCreatorId,
|
required this.postCreatorId,
|
||||||
this.sortType = CommentSortType.hot,
|
this.sortType = CommentSortType.hot,
|
||||||
}) : comments =
|
}) : comments =
|
||||||
CommentTree.sortList(sortType, CommentTree.fromList(rawComments)),
|
CommentTree.sortList(sortType, CommentTree.fromList(rawComments)),
|
||||||
|
@ -76,7 +76,7 @@ class CommentSection extends HookWidget {
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text((sortPairs[sorting.value][1] as String).tr(context)),
|
Text((sortPairs[sorting.value]![1] as String).tr(context)),
|
||||||
const Icon(Icons.arrow_drop_down),
|
const Icon(Icons.arrow_drop_down),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -10,9 +10,9 @@ class FullscreenableImage extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const FullscreenableImage({
|
const FullscreenableImage({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.url,
|
required this.url,
|
||||||
@required this.child,
|
required this.child,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -6,7 +6,7 @@ import '../hooks/ref.dart';
|
||||||
import 'bottom_safe.dart';
|
import 'bottom_safe.dart';
|
||||||
|
|
||||||
class InfiniteScrollController {
|
class InfiniteScrollController {
|
||||||
VoidCallback clear;
|
late VoidCallback clear;
|
||||||
|
|
||||||
InfiniteScrollController() {
|
InfiniteScrollController() {
|
||||||
usedBeforeCreation() => throw Exception(
|
usedBeforeCreation() => throw Exception(
|
||||||
|
@ -14,10 +14,6 @@ class InfiniteScrollController {
|
||||||
|
|
||||||
clear = usedBeforeCreation;
|
clear = usedBeforeCreation;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
clear = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `ListView.builder` with asynchronous data fetching and no `itemCount`
|
/// `ListView.builder` with asynchronous data fetching and no `itemCount`
|
||||||
|
@ -36,13 +32,13 @@ class InfiniteScroll<T> extends HookWidget {
|
||||||
/// is considered finished
|
/// is considered finished
|
||||||
final Future<List<T>> Function(int page, int batchSize) fetcher;
|
final Future<List<T>> Function(int page, int batchSize) fetcher;
|
||||||
|
|
||||||
final InfiniteScrollController controller;
|
final InfiniteScrollController? controller;
|
||||||
|
|
||||||
/// Widget to be added at the beginning of the list
|
/// Widget to be added at the beginning of the list
|
||||||
final Widget leading;
|
final Widget leading;
|
||||||
|
|
||||||
/// Padding for the [ListView.builder]
|
/// Padding for the [ListView.builder]
|
||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
/// Widget that will be displayed if there are no items
|
/// Widget that will be displayed if there are no items
|
||||||
final Widget noItems;
|
final Widget noItems;
|
||||||
|
@ -53,13 +49,11 @@ class InfiniteScroll<T> extends HookWidget {
|
||||||
this.padding,
|
this.padding,
|
||||||
this.loadingWidget =
|
this.loadingWidget =
|
||||||
const ListTile(title: Center(child: CircularProgressIndicator())),
|
const ListTile(title: Center(child: CircularProgressIndicator())),
|
||||||
@required this.itemBuilder,
|
required this.itemBuilder,
|
||||||
@required this.fetcher,
|
required this.fetcher,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.noItems = const SizedBox.shrink(),
|
this.noItems = const SizedBox.shrink(),
|
||||||
}) : assert(itemBuilder != null),
|
}) : assert(batchSize > 0);
|
||||||
assert(fetcher != null),
|
|
||||||
assert(batchSize > 0);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -69,7 +63,7 @@ class InfiniteScroll<T> extends HookWidget {
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (controller != null) {
|
if (controller != null) {
|
||||||
controller.clear = () {
|
controller?.clear = () {
|
||||||
data.value = [];
|
data.value = [];
|
||||||
hasMore.current = true;
|
hasMore.current = true;
|
||||||
};
|
};
|
||||||
|
@ -82,7 +76,9 @@ class InfiniteScroll<T> extends HookWidget {
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
controller.clear();
|
data.value = [];
|
||||||
|
hasMore.current = true;
|
||||||
|
|
||||||
await HapticFeedback.mediumImpact();
|
await HapticFeedback.mediumImpact();
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,13 +3,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'bottom_modal.dart';
|
import 'bottom_modal.dart';
|
||||||
|
|
||||||
void showInfoTablePopup({
|
void showInfoTablePopup({
|
||||||
@required BuildContext context,
|
required BuildContext context,
|
||||||
@required Map<String, dynamic> table,
|
required Map<String, dynamic> table,
|
||||||
String title,
|
String? title,
|
||||||
}) {
|
}) {
|
||||||
assert(context != null);
|
|
||||||
assert(table != null);
|
|
||||||
|
|
||||||
showBottomModal(
|
showBottomModal(
|
||||||
context: context,
|
context: context,
|
||||||
title: title,
|
title: title,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
/// used mostly for pages where markdown editor is used
|
/// used mostly for pages where markdown editor is used
|
||||||
///
|
///
|
||||||
/// brush icon is rotated to look similarly to build icon
|
/// brush icon is rotated to look similarly to build icon
|
||||||
Widget markdownModeIcon({@required bool fancy}) => fancy
|
Widget markdownModeIcon({required bool fancy}) => fancy
|
||||||
? const Icon(Icons.build)
|
? const Icon(Icons.build)
|
||||||
: const RotatedBox(
|
: const RotatedBox(
|
||||||
quarterTurns: 1,
|
quarterTurns: 1,
|
||||||
|
|
|
@ -15,8 +15,7 @@ class MarkdownText extends StatelessWidget {
|
||||||
final bool selectable;
|
final bool selectable;
|
||||||
|
|
||||||
const MarkdownText(this.text,
|
const MarkdownText(this.text,
|
||||||
{@required this.instanceHost, this.selectable = false})
|
{required this.instanceHost, this.selectable = false});
|
||||||
: assert(instanceHost != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -32,9 +31,10 @@ class MarkdownText extends StatelessWidget {
|
||||||
),
|
),
|
||||||
code: theme.textTheme.bodyText1
|
code: theme.textTheme.bodyText1
|
||||||
// TODO: use a font from google fonts maybe? the defaults aren't very pretty
|
// TODO: use a font from google fonts maybe? the defaults aren't very pretty
|
||||||
.copyWith(fontFamily: Platform.isIOS ? 'Courier' : 'monospace'),
|
?.copyWith(fontFamily: Platform.isIOS ? 'Courier' : 'monospace'),
|
||||||
),
|
),
|
||||||
onTapLink: (text, href, title) {
|
onTapLink: (text, href, title) {
|
||||||
|
if (href == null) return;
|
||||||
linkLauncher(context: context, url: href, instanceHost: instanceHost)
|
linkLauncher(context: context, url: href, instanceHost: instanceHost)
|
||||||
.catchError(
|
.catchError(
|
||||||
(e) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
(e) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
|
|
@ -33,7 +33,7 @@ enum MediaType {
|
||||||
none,
|
none,
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaType whatType(String url) {
|
MediaType whatType(String? url) {
|
||||||
if (url == null || url.isEmpty) return MediaType.none;
|
if (url == null || url.isEmpty) return MediaType.none;
|
||||||
|
|
||||||
// TODO: make detection more nuanced
|
// TODO: make detection more nuanced
|
||||||
|
@ -95,13 +95,13 @@ class PostWidget extends HookWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
void _openLink() => linkLauncher(
|
void _openLink(String url) =>
|
||||||
context: context, url: post.post.url, instanceHost: instanceHost);
|
linkLauncher(context: context, url: url, instanceHost: instanceHost);
|
||||||
|
|
||||||
final urlDomain = () {
|
final urlDomain = () {
|
||||||
if (whatType(post.post.url) == MediaType.none) return null;
|
if (whatType(post.post.url) == MediaType.none) return null;
|
||||||
|
|
||||||
return urlHost(post.post.url);
|
return urlHost(post.post.url!);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
/// assemble info section
|
/// assemble info section
|
||||||
|
@ -141,7 +141,7 @@ class PostWidget extends HookWidget {
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: theme.textTheme.bodyText1.color),
|
color: theme.textTheme.bodyText1?.color),
|
||||||
children: [
|
children: [
|
||||||
const TextSpan(
|
const TextSpan(
|
||||||
text: '!',
|
text: '!',
|
||||||
|
@ -179,10 +179,10 @@ class PostWidget extends HookWidget {
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: theme.textTheme.bodyText1.color),
|
color: theme.textTheme.bodyText1?.color),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: L10n.of(context).by,
|
text: L10n.of(context)!.by,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w300),
|
fontWeight: FontWeight.w300),
|
||||||
),
|
),
|
||||||
|
@ -206,7 +206,7 @@ class PostWidget extends HookWidget {
|
||||||
if (post.post.nsfw) const TextSpan(text: ' · '),
|
if (post.post.nsfw) const TextSpan(text: ' · '),
|
||||||
if (post.post.nsfw)
|
if (post.post.nsfw)
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: L10n.of(context).nsfw,
|
text: L10n.of(context)!.nsfw,
|
||||||
style:
|
style:
|
||||||
const TextStyle(color: Colors.red)),
|
const TextStyle(color: Colors.red)),
|
||||||
if (urlDomain != null)
|
if (urlDomain != null)
|
||||||
|
@ -260,13 +260,13 @@ class PostWidget extends HookWidget {
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
onTap: _openLink,
|
onTap: () => _openLink(post.post.url!),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: post.post.thumbnailUrl,
|
imageUrl: post.post.thumbnailUrl!,
|
||||||
width: 70,
|
width: 70,
|
||||||
height: 70,
|
height: 70,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
@ -297,11 +297,11 @@ class PostWidget extends HookWidget {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: _openLink,
|
onTap: () => _openLink(post.post.url!),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).iconTheme.color.withAlpha(170)),
|
color: Theme.of(context).iconTheme.color!.withAlpha(170)),
|
||||||
borderRadius: BorderRadius.circular(5)),
|
borderRadius: BorderRadius.circular(5)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
|
@ -312,7 +312,7 @@ class PostWidget extends HookWidget {
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text('$urlDomain ',
|
Text('$urlDomain ',
|
||||||
style: theme.textTheme.caption
|
style: theme.textTheme.caption
|
||||||
.apply(fontStyle: FontStyle.italic)),
|
?.apply(fontStyle: FontStyle.italic)),
|
||||||
const Icon(Icons.launch, size: 12),
|
const Icon(Icons.launch, size: 12),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -322,7 +322,7 @@ class PostWidget extends HookWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
post.post.embedTitle ?? '',
|
post.post.embedTitle ?? '',
|
||||||
style: theme.textTheme.subtitle1
|
style: theme.textTheme.subtitle1
|
||||||
.apply(fontWeightDelta: 2),
|
?.apply(fontWeightDelta: 2),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -330,12 +330,12 @@ class PostWidget extends HookWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (post.post.embedDescription != null &&
|
if (post.post.embedDescription != null &&
|
||||||
post.post.embedDescription.isNotEmpty)
|
post.post.embedDescription!.isNotEmpty)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
post.post.embedDescription,
|
post.post.embedDescription!,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -355,9 +355,9 @@ class PostWidget extends HookWidget {
|
||||||
assert(post.post.url != null);
|
assert(post.post.url != null);
|
||||||
|
|
||||||
return FullscreenableImage(
|
return FullscreenableImage(
|
||||||
url: post.post.url,
|
url: post.post.url!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: post.post.url,
|
imageUrl: post.post.url!,
|
||||||
errorWidget: (_, __, ___) => const Icon(Icons.warning),
|
errorWidget: (_, __, ___) => const Icon(Icons.warning),
|
||||||
progressIndicatorBuilder: (context, url, progress) =>
|
progressIndicatorBuilder: (context, url, progress) =>
|
||||||
CircularProgressIndicator(value: progress.progress),
|
CircularProgressIndicator(value: progress.progress),
|
||||||
|
@ -375,7 +375,7 @@ class PostWidget extends HookWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 999,
|
flex: 999,
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context).number_of_comments(post.counts.comments),
|
L10n.of(context)!.number_of_comments(post.counts.comments),
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
),
|
),
|
||||||
|
@ -411,13 +411,13 @@ class PostWidget extends HookWidget {
|
||||||
if (whatType(post.post.url) != MediaType.other &&
|
if (whatType(post.post.url) != MediaType.other &&
|
||||||
whatType(post.post.url) != MediaType.none)
|
whatType(post.post.url) != MediaType.none)
|
||||||
postImage()
|
postImage()
|
||||||
else if (post.post.url != null && post.post.url.isNotEmpty)
|
else if (post.post.url != null && post.post.url!.isNotEmpty)
|
||||||
linkPreview(),
|
linkPreview(),
|
||||||
if (post.post.body != null && fullPost)
|
if (post.post.body != null && fullPost)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: MarkdownText(
|
child: MarkdownText(
|
||||||
post.post.body,
|
post.post.body!,
|
||||||
instanceHost: instanceHost,
|
instanceHost: instanceHost,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
),
|
),
|
||||||
|
@ -446,7 +446,7 @@ class PostWidget extends HookWidget {
|
||||||
heightFactor: 0.8,
|
heightFactor: 0.8,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: MarkdownText(post.post.body,
|
child: MarkdownText(post.post.body!,
|
||||||
instanceHost: instanceHost)),
|
instanceHost: instanceHost)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -469,7 +469,7 @@ class PostWidget extends HookWidget {
|
||||||
} else {
|
} else {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: MarkdownText(post.post.body,
|
child: MarkdownText(post.post.body!,
|
||||||
instanceHost: instanceHost));
|
instanceHost: instanceHost));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -489,8 +489,7 @@ class _Voting extends HookWidget {
|
||||||
final bool wasVoted;
|
final bool wasVoted;
|
||||||
|
|
||||||
_Voting(this.post)
|
_Voting(this.post)
|
||||||
: assert(post != null),
|
: wasVoted = (post.myVote ?? VoteType.none) != VoteType.none;
|
||||||
wasVoted = (post.myVote ?? VoteType.none) != VoteType.none;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -12,8 +12,8 @@ class PostListOptions extends StatelessWidget {
|
||||||
final bool styleButton;
|
final bool styleButton;
|
||||||
|
|
||||||
const PostListOptions({
|
const PostListOptions({
|
||||||
@required this.onSortChanged,
|
required this.onSortChanged,
|
||||||
@required this.sortValue,
|
required this.sortValue,
|
||||||
this.styleButton = true,
|
this.styleButton = true,
|
||||||
}) : assert(sortValue != null);
|
}) : assert(sortValue != null);
|
||||||
|
|
||||||
|
|
|
@ -6,31 +6,29 @@ import 'bottom_modal.dart';
|
||||||
class RadioPicker<T> extends StatelessWidget {
|
class RadioPicker<T> extends StatelessWidget {
|
||||||
final List<T> values;
|
final List<T> values;
|
||||||
final T groupValue;
|
final T groupValue;
|
||||||
final ValueChanged<T> onChanged;
|
final ValueChanged<T>? onChanged;
|
||||||
|
|
||||||
/// Map a given value to a string for display
|
/// Map a given value to a string for display
|
||||||
final String Function(T) mapValueToString;
|
final String Function(T)? mapValueToString;
|
||||||
final String title;
|
final String? title;
|
||||||
|
|
||||||
/// custom button builder. When null, an OutlinedButton is used
|
/// custom button builder. When null, an OutlinedButton is used
|
||||||
final Widget Function(
|
final Widget Function(
|
||||||
BuildContext context, String displayValue, VoidCallback onPressed)
|
BuildContext context, String displayValue, VoidCallback onPressed)?
|
||||||
buttonBuilder;
|
buttonBuilder;
|
||||||
|
|
||||||
final Widget trailing;
|
final Widget? trailing;
|
||||||
|
|
||||||
const RadioPicker({
|
const RadioPicker({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.values,
|
required this.values,
|
||||||
@required this.groupValue,
|
required this.groupValue,
|
||||||
@required this.onChanged,
|
required this.onChanged,
|
||||||
this.mapValueToString,
|
this.mapValueToString,
|
||||||
this.buttonBuilder,
|
this.buttonBuilder,
|
||||||
this.title,
|
this.title,
|
||||||
this.trailing,
|
this.trailing,
|
||||||
}) : assert(values != null),
|
}) : super(key: key);
|
||||||
assert(groupValue != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -63,7 +61,7 @@ class RadioPicker<T> extends StatelessWidget {
|
||||||
title: Text(mapValueToString(value)),
|
title: Text(mapValueToString(value)),
|
||||||
onChanged: (value) => Navigator.of(context).pop(value),
|
onChanged: (value) => Navigator.of(context).pop(value),
|
||||||
),
|
),
|
||||||
if (trailing != null) trailing
|
if (trailing != null) trailing!
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,9 +14,9 @@ class RevealAfterScroll extends HookWidget {
|
||||||
final bool fade;
|
final bool fade;
|
||||||
|
|
||||||
const RevealAfterScroll({
|
const RevealAfterScroll({
|
||||||
@required this.scrollController,
|
required this.scrollController,
|
||||||
@required this.child,
|
required this.child,
|
||||||
@required this.after,
|
required this.after,
|
||||||
this.transition = 15,
|
this.transition = 15,
|
||||||
this.fade = false,
|
this.fade = false,
|
||||||
}) : assert(scrollController != null),
|
}) : assert(scrollController != null),
|
||||||
|
|
|
@ -14,7 +14,7 @@ class SavePostButton extends HookWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isSaved = useState(post.saved ?? false);
|
final isSaved = useState(post.saved);
|
||||||
final savedIcon = isSaved.value ? Icons.bookmark : Icons.bookmark_border;
|
final savedIcon = isSaved.value ? Icons.bookmark : Icons.bookmark_border;
|
||||||
final loading = useDelayedLoading();
|
final loading = useDelayedLoading();
|
||||||
final loggedInAction = useLoggedInAction(post.instanceHost);
|
final loggedInAction = useLoggedInAction(post.instanceHost);
|
||||||
|
|
|
@ -16,21 +16,19 @@ typedef FetcherWithSorting<T> = Future<List<T>> Function(
|
||||||
class SortableInfiniteList<T> extends HookWidget {
|
class SortableInfiniteList<T> extends HookWidget {
|
||||||
final FetcherWithSorting<T> fetcher;
|
final FetcherWithSorting<T> fetcher;
|
||||||
final Widget Function(T) itemBuilder;
|
final Widget Function(T) itemBuilder;
|
||||||
final InfiniteScrollController controller;
|
final InfiniteScrollController? controller;
|
||||||
final Function onStyleChange;
|
final Function? onStyleChange;
|
||||||
final Widget noItems;
|
final Widget noItems;
|
||||||
final SortType defaultSort;
|
final SortType defaultSort;
|
||||||
|
|
||||||
const SortableInfiniteList({
|
const SortableInfiniteList({
|
||||||
@required this.fetcher,
|
required this.fetcher,
|
||||||
@required this.itemBuilder,
|
required this.itemBuilder,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.onStyleChange,
|
this.onStyleChange,
|
||||||
this.noItems,
|
this.noItems = const SizedBox.shrink(),
|
||||||
this.defaultSort = SortType.active,
|
this.defaultSort = SortType.active,
|
||||||
}) : assert(fetcher != null),
|
});
|
||||||
assert(itemBuilder != null),
|
|
||||||
assert(defaultSort != null);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -62,12 +60,12 @@ class SortableInfiniteList<T> extends HookWidget {
|
||||||
|
|
||||||
class InfinitePostList extends StatelessWidget {
|
class InfinitePostList extends StatelessWidget {
|
||||||
final FetcherWithSorting<PostView> fetcher;
|
final FetcherWithSorting<PostView> fetcher;
|
||||||
final InfiniteScrollController controller;
|
final InfiniteScrollController? controller;
|
||||||
|
|
||||||
const InfinitePostList({
|
const InfinitePostList({
|
||||||
@required this.fetcher,
|
required this.fetcher,
|
||||||
this.controller,
|
this.controller,
|
||||||
}) : assert(fetcher != null);
|
});
|
||||||
|
|
||||||
Widget build(BuildContext context) => SortableInfiniteList<PostView>(
|
Widget build(BuildContext context) => SortableInfiniteList<PostView>(
|
||||||
onStyleChange: () {},
|
onStyleChange: () {},
|
||||||
|
@ -85,12 +83,12 @@ class InfinitePostList extends StatelessWidget {
|
||||||
|
|
||||||
class InfiniteCommentList extends StatelessWidget {
|
class InfiniteCommentList extends StatelessWidget {
|
||||||
final FetcherWithSorting<CommentView> fetcher;
|
final FetcherWithSorting<CommentView> fetcher;
|
||||||
final InfiniteScrollController controller;
|
final InfiniteScrollController? controller;
|
||||||
|
|
||||||
const InfiniteCommentList({
|
const InfiniteCommentList({
|
||||||
@required this.fetcher,
|
required this.fetcher,
|
||||||
this.controller,
|
this.controller,
|
||||||
}) : assert(fetcher != null);
|
});
|
||||||
|
|
||||||
Widget build(BuildContext context) => SortableInfiniteList<CommentView>(
|
Widget build(BuildContext context) => SortableInfiniteList<CommentView>(
|
||||||
itemBuilder: (comment) => CommentWidget(
|
itemBuilder: (comment) => CommentWidget(
|
||||||
|
|
|
@ -9,20 +9,17 @@ class TileAction extends StatelessWidget {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
final String tooltip;
|
final String tooltip;
|
||||||
final DelayedLoading delayedLoading;
|
final DelayedLoading? delayedLoading;
|
||||||
final Color iconColor;
|
final Color? iconColor;
|
||||||
|
|
||||||
const TileAction({
|
const TileAction({
|
||||||
Key key,
|
Key? key,
|
||||||
this.delayedLoading,
|
this.delayedLoading,
|
||||||
this.iconColor,
|
this.iconColor,
|
||||||
@required this.icon,
|
required this.icon,
|
||||||
@required this.onPressed,
|
required this.onPressed,
|
||||||
@required this.tooltip,
|
required this.tooltip,
|
||||||
}) : assert(icon != null),
|
}) : super(key: key);
|
||||||
assert(onPressed != null),
|
|
||||||
assert(tooltip != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => IconButton(
|
Widget build(BuildContext context) => IconButton(
|
||||||
|
@ -34,7 +31,7 @@ class TileAction extends StatelessWidget {
|
||||||
: Icon(
|
: Icon(
|
||||||
icon,
|
icon,
|
||||||
color: iconColor ??
|
color: iconColor ??
|
||||||
Theme.of(context).iconTheme.color.withAlpha(190),
|
Theme.of(context).iconTheme.color?.withAlpha(190),
|
||||||
),
|
),
|
||||||
splashRadius: 25,
|
splashRadius: 25,
|
||||||
onPressed: delayedLoading?.pending ?? false ? () {} : onPressed,
|
onPressed: delayedLoading?.pending ?? false ? () {} : onPressed,
|
||||||
|
|
|
@ -22,16 +22,13 @@ class UserProfile extends HookWidget {
|
||||||
final String instanceHost;
|
final String instanceHost;
|
||||||
final int userId;
|
final int userId;
|
||||||
|
|
||||||
final FullPersonView _fullUserView;
|
final FullPersonView? _fullUserView;
|
||||||
|
|
||||||
const UserProfile({@required this.userId, @required this.instanceHost})
|
const UserProfile({required this.userId, required this.instanceHost})
|
||||||
: assert(userId != null),
|
: _fullUserView = null;
|
||||||
assert(instanceHost != null),
|
|
||||||
_fullUserView = null;
|
|
||||||
|
|
||||||
UserProfile.fromFullPersonView(this._fullUserView)
|
UserProfile.fromFullPersonView(FullPersonView this._fullUserView)
|
||||||
: assert(_fullUserView != null),
|
: userId = _fullUserView.personView.person.id,
|
||||||
userId = _fullUserView.personView.person.id,
|
|
||||||
instanceHost = _fullUserView.instanceHost;
|
instanceHost = _fullUserView.instanceHost;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -62,8 +59,8 @@ class UserProfile extends HookWidget {
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final fullPersonView = userDetailsSnap.data!;
|
||||||
final userView = userDetailsSnap.data.personView;
|
final userView = fullPersonView.personView;
|
||||||
|
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 3,
|
length: 3,
|
||||||
|
@ -83,8 +80,8 @@ class UserProfile extends HookWidget {
|
||||||
color: theme.cardColor,
|
color: theme.cardColor,
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: L10n.of(context).posts),
|
Tab(text: L10n.of(context)!.posts),
|
||||||
Tab(text: L10n.of(context).comments),
|
Tab(text: L10n.of(context)!.comments),
|
||||||
const Tab(text: 'About'),
|
const Tab(text: 'About'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -119,7 +116,7 @@ class UserProfile extends HookWidget {
|
||||||
))
|
))
|
||||||
.then((val) => val.comments),
|
.then((val) => val.comments),
|
||||||
),
|
),
|
||||||
_AboutTab(userDetailsSnap.data),
|
_AboutTab(fullPersonView),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -146,9 +143,9 @@ class _UserOverview extends HookWidget {
|
||||||
if (userView.person.banner != null)
|
if (userView.person.banner != null)
|
||||||
// TODO: for some reason doesnt react to presses
|
// TODO: for some reason doesnt react to presses
|
||||||
FullscreenableImage(
|
FullscreenableImage(
|
||||||
url: userView.person.banner,
|
url: userView.person.banner!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: userView.person.banner,
|
imageUrl: userView.person.banner!,
|
||||||
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -207,9 +204,9 @@ class _UserOverview extends HookWidget {
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
child: FullscreenableImage(
|
child: FullscreenableImage(
|
||||||
url: userView.person.avatar,
|
url: userView.person.avatar!,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: userView.person.avatar,
|
imageUrl: userView.person.avatar!,
|
||||||
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -255,7 +252,7 @@ class _UserOverview extends HookWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context)
|
L10n.of(context)!
|
||||||
.number_of_posts(userView.counts.postCount),
|
.number_of_posts(userView.counts.postCount),
|
||||||
style: TextStyle(color: colorOnTopOfAccentColor),
|
style: TextStyle(color: colorOnTopOfAccentColor),
|
||||||
),
|
),
|
||||||
|
@ -273,7 +270,7 @@ class _UserOverview extends HookWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
L10n.of(context)
|
L10n.of(context)!
|
||||||
.number_of_comments(userView.counts.commentCount),
|
.number_of_comments(userView.counts.commentCount),
|
||||||
style: TextStyle(color: colorOnTopOfAccentColor),
|
style: TextStyle(color: colorOnTopOfAccentColor),
|
||||||
),
|
),
|
||||||
|
@ -338,7 +335,7 @@ class _AboutTab extends HookWidget {
|
||||||
child: const Divider(),
|
child: const Divider(),
|
||||||
);
|
);
|
||||||
|
|
||||||
communityTile(String name, String icon, int id) => ListTile(
|
communityTile(String name, String? icon, int id) => ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
onTap: () => goToCommunity.byId(context, instanceHost, id),
|
onTap: () => goToCommunity.byId(context, instanceHost, id),
|
||||||
title: Text('!$name'),
|
title: Text('!$name'),
|
||||||
|
@ -371,7 +368,7 @@ class _AboutTab extends HookWidget {
|
||||||
if (userDetails.personView.person.bio != null) ...[
|
if (userDetails.personView.person.bio != null) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: wallPadding,
|
padding: wallPadding,
|
||||||
child: MarkdownText(userDetails.personView.person.bio,
|
child: MarkdownText(userDetails.personView.person.bio!,
|
||||||
instanceHost: instanceHost)),
|
instanceHost: instanceHost)),
|
||||||
divider,
|
divider,
|
||||||
],
|
],
|
||||||
|
@ -380,7 +377,7 @@ class _AboutTab extends HookWidget {
|
||||||
title: Center(
|
title: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Moderates:',
|
'Moderates:',
|
||||||
style: theme.textTheme.headline6.copyWith(fontSize: 18),
|
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -396,7 +393,7 @@ class _AboutTab extends HookWidget {
|
||||||
title: Center(
|
title: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Subscribed:',
|
'Subscribed:',
|
||||||
style: theme.textTheme.headline6.copyWith(fontSize: 18),
|
style: theme.textTheme.headline6?.copyWith(fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:lemmur/hooks/logged_in_action.dart';
|
||||||
import 'package:lemmy_api_client/v3.dart';
|
import 'package:lemmy_api_client/v3.dart';
|
||||||
|
|
||||||
import '../hooks/delayed_loading.dart';
|
import '../hooks/delayed_loading.dart';
|
||||||
import '../hooks/stores.dart';
|
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import 'markdown_mode_icon.dart';
|
import 'markdown_mode_icon.dart';
|
||||||
import 'markdown_text.dart';
|
import 'markdown_text.dart';
|
||||||
|
@ -13,19 +13,20 @@ import 'markdown_text.dart';
|
||||||
/// or `null` if cancelled
|
/// or `null` if cancelled
|
||||||
class WriteComment extends HookWidget {
|
class WriteComment extends HookWidget {
|
||||||
final Post post;
|
final Post post;
|
||||||
final Comment comment;
|
final Comment? comment;
|
||||||
|
|
||||||
const WriteComment.toPost(this.post) : comment = null;
|
const WriteComment.toPost(this.post) : comment = null;
|
||||||
const WriteComment.toComment({@required this.comment, @required this.post})
|
const WriteComment.toComment({
|
||||||
: assert(comment != null),
|
required Comment this.comment,
|
||||||
assert(post != null);
|
required this.post,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final controller = useTextEditingController();
|
final controller = useTextEditingController();
|
||||||
final showFancy = useState(false);
|
final showFancy = useState(false);
|
||||||
final delayed = useDelayedLoading();
|
final delayed = useDelayedLoading();
|
||||||
final accStore = useAccountsStore();
|
final loggedInAction = useLoggedInAction(post.instanceHost);
|
||||||
|
|
||||||
final preview = () {
|
final preview = () {
|
||||||
final body = () {
|
final body = () {
|
||||||
|
@ -38,27 +39,21 @@ class WriteComment extends HookWidget {
|
||||||
);
|
);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
if (post != null) {
|
return Column(
|
||||||
return Column(
|
children: [
|
||||||
children: [
|
SelectableText(
|
||||||
SelectableText(
|
post.name,
|
||||||
post.name,
|
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
||||||
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
const SizedBox(height: 4),
|
body,
|
||||||
body,
|
],
|
||||||
],
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return body;
|
|
||||||
}();
|
}();
|
||||||
|
|
||||||
handleSubmit() async {
|
handleSubmit(Jwt token) async {
|
||||||
final api = LemmyApiV3(post.instanceHost);
|
final api = LemmyApiV3(post.instanceHost);
|
||||||
|
|
||||||
final token = accStore.defaultTokenFor(post.instanceHost);
|
|
||||||
|
|
||||||
delayed.start();
|
delayed.start();
|
||||||
try {
|
try {
|
||||||
final res = await api.run(CreateComment(
|
final res = await api.run(CreateComment(
|
||||||
|
@ -119,10 +114,11 @@ class WriteComment extends HookWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: delayed.pending ? () {} : handleSubmit,
|
onPressed:
|
||||||
|
delayed.pending ? () {} : loggedInAction(handleSubmit),
|
||||||
child: delayed.loading
|
child: delayed.loading
|
||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: Text(L10n.of(context).post),
|
: Text(L10n.of(context)!.post),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
82
pubspec.lock
82
pubspec.lock
|
@ -105,7 +105,7 @@ packages:
|
||||||
name: cached_network_image
|
name: cached_network_image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.1"
|
version: "3.0.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -168,14 +168,14 @@ packages:
|
||||||
name: crypto
|
name: crypto
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "1.0.2"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -222,14 +222,14 @@ packages:
|
||||||
name: flutter_blurhash
|
name: flutter_blurhash
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.6.0"
|
||||||
flutter_cache_manager:
|
flutter_cache_manager:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_cache_manager
|
name: flutter_cache_manager
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "3.0.1"
|
||||||
flutter_hooks:
|
flutter_hooks:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -262,14 +262,14 @@ packages:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.1"
|
||||||
flutter_speed_dial:
|
flutter_speed_dial:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_speed_dial
|
name: flutter_speed_dial
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.5"
|
version: "3.0.5"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -293,7 +293,7 @@ packages:
|
||||||
name: fuzzy
|
name: fuzzy
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.4.0-nullsafety.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -342,7 +342,7 @@ packages:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.3"
|
version: "0.7.4"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -356,7 +356,7 @@ packages:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.1"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -398,7 +398,7 @@ packages:
|
||||||
name: latinize
|
name: latinize
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.2"
|
version: "0.1.0-nullsafety.0"
|
||||||
lemmy_api_client:
|
lemmy_api_client:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -433,7 +433,7 @@ packages:
|
||||||
name: matrix4_transform
|
name: matrix4_transform
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.7"
|
version: "2.0.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -454,7 +454,7 @@ packages:
|
||||||
name: modal_bottom_sheet
|
name: modal_bottom_sheet
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0+1"
|
version: "2.0.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -468,7 +468,7 @@ packages:
|
||||||
name: octo_image
|
name: octo_image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "1.0.0+1"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -482,7 +482,7 @@ packages:
|
||||||
name: package_info
|
name: package_info
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.3+4"
|
version: "2.0.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -517,7 +517,7 @@ packages:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.1"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -538,14 +538,14 @@ packages:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.1.0"
|
||||||
photo_view:
|
photo_view:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: photo_view
|
name: photo_view
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.3"
|
version: "0.11.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -559,7 +559,7 @@ packages:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "2.0.0"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -580,7 +580,7 @@ packages:
|
||||||
name: provider
|
name: provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.3"
|
version: "5.0.0"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -601,7 +601,7 @@ packages:
|
||||||
name: rxdart
|
name: rxdart
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.25.0"
|
version: "0.26.0"
|
||||||
share:
|
share:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -615,28 +615,42 @@ packages:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.7+3"
|
version: "2.0.5"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
shared_preferences_macos:
|
shared_preferences_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_macos
|
name: shared_preferences_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.1+11"
|
version: "2.0.0"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "2.0.0"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2+7"
|
version: "2.0.0"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -739,7 +753,7 @@ packages:
|
||||||
name: timeago
|
name: timeago
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.30"
|
version: "3.0.2"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -760,42 +774,42 @@ packages:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.7.10"
|
version: "6.0.3"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.1+4"
|
version: "2.0.0"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.1+9"
|
version: "2.0.0"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.9"
|
version: "2.0.2"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5+3"
|
version: "2.0.0"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.1+3"
|
version: "2.0.0"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -844,7 +858,7 @@ packages:
|
||||||
name: xml
|
name: xml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.2"
|
version: "5.1.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -854,4 +868,4 @@ packages:
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.12.0 <3.0.0"
|
dart: ">=2.12.0 <3.0.0"
|
||||||
flutter: ">=1.24.0-10"
|
flutter: ">=1.24.0-10.2.pre"
|
||||||
|
|
28
pubspec.yaml
28
pubspec.yaml
|
@ -18,34 +18,34 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||||
version: 0.4.1+14
|
version: 0.4.1+14
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.7.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
# widgets
|
# widgets
|
||||||
flutter_speed_dial: ^1.2.5
|
flutter_speed_dial: ^3.0.5
|
||||||
photo_view: ^0.10.2
|
photo_view: ^0.11.1
|
||||||
markdown: ^4.0.0
|
markdown: ^4.0.0
|
||||||
flutter_markdown: ^0.6.1
|
flutter_markdown: ^0.6.1
|
||||||
cached_network_image: ^2.2.0+1
|
cached_network_image: ^3.0.0
|
||||||
modal_bottom_sheet: ^1.0.0+1
|
modal_bottom_sheet: ^2.0.0
|
||||||
|
|
||||||
# native
|
# native
|
||||||
share: ^2.0.1
|
share: ^2.0.1
|
||||||
url_launcher: ^5.5.1
|
url_launcher: ^6.0.3
|
||||||
shared_preferences: ">=0.5.0 <2.0.0"
|
shared_preferences: ^2.0.5
|
||||||
package_info: ^0.4.3
|
package_info: ^2.0.0
|
||||||
image_picker: ^0.7.3
|
image_picker: ^0.7.4
|
||||||
|
|
||||||
# state management
|
# state management
|
||||||
flutter_hooks: ^0.16.0
|
flutter_hooks: ^0.16.0
|
||||||
provider: ^4.3.1
|
provider: ^5.0.0
|
||||||
|
|
||||||
# utils
|
# utils
|
||||||
timeago: ^2.0.27
|
timeago: ^3.0.2
|
||||||
fuzzy: <1.0.0
|
fuzzy: ^0.4.0-nullsafety.0
|
||||||
lemmy_api_client: ^0.14.0
|
lemmy_api_client: ^0.14.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
matrix4_transform: ^1.1.7
|
matrix4_transform: ^2.0.0
|
||||||
json_annotation: ^4.0.1
|
json_annotation: ^4.0.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
@ -55,7 +55,7 @@ dependencies:
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^0.1.3
|
cupertino_icons: ^1.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -30,7 +30,7 @@ ${keys.map((key) => " static const $key = '$key';").join('\n')}
|
||||||
extension L10nFromString on String {
|
extension L10nFromString on String {
|
||||||
String tr(BuildContext context) {
|
String tr(BuildContext context) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
${keysWithoutVariables.map((key) => " case L10nStrings.$key:\n return L10n.of(context).$key;").join('\n')}
|
${keysWithoutVariables.map((key) => " case L10nStrings.$key:\n return L10n.of(context)!.$key;").join('\n')}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return this;
|
return this;
|
||||||
|
|
Loading…
Reference in New Issue