Compare commits

...

2 Commits

Author SHA1 Message Date
381c99b353 fix crashing. (#248) 2023-09-08 09:07:48 -07:00
39ee3137f8 fix reply box in full screen. (#247) 2023-09-05 15:17:23 -07:00
41 changed files with 187 additions and 226 deletions

View File

@ -17,8 +17,6 @@ jobs:
with:
submodules: recursive
fetch-depth: 0
- run: cd submodules/flutter
- run: git remote set-url origin https://github.com/flutter/flutter.git
- run: submodules/flutter/bin/flutter doctor
- run: submodules/flutter/bin/flutter pub get
- run: submodules/flutter/bin/dart format --set-exit-if-changed lib test integration_test

View File

@ -24,8 +24,6 @@ jobs:
with:
submodules: recursive
fetch-depth: 0
- run: cd submodules/flutter
- run: git remote set-url origin https://github.com/flutter/flutter.git
- run: submodules/flutter/bin/flutter doctor
- run: submodules/flutter/bin/flutter pub get
- run: submodules/flutter/bin/dart format --set-exit-if-changed lib test integration_test

View File

@ -151,12 +151,12 @@ SPEC CHECKSUMS:
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
synced_shared_preferences: f722742b06d65c7315b8e9f56b794c9fbd5597f7
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4

View File

@ -77,8 +77,10 @@
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes</string>
<key>io.flutter.embedded_views_preview</key>
<true/>
<string>This app needs camera access to scan QR codes</string>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>FLTEnableWideGamut</key>
<false/>
</dict>
</plist>

View File

@ -52,14 +52,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
state.copyWith(
isLoggedIn: true,
user: user,
status: AuthStatus.loaded,
status: Status.success,
),
);
} else {
emit(
state.copyWith(
isLoggedIn: false,
status: AuthStatus.loaded,
status: Status.success,
),
);
}
@ -81,7 +81,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
Future<void> onLogin(AuthLogin event, Emitter<AuthState> emit) async {
emit(state.copyWith(status: AuthStatus.loading));
emit(state.copyWith(status: Status.inProgress));
final bool successful = await _authRepository.login(
username: event.username,
@ -94,11 +94,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
state.copyWith(
user: user ?? User.emptyWithId(event.username),
isLoggedIn: true,
status: AuthStatus.loaded,
status: Status.success,
),
);
} else {
emit(state.copyWith(status: AuthStatus.failure));
emit(state.copyWith(status: Status.failure));
}
}

View File

