added lots of dartdoc/comments

This commit is contained in:
shilangyu 2020-09-30 17:05:00 +00:00
parent e879a17948
commit 94d12fccff
33 changed files with 71 additions and 8 deletions

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
/// creates an [AsyncSnapshot] from the Future returned from the valueBuilder.
/// [keys] can be used to rebuild the Future
AsyncSnapshot<T> useMemoFuture<T>(Future<T> Function() valueBuilder,
[List<Object> keys = const <dynamic>[]]) =>
useFuture(useMemoized<Future<T>>(valueBuilder, keys), preserveState: false);

View File

@ -2,9 +2,9 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:mobx/mobx.dart';
/// Observes MobX observables in [fn] and returns the built value.
/// Behaves like a [useMemoized] with observables as a list of dependencies.
/// When observable inside have changed, the hook rebuilds the value.
/// The returned value can be ignored for a `useEffect(() { autorun(fn); }, [])`
/// clone.
/// effect.
T useObserved<T>(T Function() fn) {
final returnValue = useState(useMemoized(fn));

View File

@ -11,6 +11,7 @@ import '../widgets/bottom_modal.dart';
import '../widgets/fullscreenable_image.dart';
import 'add_instance.dart';
/// A modal where an account can be added for a given instance
class AddAccountPage extends HookWidget {
final String instanceUrl;
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
@ -27,7 +28,7 @@ class AddAccountPage extends HookWidget {
useValueListenable(passwordController);
final accountsStore = useAccountsStore();
final loading = useDelayedLoading(Duration(milliseconds: 500));
final loading = useDelayedLoading();
final selectedInstance = useState(instanceUrl);
final icon = useState<String>(null);
useEffect(() {
@ -38,6 +39,7 @@ class AddAccountPage extends HookWidget {
return null;
}, [selectedInstance.value]);
// show a modal with a list of instance checkboxes
selectInstance() async {
final val = await showModalBottomSheet<String>(
backgroundColor: Colors.transparent,

View File

@ -146,6 +146,7 @@ class AddInstancePage extends HookWidget {
}
}
/// removes protocol and trailing slash
String _fixInstanceUrl(String inst) {
if (inst.startsWith('https://')) {
inst = inst.substring(8);
@ -155,7 +156,9 @@ String _fixInstanceUrl(String inst) {
inst = inst.substring(7);
}
if (inst.endsWith('/')) inst = inst.substring(0, inst.length - 1);
if (inst.endsWith('/')) {
inst = inst.substring(0, inst.length - 1);
}
return inst;
}

View File

@ -7,6 +7,7 @@ import '../util/goto.dart';
import '../widgets/markdown_text.dart';
import '../widgets/sortable_infinite_list.dart';
/// Infinite list of Communities fetched by the given fetcher
class CommunitiesListPage extends StatelessWidget {
final String title;
final Future<List<CommunityView>> Function(

View File

@ -13,6 +13,7 @@ import '../util/extensions/iterators.dart';
import '../util/goto.dart';
import '../util/text_color.dart';
/// List of subscribed communities per instance
class CommunitiesTab extends HookWidget {
CommunitiesTab();

View File

@ -21,6 +21,7 @@ import '../widgets/fullscreenable_image.dart';
import '../widgets/markdown_text.dart';
import '../widgets/sortable_infinite_list.dart';
/// Displays posts, comments, and general info about the given community
class CommunityPage extends HookWidget {
final CommunityView _community;
final String instanceUrl;

View File

@ -13,6 +13,7 @@ import '../util/spaced.dart';
import '../widgets/markdown_text.dart';
import 'full_post.dart';
/// Fab that triggers the [CreatePost] modal
class CreatePostFab extends HookWidget {
@override
Widget build(BuildContext context) {
@ -26,6 +27,7 @@ class CreatePostFab extends HookWidget {
}
}
/// Modal for creating a post to some community in some instance
class CreatePost extends HookWidget {
final CommunityView community;

View File

@ -13,6 +13,7 @@ import '../widgets/post.dart';
import '../widgets/save_post_button.dart';
import '../widgets/write_comment.dart';
/// Displays a post with its comment section
class FullPostPage extends HookWidget {
final int id;
final String instanceUrl;

View File

@ -19,6 +19,7 @@ import '../widgets/sortable_infinite_list.dart';
import 'communities_list.dart';
import 'users_list.dart';
/// Displays posts, comments, and general info about the given instance
class InstancePage extends HookWidget {
final String instanceUrl;
final Future<FullSiteView> siteFuture;

View File

@ -7,6 +7,7 @@ import 'package:photo_view/photo_view.dart';
import '../widgets/bottom_modal.dart';
/// View to interact with a media object. Zoom in/out, download, share, etc.
class MediaViewPage extends HookWidget {
final String url;

View File

@ -9,6 +9,8 @@ import '../widgets/bottom_modal.dart';
import '../widgets/user_profile.dart';
import 'settings.dart';
/// Profile page for a logged in user. The difference between this and
/// UserPage is that here you have access to settings
class UserProfileTab extends HookWidget {
UserProfileTab();

View File

@ -10,6 +10,7 @@ import '../util/goto.dart';
import 'add_account.dart';
import 'add_instance.dart';
/// Page with a list of different settings sections
class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
@ -48,6 +49,7 @@ class SettingsPage extends StatelessWidget {
}
}
/// Settings for theme color, AMOLED switch
class AppearanceConfigPage extends HookWidget {
@override
Widget build(BuildContext context) {
@ -89,6 +91,7 @@ class AppearanceConfigPage extends HookWidget {
}
}
/// Settings for managing accounts
class AccountsConfigPage extends HookWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();

View File

@ -5,6 +5,8 @@ import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../widgets/user_profile.dart';
/// Page showing posts, comments, and general info about a user.
class UserPage extends HookWidget {
final int userId;
final String instanceUrl;

View File

@ -5,6 +5,7 @@ import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../util/extensions/api.dart';
import '../widgets/markdown_text.dart';
/// Infinite list of Users fetched by the given fetcher
class UsersListPage extends StatelessWidget {
final String title;
final List<UserView> users;
@ -13,6 +14,7 @@ class UsersListPage extends StatelessWidget {
: assert(users != null),
super(key: key);
// TODO: go to user
void goToUser(BuildContext context, int id) {
print('GO TO USER $id');
}
@ -20,6 +22,8 @@ class UsersListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// TODO: change to infinite scroll
return Scaffold(
appBar: AppBar(
title: Text(title ?? '', style: theme.textTheme.headline6),

View File

@ -6,6 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
part 'accounts_store.g.dart';
/// Store that manages all accounts
class AccountsStore extends _AccountsStore with _$AccountsStore {}
abstract class _AccountsStore with Store {
@ -24,7 +25,7 @@ abstract class _AccountsStore with Store {
(_) => save(),
);
// check if there's a default profile and if not, select one
// automatically set new default accounts when accounts are added/removed
_pickDefaultsDisposer = reaction(
(_) => [
tokens.forEach((k, submap) =>
@ -87,6 +88,8 @@ abstract class _AccountsStore with Store {
void load() async {
final prefs = await SharedPreferences.getInstance();
// I barely understand what I did. Long story short it asserts a
// raw json into a nested ObservableMap
nestedMapsCast<T>(String key, T f(Map<String, dynamic> json)) =>
ObservableMap.of(
(jsonDecode(prefs.getString(key) ?? '{}') as Map<String, dynamic>)
@ -118,6 +121,9 @@ abstract class _AccountsStore with Store {
await prefs.setString('tokens', jsonEncode(tokens));
}
/// Map containing JWT tokens of specific users.
/// If a token is in this map, the user is considered logged in
/// for that account.
/// `tokens['instanceUrl']['username']`
@observable
ObservableMap<String, ObservableMap<String, Jwt>> tokens;
@ -128,7 +134,7 @@ abstract class _AccountsStore with Store {
ObservableMap<String, String> _defaultAccounts;
/// default account for the app
/// username@instanceUrl
/// It is in a form of `username@instanceUrl`
@observable
String _defaultAccount;
@ -188,6 +194,8 @@ abstract class _AccountsStore with Store {
_defaultAccounts[instanceUrl] = username;
}
/// An instance is considered anonymous if it was not
/// added or there are no accounts assigned to it.
bool isAnonymousFor(String instanceUrl) => Computed(() {
if (!instances.contains(instanceUrl)) {
return true;
@ -196,6 +204,7 @@ abstract class _AccountsStore with Store {
return tokens[instanceUrl].isEmpty;
}).value;
/// `true` if no added instance has an account assigned to it
@computed
bool get hasNoAccount => loggedInInstances.isEmpty;
@ -234,7 +243,8 @@ abstract class _AccountsStore with Store {
}
/// adds a new instance with no accounts associated with it.
/// Additionally makes a test GET /site request to check if the instance exists
/// Additionally makes a test `GET /site` request to check if the instance exists.
/// Check is skipped when [assumeValid] is `true`
@action
Future<void> addInstance(
String instanceUrl, {
@ -256,6 +266,7 @@ abstract class _AccountsStore with Store {
tokens[instanceUrl] = ObservableMap();
}
/// This also removes all accounts assigned to this instance
@action
void removeInstance(String instanceUrl) {
tokens.remove(instanceUrl);

View File

@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart';
part 'config_store.g.dart';
/// Store managing user-level configuration such as theme or language
class ConfigStore extends _ConfigStore with _$ConfigStore {}
abstract class _ConfigStore with Store {
@ -42,5 +43,6 @@ abstract class _ConfigStore with Store {
bool amoledDarkMode;
}
/// converts string to ThemeMode
ThemeMode _themeModeFromString(String theme) =>
ThemeMode.values.firstWhere((e) => describeEnum(e) == theme);

View File

@ -10,6 +10,8 @@ import 'pages/user.dart';
import 'stores/accounts_store.dart';
import 'util/goto.dart';
/// Decides where does a link link to. Either somewhere in-app:
/// opens the correct page, or outside of the app: opens in a browser
Future<void> linkLauncher({
@required BuildContext context,
@required String url,

View File

@ -1,7 +1,12 @@
import 'package:lemmy_api_client/lemmy_api_client.dart';
// Extensions to lemmy api objects which give a [.instanceUrl] getter
// allowing for a convenient way of knowing from which instance did this
// object come from
// TODO: change it to something more robust? regex?
extension GetInstanceCommunityView on CommunityView {
// TODO: change it to something more robust? regex?
String get instanceUrl => actorId.split('/')[2];
}

View File

@ -1,4 +1,5 @@
extension ExtraIterators<E> on Iterable<E> {
/// A `.map` but with an index as the second argument
Iterable<T> mapWithIndex<T>(T f(E e, int i)) {
var i = 0;
return map((e) => f(e, i++));

View File

@ -6,6 +6,7 @@ import '../pages/full_post.dart';
import '../pages/instance.dart';
import '../pages/user.dart';
/// Pushes onto the navigator stack the given widget
Future<dynamic> goTo(
BuildContext context,
Widget Function(BuildContext context) builder,
@ -14,6 +15,7 @@ Future<dynamic> goTo(
builder: builder,
));
/// Replaces the top of the navigator stack with the given widget
Future<dynamic> goToReplace(
BuildContext context,
Widget Function(BuildContext context) builder,

View File

@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart';
/// Creates gaps between given widgets
List<Widget> spaced(double gap, Iterable<Widget> children) => children
.expand((item) sync* {
yield SizedBox(width: gap, height: gap);

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
/// Given the background color, returns a text color
/// with a good contrast ratio
Color textColorBasedOnBackground(Color color) {
if (color.computeLuminance() > 0.5) {
return Colors.black;

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
/// A badge with accent color as background
class Badge extends StatelessWidget {
final Widget child;
final BorderRadiusGeometry borderRadius;

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
/// Should be spawned with a showModalBottomSheet, not routed to.
class BottomModal extends StatelessWidget {
final Widget child;
final String title;

View File

@ -20,6 +20,7 @@ import 'bottom_modal.dart';
import 'markdown_text.dart';
import 'write_comment.dart';
/// A single comment that renders its replies
class Comment extends HookWidget {
final int indent;
final int postCreatorId;

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import '../pages/media_view.dart';
import '../util/goto.dart';
/// If the media is pressed, it opens itself in a [MediaViewPage]
class FullscreenableImage extends StatelessWidget {
final String url;
final Widget child;

View File

@ -18,6 +18,7 @@ class InfiniteScrollController {
}
}
/// `ListView.builder` with asynchronous data fetching
class InfiniteScroll<T> extends HookWidget {
final int batchSize;
final Widget loadingWidget;

View File

@ -6,6 +6,7 @@ import 'package:markdown/markdown.dart' as md;
import '../url_launcher.dart';
import 'fullscreenable_image.dart';
/// A Markdown renderer with link/image handling
class MarkdownText extends StatelessWidget {
final String instanceUrl;
final String text;

View File

@ -44,6 +44,7 @@ MediaType whatType(String url) {
return MediaType.other;
}
/// A post overview card
class Post extends HookWidget {
final PostView post;
final String instanceUrl;
@ -426,6 +427,7 @@ class Post extends HookWidget {
else if (post.url != null && post.url.isNotEmpty)
linkPreview(),
if (post.body != null)
// TODO: trim content
Padding(
padding: const EdgeInsets.all(10),
child: MarkdownText(post.body, instanceUrl: instanceUrl)),

View File

@ -5,6 +5,7 @@ import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'bottom_modal.dart';
/// Dropdown filters where you can change sorting or viewing type
class PostListOptions extends HookWidget {
final void Function(SortType sort) onChange;
final SortType defaultSort;

View File

@ -11,6 +11,7 @@ import '../util/intl.dart';
import '../util/text_color.dart';
import 'badge.dart';
/// Shared widget of UserPage and ProfileTab
class UserProfile extends HookWidget {
final Future<UserView> _userView;
final String instanceUrl;

View File

@ -7,6 +7,7 @@ import '../hooks/stores.dart';
import '../util/extensions/api.dart';
import 'markdown_text.dart';
/// Modal for writing a comment to a given post/comment (aka reply)
/// on submit pops the navigator stack with a [CommentView]
/// or `null` if cancelled
class WriteComment extends HookWidget {