@ -1,11 +1,5 @@
part of 'auth_bloc.dart';
enum AuthStatus {
loading,
loaded,
failure,
}
class AuthState extends Equatable {
const AuthState({
required this.user,
@ -17,13 +11,13 @@ class AuthState extends Equatable {
const AuthState.init()
: user = const User.empty(),
isLoggedIn = false,
status = AuthStatus.loaded,
status = Status.success,
agreedToEULA = false;
final User user;
final bool isLoggedIn;
final bool agreedToEULA;
final AuthStatus status;
final Status status;
String get username => user.id;
@ -31,7 +25,7 @@ class AuthState extends Equatable {
User? user,
bool? isLoggedIn,
bool? agreedToEULA,
AuthStatus? status,
Status? status,
}) {
return AuthState(
user: user ?? this.user,

View File

@ -79,7 +79,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
const StoriesState.init().copyWith(
isOfflineReading: hasCachedStories &&
// Only go into offline mode in the next session.
state.downloadStatus == StoriesDownloadStatus.initial,
state.downloadStatus == StoriesDownloadStatus.idle,
currentPageSize: pageSize,
downloadStatus: state.downloadStatus,
storiesDownloaded: state.storiesDownloaded,
@ -133,12 +133,12 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
StoriesRefresh event,
Emitter<StoriesState> emit,
) async {
if (state.statusByType[event.type] == StoriesStatus.loading) return;
if (state.statusByType[event.type] == Status.inProgress) return;
emit(
state.copyWithStatusUpdated(
type: event.type,
to: StoriesStatus.loading,
to: Status.inProgress,
),
);
@ -146,7 +146,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
emit(
state.copyWithStatusUpdated(
type: event.type,
to: StoriesStatus.loaded,
to: Status.success,
),
);
} else {
@ -159,7 +159,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
emit(
state.copyWithStatusUpdated(
type: event.type,
to: StoriesStatus.loading,
to: Status.inProgress,
),
);
@ -218,7 +218,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
emit(
state.copyWithStatusUpdated(
type: event.type,
to: StoriesStatus.loaded,
to: Status.success,
),
);
}
@ -245,7 +245,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
void onStoriesLoaded(StoriesLoaded event, Emitter<StoriesState> emit) {
emit(
state.copyWithStatusUpdated(type: event.type, to: StoriesStatus.loaded),
state.copyWithStatusUpdated(type: event.type, to: Status.success),
);
}

View File

@ -1,13 +1,7 @@
part of 'stories_bloc.dart';
enum StoriesStatus {
initial,
loading,
loaded,
}
enum StoriesDownloadStatus {
initial,
idle,
downloading,
finished,
failure,
@ -43,12 +37,12 @@ class StoriesState extends Equatable {
StoryType.ask: <int>[],
StoryType.show: <int>[],
},
this.statusByType = const <StoryType, StoriesStatus>{
StoryType.top: StoriesStatus.initial,
StoryType.best: StoriesStatus.initial,
StoryType.latest: StoriesStatus.initial,
StoryType.ask: StoriesStatus.initial,
StoryType.show: StoriesStatus.initial,
this.statusByType = const <StoryType, Status>{
StoryType.top: Status.idle,
StoryType.best: Status.idle,
StoryType.latest: Status.idle,
StoryType.ask: Status.idle,
StoryType.show: Status.idle,
},
this.currentPageByType = const <StoryType, int>{
StoryType.top: 0,
@ -58,7 +52,7 @@ class StoriesState extends Equatable {
StoryType.show: 0,
},
}) : isOfflineReading = false,
downloadStatus = StoriesDownloadStatus.initial,
downloadStatus = StoriesDownloadStatus.idle,
currentPageSize = 0,
readStoriesIds = const <int>{},
storiesDownloaded = 0,
@ -66,7 +60,7 @@ class StoriesState extends Equatable {
final Map<StoryType, List<Story>> storiesByType;
final Map<StoryType, List<int>> storyIdsByType;
final Map<StoryType, StoriesStatus> statusByType;
final Map<StoryType, Status> statusByType;
final Map<StoryType, int> currentPageByType;
final Set<int> readStoriesIds;
final StoriesDownloadStatus downloadStatus;
@ -78,7 +72,7 @@ class StoriesState extends Equatable {
StoriesState copyWith({
Map<StoryType, List<Story>>? storiesByType,
Map<StoryType, List<int>>? storyIdsByType,
Map<StoryType, StoriesStatus>? statusByType,
Map<StoryType, Status>? statusByType,
Map<StoryType, int>? currentPageByType,
Set<int>? readStoriesIds,
StoriesDownloadStatus? downloadStatus,
@ -133,10 +127,10 @@ class StoriesState extends Equatable {
StoriesState copyWithStatusUpdated({
required StoryType type,
required StoriesStatus to,
required Status to,
}) {
final Map<StoryType, StoriesStatus> newMap =
Map<StoryType, StoriesStatus>.from(statusByType);
final Map<StoryType, Status> newMap =
Map<StoryType, Status>.from(statusByType);
newMap[type] = to;
return copyWith(
statusByType: newMap,
@ -162,9 +156,9 @@ class StoriesState extends Equatable {
final Map<StoryType, List<int>> newStoryIdsMap =
Map<StoryType, List<int>>.from(storyIdsByType);
newStoryIdsMap[type] = <int>[];
final Map<StoryType, StoriesStatus> newStatusMap =
Map<StoryType, StoriesStatus>.from(statusByType);
newStatusMap[type] = StoriesStatus.loading;
final Map<StoryType, Status> newStatusMap =
Map<StoryType, Status>.from(statusByType);
newStatusMap[type] = Status.inProgress;
final Map<StoryType, int> newCurrentPageMap =
Map<StoryType, int>.from(currentPageByType);
newCurrentPageMap[type] = 0;

View File

@ -106,7 +106,7 @@ class CommentsCubit extends Cubit<CommentsState> {
emit(
state.copyWith(
status: CommentsStatus.loading,
status: CommentsStatus.inProgress,
comments: <Comment>[],
currentPage: 0,
),
@ -150,7 +150,7 @@ class CommentsCubit extends Cubit<CommentsState> {
Future<void> refresh() async {
emit(
state.copyWith(
status: CommentsStatus.loading,
status: CommentsStatus.inProgress,
),
);
@ -224,7 +224,7 @@ class CommentsCubit extends Cubit<CommentsState> {
void Function(Comment)? onCommentFetched,
VoidCallback? onDone,
}) {
if (comment == null && state.status == CommentsStatus.loading) return;
if (comment == null && state.status == CommentsStatus.inProgress) return;
switch (state.fetchMode) {
case FetchMode.lazy:
@ -269,7 +269,7 @@ class CommentsCubit extends Cubit<CommentsState> {
_streamSubscriptions[comment.id] = streamSubscription;
case FetchMode.eager:
if (_streamSubscription != null) {
emit(state.copyWith(status: CommentsStatus.loading));
emit(state.copyWith(status: CommentsStatus.inProgress));
_streamSubscription
?..resume()
..onData(onCommentFetched);
@ -279,7 +279,7 @@ class CommentsCubit extends Cubit<CommentsState> {
Future<void> loadParentThread() async {
HapticFeedbackUtil.light();
emit(state.copyWith(fetchParentStatus: CommentsStatus.loading));
emit(state.copyWith(fetchParentStatus: CommentsStatus.inProgress));
final Item? parent =
await _storiesRepository.fetchItem(id: state.item.parent);
@ -301,7 +301,7 @@ class CommentsCubit extends Cubit<CommentsState> {
Future<void> loadRootThread() async {
HapticFeedbackUtil.light();
emit(state.copyWith(fetchRootStatus: CommentsStatus.loading));
emit(state.copyWith(fetchRootStatus: CommentsStatus.inProgress));
final Story? parent = await _storiesRepository
.fetchParentStory(id: state.item.id)
.then(_toBuildableStory);

View File

@ -1,11 +1,11 @@
part of 'comments_cubit.dart';
enum CommentsStatus {
init,
loading,
idle,
inProgress,
loaded,
allLoaded,
failure,
error,
}
class CommentsState extends Equatable {
@ -28,9 +28,9 @@ class CommentsState extends Equatable {
required this.fetchMode,
required this.order,
}) : comments = <Comment>[],
status = CommentsStatus.init,
fetchParentStatus = CommentsStatus.init,
fetchRootStatus = CommentsStatus.init,
status = CommentsStatus.idle,
fetchParentStatus = CommentsStatus.idle,
fetchRootStatus = CommentsStatus.idle,
onlyShowTargetComment = false,
currentPage = 0;

View File

@ -51,7 +51,7 @@ class FavCubit extends Cubit<FavState> {
.onDone(() {
emit(
state.copyWith(
status: FavStatus.loaded,
status: Status.success,
),
);
});
@ -107,7 +107,7 @@ class FavCubit extends Cubit<FavState> {
}
void loadMore() {
emit(state.copyWith(status: FavStatus.loading));
emit(state.copyWith(status: Status.inProgress));
final int currentPage = state.currentPage;
final int len = state.favIds.length;
emit(state.copyWith(currentPage: currentPage + 1));
@ -128,10 +128,10 @@ class FavCubit extends Cubit<FavState> {
)
.listen(_onItemLoaded)
.onDone(() {
emit(state.copyWith(status: FavStatus.loaded));
emit(state.copyWith(status: Status.success));
});
} else {
emit(state.copyWith(status: FavStatus.loaded));
emit(state.copyWith(status: Status.success));
}
}
@ -140,7 +140,7 @@ class FavCubit extends Cubit<FavState> {
emit(
state.copyWith(
status: FavStatus.loading,
status: Status.inProgress,
currentPage: 0,
favItems: <Item>[],
favIds: <int>[],
@ -155,7 +155,7 @@ class FavCubit extends Cubit<FavState> {
)
.listen(_onItemLoaded)
.onDone(() {
emit(state.copyWith(status: FavStatus.loaded));
emit(state.copyWith(status: Status.success));
});
});
}

View File

@ -1,12 +1,5 @@
part of 'fav_cubit.dart';
enum FavStatus {
init,
loading,
loaded,
failure,
}
class FavState extends Equatable {
const FavState({
required this.favIds,
@ -18,18 +11,18 @@ class FavState extends Equatable {
FavState.init()
: favIds = <int>[],
favItems = <Item>[],
status = FavStatus.init,
status = Status.idle,
currentPage = 0;
final List<int> favIds;
final List<Item> favItems;
final FavStatus status;
final Status status;
final int currentPage;
FavState copyWith({
List<int>? favIds,
List<Item>? favItems,
FavStatus? status,
Status? status,
int? currentPage,
}) {
return FavState(

View File

@ -54,7 +54,7 @@ class HistoryCubit extends Cubit<HistoryState> {
}
void loadMore() {
emit(state.copyWith(status: HistoryStatus.loading));
emit(state.copyWith(status: Status.inProgress));
final int currentPage = state.currentPage;
final int len = state.submittedIds.length;
emit(state.copyWith(currentPage: currentPage + 1));
@ -75,10 +75,10 @@ class HistoryCubit extends Cubit<HistoryState> {
)
.listen(_onItemLoaded)
.onDone(() {
emit(state.copyWith(status: HistoryStatus.loaded));
emit(state.copyWith(status: Status.success));
});
} else {
emit(state.copyWith(status: HistoryStatus.loaded));
emit(state.copyWith(status: Status.success));
}
}
@ -86,7 +86,7 @@ class HistoryCubit extends Cubit<HistoryState> {
final String username = _authBloc.state.username;
emit(
state.copyWith(
status: HistoryStatus.loading,
status: Status.inProgress,
currentPage: 0,
submittedIds: <int>[],
submittedItems: <Item>[],
@ -107,7 +107,7 @@ class HistoryCubit extends Cubit<HistoryState> {
)
.listen(_onItemLoaded)
.onDone(() {
emit(state.copyWith(status: HistoryStatus.loaded));
emit(state.copyWith(status: Status.success));
});
}
});

View File

@ -1,12 +1,5 @@
part of 'history_cubit.dart';
enum HistoryStatus {
init,
loading,
loaded,
failure,
}
class HistoryState extends Equatable {
const HistoryState({
required this.submittedIds,
@ -18,18 +11,18 @@ class HistoryState extends Equatable {
HistoryState.init()
: submittedIds = <int>[],
submittedItems = <Item>[],
status = HistoryStatus.init,
status = Status.idle,
currentPage = 0;
final List<int> submittedIds;
final List<Item> submittedItems;
final HistoryStatus status;
final Status status;
final int currentPage;
HistoryState copyWith({
List<int>? submittedIds,
List<Item>? submittedItems,
HistoryStatus? status,
Status? status,
int? currentPage,
}) {
return HistoryState(

View File

@ -100,7 +100,7 @@ class NotificationCubit extends Cubit<NotificationState> {
void markAsRead(int id) {
Future.doWhile(() {
if (state.status != NotificationStatus.loading) {
if (state.status != Status.inProgress) {
if (state.unreadCommentsIds.contains(id)) {
final List<int> updatedUnreadIds = <int>[...state.unreadCommentsIds]
..remove(id);
@ -116,7 +116,7 @@ class NotificationCubit extends Cubit<NotificationState> {
void markAllAsRead() {
Future.doWhile(() {
if (state.status != NotificationStatus.loading) {
if (state.status != Status.inProgress) {
emit(state.copyWith(unreadCommentsIds: <int>[]));
_preferenceRepository.updateUnreadCommentsIds(<int>[]);
return false;
@ -131,7 +131,7 @@ class NotificationCubit extends Cubit<NotificationState> {
_preferenceCubit.state.notificationEnabled) {
emit(
state.copyWith(
status: NotificationStatus.loading,
status: Status.inProgress,
),
);
@ -141,14 +141,14 @@ class NotificationCubit extends Cubit<NotificationState> {
} else {
emit(
state.copyWith(
status: NotificationStatus.loaded,
status: Status.success,
),
);
}
}
Future<void> loadMore() async {
emit(state.copyWith(status: NotificationStatus.loading));
emit(state.copyWith(status: Status.inProgress));
final int currentPage = state.currentPage + 1;
final int lower = currentPage * _pageSize + state.offset;
@ -169,7 +169,7 @@ class NotificationCubit extends Cubit<NotificationState> {
emit(
state.copyWith(
status: NotificationStatus.loaded,
status: Status.success,
currentPage: currentPage,
),
);
@ -237,7 +237,7 @@ class NotificationCubit extends Cubit<NotificationState> {
}
}).whenComplete(
() => emit(
state.copyWith(status: NotificationStatus.loaded),
state.copyWith(status: Status.success),
),
);
}

View File

@ -1,12 +1,5 @@
part of 'notification_cubit.dart';
enum NotificationStatus {
initial,
loading,
loaded,
failure,
}
class NotificationState extends Equatable {
const NotificationState({
required this.comments,
@ -23,14 +16,14 @@ class NotificationState extends Equatable {
allCommentsIds = <int>[],
currentPage = 0,
offset = 0,
status = NotificationStatus.initial;
status = Status.idle;
final List<Comment> comments;
final List<int> allCommentsIds;
final List<int> unreadCommentsIds;
final int currentPage;
final int offset;
final NotificationStatus status;
final Status status;
NotificationState copyWith({
List<Comment>? comments,
@ -38,7 +31,7 @@ class NotificationState extends Equatable {
List<int>? unreadCommentsIds,
int? currentPage,
int? offset,
NotificationStatus? status,
Status? status,
}) {
return NotificationState(
comments: comments ?? this.comments,

View File

@ -27,7 +27,7 @@ class PinCubit extends Cubit<PinState> {
emit(state.copyWith(pinnedStoriesIds: ids));
_storiesRepository.fetchStoriesStream(ids: ids).listen(_onStoryFetched);
}).whenComplete(() => emit(state.copyWith(status: Status.loaded)));
}).whenComplete(() => emit(state.copyWith(status: Status.success)));
}
void pinStory(Story story) {
@ -53,7 +53,7 @@ class PinCubit extends Cubit<PinState> {
}
void refresh() {
if (state.status == Status.loading) return;
if (state.status.isLoading) return;
init();
}

View File

@ -27,7 +27,7 @@ class PollCubit extends Cubit<PollState> {
emit(PollState.init());
}
emit(state.copyWith(status: PollStatus.loading));
emit(state.copyWith(status: Status.inProgress));
List<int> pollOptionsIds = _story.parts;
@ -42,7 +42,7 @@ class PollCubit extends Cubit<PollState> {
// If pollOptionsIds is still empty, exit loading state.
if (pollOptionsIds.isEmpty) {
emit(state.copyWith(status: PollStatus.loaded));
emit(state.copyWith(status: Status.success));
return;
}
@ -72,7 +72,7 @@ class PollCubit extends Cubit<PollState> {
);
}
emit(state.copyWith(status: PollStatus.loaded));
emit(state.copyWith(status: Status.success));
}
}

View File

@ -1,12 +1,5 @@
part of 'poll_cubit.dart';
enum PollStatus {
initial,
loading,
loaded,
failure,
}
class PollState extends Equatable {
const PollState({
required this.totalVotes,
@ -19,18 +12,18 @@ class PollState extends Equatable {
: totalVotes = 0,
selections = <int>{},
pollOptions = <PollOption>[],
status = PollStatus.initial;
status = Status.idle;
final int totalVotes;
final Set<int> selections;
final List<PollOption> pollOptions;
final PollStatus status;
final Status status;
PollState copyWith({
int? totalVotes,
Set<int>? selections,
List<PollOption>? pollOptions,
PollStatus? status,
Status? status,
}) {
return PollState(
totalVotes: totalVotes ?? this.totalVotes,

View File

@ -1,6 +1,7 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/repositories/repositories.dart';
part 'post_state.dart';
@ -14,31 +15,31 @@ class PostCubit extends Cubit<PostState> {
final PostRepository _postRepository;
Future<void> post({required String text, required int to}) async {
emit(state.copyWith(status: PostStatus.loading));
emit(state.copyWith(status: Status.inProgress));
final bool successful = await _postRepository.comment(
parentId: to,
text: text,
);
if (successful) {
emit(state.copyWith(status: PostStatus.successful));
emit(state.copyWith(status: Status.success));
} else {
emit(state.copyWith(status: PostStatus.failure));
emit(state.copyWith(status: Status.failure));
}
}
Future<void> edit({required String text, required int id}) async {
emit(state.copyWith(status: PostStatus.loading));
emit(state.copyWith(status: Status.inProgress));
final bool successful = await _postRepository.edit(id: id, text: text);
if (successful) {
emit(state.copyWith(status: PostStatus.successful));
emit(state.copyWith(status: Status.success));
} else {
emit(state.copyWith(status: PostStatus.failure));
emit(state.copyWith(status: Status.failure));
}
}
void reset() {
emit(state.copyWith(status: PostStatus.init));
emit(state.copyWith(status: Status.idle));
}
}

View File

@ -1,20 +1,13 @@
part of 'post_cubit.dart';
enum PostStatus {
init,
loading,
successful,
failure,
}
class PostState extends Equatable {
const PostState({required this.status});
const PostState.init() : status = PostStatus.init;
const PostState.init() : status = Status.idle;
final PostStatus status;
final Status status;
PostState copyWith({PostStatus? status}) {
PostState copyWith({Status? status}) {
return PostState(
status: status ?? this.status,
);

View File

@ -1,7 +1,7 @@
part of 'search_cubit.dart';
enum SearchStatus {
initial,
idle,
loading,
loadingMore,
loaded,
@ -15,7 +15,7 @@ class SearchState extends Equatable {
});
SearchState.init()
: status = SearchStatus.initial,
: status = SearchStatus.idle,
results = <Item>[],
params = SearchParams.init();

View File

@ -1,6 +1,7 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/repositories/post_repository.dart';
part 'submit_state.dart';
@ -25,7 +26,7 @@ class SubmitCubit extends Cubit<SubmitState> {
}
void onSubmitTapped() {
emit(state.copyWith(status: SubmitStatus.submitting));
emit(state.copyWith(status: Status.inProgress));
if (state.title?.isNotEmpty ?? false) {
_postRepository
@ -35,9 +36,9 @@ class SubmitCubit extends Cubit<SubmitState> {
text: state.text,
)
.then((bool successful) {
emit(state.copyWith(status: SubmitStatus.submitted));
emit(state.copyWith(status: Status.success));
}).onError((Object? error, StackTrace stackTrace) {
emit(state.copyWith(status: SubmitStatus.failure));
emit(state.copyWith(status: Status.failure));
});
}
}

View File

@ -1,12 +1,5 @@
part of 'submit_cubit.dart';
enum SubmitStatus {
initial,
submitting,
submitted,
failure,
}
class SubmitState extends Equatable {
const SubmitState({
required this.title,
@ -19,18 +12,18 @@ class SubmitState extends Equatable {
: title = null,
url = null,
text = null,
status = SubmitStatus.initial;
status = Status.idle;
final String? title;
final String? url;
final String? text;
final SubmitStatus status;
final Status status;
SubmitState copyWith({
String? title,
String? url,
String? text,
SubmitStatus? status,
Status? status,
}) {
return SubmitState(
title: title ?? this.title,

View File

@ -15,16 +15,16 @@ class UserCubit extends Cubit<UserState> {
final StoriesRepository _storiesRepository;
void init({required String userId}) {
emit(state.copyWith(status: UserStatus.loading));
emit(state.copyWith(status: Status.inProgress));
_storiesRepository.fetchUser(id: userId).then((User? user) {
emit(
state.copyWith(
user: user ?? User.emptyWithId(userId),
status: UserStatus.loaded,
status: Status.success,
),
);
}).onError((_, __) {
emit(state.copyWith(status: UserStatus.failure));
emit(state.copyWith(status: Status.failure));
return;
});
}

View File

@ -1,12 +1,5 @@
part of 'user_cubit.dart';
enum UserStatus {
initial,
loading,
loaded,
failure,
}
class UserState extends Equatable {
const UserState({
required this.user,
@ -15,14 +8,14 @@ class UserState extends Equatable {
const UserState.init()
: user = const User.empty(),
status = UserStatus.initial;
status = Status.idle;
final User user;
final UserStatus status;
final Status status;
UserState copyWith({
User? user,
UserStatus? status,
Status? status,
}) {
return UserState(
user: user ?? this.user,

View File

@ -6,7 +6,7 @@ enum Vote {
}
enum VoteStatus {
initial,
idle,
canceled,
submitted,
failureBeHumble,
@ -24,7 +24,7 @@ class VoteState extends Equatable {
const VoteState.init({required this.item})
: vote = null,
status = VoteStatus.initial;
status = VoteStatus.idle;
/// Null means user has not voted,
/// True means user voted up,

View File

@ -1,6 +1,14 @@
enum Status {
idle,
loading,
loaded,
error,
inProgress,
success,
failure,
}
extension StatusExtension on Status {
bool get isLoading => this == Status.inProgress;
bool get isSuccessful => this == Status.success;
bool get hasError => this == Status.failure;
}

View File

@ -213,7 +213,7 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
listeners: <BlocListener<dynamic, dynamic>>[
BlocListener<PostCubit, PostState>(
listener: (BuildContext context, PostState postState) {
if (postState.status == PostStatus.successful) {
if (postState.status == Status.success) {
Navigator.popUntil(
context,
(Route<dynamic> route) =>
@ -228,7 +228,7 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
showSnackBar(content: msg);
context.read<EditCubit>().onReplySubmittedSuccessfully();
context.read<PostCubit>().reset();
} else if (postState.status == PostStatus.failure) {
} else if (postState.status == Status.failure) {
Navigator.popUntil(
context,
(Route<dynamic> route) =>
@ -355,6 +355,7 @@ class _ItemScreenState extends State<ItemScreen> with RouteAware {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
enableDrag: false,
builder: (BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,

View File

@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/status.dart';
import 'package:hacki/screens/widgets/widgets.dart';
import 'package:hacki/styles/styles.dart';
import 'package:hacki/utils/utils.dart';
@ -32,7 +33,7 @@ class _LoginDialogState extends State<LoginDialog> {
builder: (BuildContext context, AuthState state) {
return SimpleDialog(
children: <Widget>[
if (state.status == AuthStatus.loading)
if (state.status.isLoading)
const SizedBox(
height: Dimens.pt36,
width: Dimens.pt36,
@ -82,7 +83,7 @@ class _LoginDialogState extends State<LoginDialog> {
const SizedBox(
height: Dimens.pt16,
),
if (state.status == AuthStatus.failure)
if (state.status == Status.failure)
Padding(
padding: const EdgeInsets.only(
left: Dimens.pt18,

View File

@ -159,7 +159,7 @@ class MainView extends StatelessWidget {
prev.status != current.status,
builder: (BuildContext context, CommentsState state) {
return AnimatedOpacity(
opacity: state.status == CommentsStatus.loading
opacity: state.status == CommentsStatus.inProgress
? NumSwitch.on
: NumSwitch.off,
duration: const Duration(
@ -417,27 +417,28 @@ class _ParentItemSection extends StatelessWidget {
width: _viewParentButtonWidth,
child: TextButton(
onPressed: context.read<CommentsCubit>().loadParentThread,
child: state.fetchParentStatus == CommentsStatus.loading
? const SizedBox(
height: Dimens.pt12,
width: Dimens.pt12,
child: CustomCircularProgressIndicator(
strokeWidth: Dimens.pt2,
),
)
: const Text(
'View parent',
style: TextStyle(
fontSize: TextDimens.pt13,
),
),
child:
state.fetchParentStatus == CommentsStatus.inProgress
? const SizedBox(
height: Dimens.pt12,
width: Dimens.pt12,
child: CustomCircularProgressIndicator(
strokeWidth: Dimens.pt2,
),
)
: const Text(
'View parent',
style: TextStyle(
fontSize: TextDimens.pt13,
),
),
),
),
SizedBox(
width: _viewRootButtonWidth,
child: TextButton(
onPressed: context.read<CommentsCubit>().loadRootThread,
child: state.fetchRootStatus == CommentsStatus.loading
child: state.fetchRootStatus == CommentsStatus.inProgress
? const SizedBox(
height: Dimens.pt12,
width: Dimens.pt12,

View File

@ -81,7 +81,7 @@ class MorePopupMenu extends StatelessWidget {
child: BlocBuilder<UserCubit, UserState>(
builder: (BuildContext context, UserState state) {
return Semantics(
excludeSemantics: state.status == UserStatus.loading,
excludeSemantics: state.status == Status.inProgress,
child: ListTile(
leading: const Icon(
Icons.account_circle,

View File

@ -25,7 +25,7 @@ class _PollViewState extends State<PollView> {
const SizedBox(
height: Dimens.pt24,
),
if (state.status == PollStatus.loading) ...<Widget>[
if (state.status == Status.inProgress) ...<Widget>[
const LinearProgressIndicator(),
const SizedBox(
height: Dimens.pt24,

View File

@ -5,7 +5,7 @@ import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:hacki/config/constants.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/item/item.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/screens/screens.dart';
import 'package:hacki/screens/widgets/widgets.dart';
import 'package:hacki/styles/styles.dart';
@ -35,11 +35,12 @@ class _ReplyBoxState extends State<ReplyBox> {
bool expanded = false;
double? expandedHeight;
static const double _collapsedHeight = 100;
static const double collapsedHeight = 140;
@override
Widget build(BuildContext context) {
expandedHeight ??= MediaQuery.of(context).size.height;
expandedHeight ??= MediaQuery.of(context).size.height -
MediaQuery.of(context).viewInsets.bottom;
return BlocBuilder<EditCubit, EditState>(
buildWhen: (EditState previous, EditState current) =>
previous.showReplyBox != current.showReplyBox ||
@ -49,7 +50,7 @@ class _ReplyBoxState extends State<ReplyBox> {
return BlocBuilder<PostCubit, PostState>(
builder: (BuildContext context, PostState postState) {
final Item? replyingTo = editState.replyingTo;
final bool isLoading = postState.status == PostStatus.loading;
final bool isLoading = postState.status.isLoading;
return Padding(
padding: EdgeInsets.only(
@ -60,7 +61,7 @@ class _ReplyBoxState extends State<ReplyBox> {
: Dimens.zero,
),
child: AnimatedContainer(
height: expanded ? expandedHeight : _collapsedHeight,
height: expanded ? expandedHeight : collapsedHeight,
duration: Durations.ms200,
decoration: BoxDecoration(
boxShadow: <BoxShadow>[
@ -73,6 +74,7 @@ class _ReplyBoxState extends State<ReplyBox> {
),
child: Material(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (context.read<SplitViewCubit>().state.enabled)
const Divider(
@ -213,13 +215,21 @@ class _ReplyBoxState extends State<ReplyBox> {
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Dimens.pt16,
padding: EdgeInsets.only(
left: Dimens.pt16,
right: Dimens.pt16,
bottom: expanded
// This padding here prevents keyboard
// overlapping with TextField.
? MediaQuery.of(context).viewInsets.bottom +
Dimens.pt16
: Dimens.zero,
),
child: TextField(
autofocus: true,
controller: widget.textEditingController,
maxLines: 100,
autofocus: true,
expands: true,
maxLines: null,
decoration: const InputDecoration(
alignLabelWithHint: true,
contentPadding: EdgeInsets.zero,

View File

@ -53,7 +53,7 @@ class _ProfileScreenState extends State<ProfileScreen>
previous.status != current.status,
listener:
(BuildContext context, NotificationState notificationState) {
if (notificationState.status == NotificationStatus.loaded) {
if (notificationState.status == Status.success) {
refreshControllerNotification
..refreshCompleted()
..loadComplete();
@ -71,7 +71,7 @@ class _ProfileScreenState extends State<ProfileScreen>
BuildContext context,
HistoryState historyState,
) {
if (historyState.status == HistoryStatus.loaded) {
if (historyState.status == Status.success) {
refreshControllerHistory
..refreshCompleted()
..loadComplete();
@ -83,7 +83,7 @@ class _ProfileScreenState extends State<ProfileScreen>
) {
if ((!authState.isLoggedIn ||
historyState.submittedItems.isEmpty) &&
historyState.status != HistoryStatus.loading) {
historyState.status != Status.inProgress) {
return const CenteredMessageView(
content: 'Your past comments and stories will '
'show up here.',
@ -126,7 +126,7 @@ class _ProfileScreenState extends State<ProfileScreen>
visible: pageType == PageType.fav,
child: BlocConsumer<FavCubit, FavState>(
listener: (BuildContext context, FavState favState) {
if (favState.status == FavStatus.loaded) {
if (favState.status == Status.success) {
refreshControllerFav
..refreshCompleted()
..loadComplete();
@ -134,7 +134,7 @@ class _ProfileScreenState extends State<ProfileScreen>
},
builder: (BuildContext context, FavState favState) {
if (favState.favItems.isEmpty &&
favState.status != FavStatus.loading) {
favState.status != Status.inProgress) {
return const CenteredMessageView(
content: 'Your favorite stories will show up here.'
'\nThey will be synced to your Hacker '

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart';
import 'package:hacki/styles/styles.dart';
import 'package:hacki/utils/utils.dart';
@ -43,13 +44,13 @@ class _SubmitScreenState extends State<SubmitScreen> {
listenWhen: (SubmitState previous, SubmitState current) =>
previous.status != current.status,
listener: (BuildContext context, SubmitState state) {
if (state.status == SubmitStatus.submitted) {
if (state.status == Status.success) {
Navigator.pop(context);
HapticFeedbackUtil.light();
showSnackBar(
content: 'Post submitted successfully.',
);
} else if (state.status == SubmitStatus.failure) {
} else if (state.status == Status.failure) {
showErrorSnackBar();
}
},
@ -95,7 +96,7 @@ class _SubmitScreenState extends State<SubmitScreen> {
'Submit',
),
actions: <Widget>[
if (state.status == SubmitStatus.submitting)
if (state.status == Status.inProgress)
const Padding(
padding: EdgeInsets.symmetric(
vertical: Dimens.pt18,

View File

@ -49,7 +49,7 @@ class _StoriesListViewState extends State<StoriesListView> {
previous.statusByType[storyType] !=
current.statusByType[storyType],
listener: (BuildContext context, StoriesState state) {
if (state.statusByType[storyType] == StoriesStatus.loaded) {
if (state.statusByType[storyType] == Status.success) {
refreshController
..refreshCompleted(resetFooterState: true)
..loadComplete();

View File

@ -1414,4 +1414,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.13.0"
flutter: ">=3.13.2"

View File

@ -1,11 +1,11 @@
name: hacki
description: A Hacker News reader.
version: 1.8.3+119
version: 1.8.4+120
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: "3.13.0"
flutter: "3.13.2"
dependencies:
adaptive_theme: ^3.2.0

View File

@ -85,7 +85,7 @@ void main() {
},
expect: () => <AuthState>[
const AuthState.init().copyWith(
status: AuthStatus.loaded,
status: Status.success,
),
],
verify: (_) {
@ -125,25 +125,25 @@ void main() {
const AuthState(
user: User.empty(),
isLoggedIn: false,
status: AuthStatus.loaded,
status: Status.success,
agreedToEULA: false,
),
const AuthState(
user: User.empty(),
isLoggedIn: false,
status: AuthStatus.loaded,
status: Status.success,
agreedToEULA: true,
),
const AuthState(
user: User.empty(),
isLoggedIn: false,
status: AuthStatus.loading,
status: Status.inProgress,
agreedToEULA: true,
),
const AuthState(
user: tUser,
isLoggedIn: true,
status: AuthStatus.loaded,
status: Status.success,
agreedToEULA: true,
),
],