diff --git a/lib/providers/folder_detail_provider.dart b/lib/providers/folder_detail_provider.dart new file mode 100644 index 0000000..406689b --- /dev/null +++ b/lib/providers/folder_detail_provider.dart @@ -0,0 +1,43 @@ +import 'dart:async'; + +import 'package:moa_app/models/content_model.dart'; +import 'package:moa_app/repositories/folder_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'folder_detail_provider.g.dart'; + +/// AsyncNotifierProvider +// @Riverpod(keepAlive: true) +@riverpod +class FolderDetail extends _$FolderDetail { + Future> fetchItem({required String folderName}) async { + // get the [KeepAliveLink] + var link = ref.keepAlive(); + // a timer to be used by the callbacks below + Timer? timer; + // An object from package:dio that allows cancelling http requests + // When the provider is destroyed, cancel the http request and the timer + ref.onDispose(() { + timer?.cancel(); + }); + // When the last listener is removed, start a timer to dispose the cached data + ref.onCancel(() { + // start a 30 second timer + timer = Timer(const Duration(seconds: 30), () { + // dispose on timeout + link.close(); + }); + }); + // If the provider is listened again after it was paused, cancel the timer + ref.onResume(() { + timer?.cancel(); + }); + + var data = + FolderRepository.instance.getFolderDetailList(folderName: folderName); + return data; + } + + @override + Future build() async {} +} diff --git a/lib/providers/folder_view_provider.dart b/lib/providers/folder_view_provider.dart index 95e8ba2..a44a8c1 100644 --- a/lib/providers/folder_view_provider.dart +++ b/lib/providers/folder_view_provider.dart @@ -33,7 +33,7 @@ class FolderView extends _$FolderView { timer?.cancel(); }); - var data = FolderRepository.instance.getFolderList(); + var data = await FolderRepository.instance.getFolderList(); return data; } @@ -41,4 +41,64 @@ class FolderView extends _$FolderView { Future> build() async { return fetchItem(); } + + Future addFolder({ + required String folderName, + }) async { + state = const AsyncValue.loading(); + + state = await AsyncValue.guard(() async { + // await FolderRepository.instance.addFolder(folderName: folderName); + return fetchItem(); + // return [ + // ...state.value ?? [], + // FolderModel( + // folderId: 0, + // folderName: folderName, + // count: 0, + // ) + // ]; + }); + } + + Future editFolderName({ + required String currentFolderName, + required String editFolderName, + }) async { + state = const AsyncValue.loading(); + + state = await AsyncValue.guard(() async { + // await FolderRepository.instance.editFolderName( + // currentFolderName: currentFolderName, + // editFolderName: editFolderName, + // ); + return fetchItem(); + + // return state.value?.map((element) { + // if (element.folderName == currentFolderName) { + // return FolderModel( + // folderId: element.folderId, + // folderName: editFolderName, + // count: element.count, + // ); + // } + // return element; + // }).toList() ?? + // []; + }); + } + + Future deleteFolder({required String folderName}) async { + state = const AsyncValue.loading(); + + state = await AsyncValue.guard(() async { + // await FolderRepository.instance.deleteFolder(folderName: folderName); + + return fetchItem(); + // return state.value + // ?.where((element) => element.folderName != folderName) + // .toList() ?? + // []; + }); + } } diff --git a/lib/providers/hashtag_view_provider.dart b/lib/providers/hashtag_view_provider.dart index 57f9bc6..8a2d31b 100644 --- a/lib/providers/hashtag_view_provider.dart +++ b/lib/providers/hashtag_view_provider.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:moa_app/models/content_model.dart'; +import 'package:moa_app/repositories/content_repository.dart'; import 'package:moa_app/repositories/hashtag_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -11,27 +12,27 @@ part 'hashtag_view_provider.g.dart'; @riverpod class HashtagView extends _$HashtagView { Future<(List, int)> fetchItem() async { - // get the [KeepAliveLink] - var link = ref.keepAlive(); - // a timer to be used by the callbacks below - Timer? timer; - // An object from package:dio that allows cancelling http requests - // When the provider is destroyed, cancel the http request and the timer - ref.onDispose(() { - timer?.cancel(); - }); - // When the last listener is removed, start a timer to dispose the cached data - ref.onCancel(() { - // start a 30 second timer - timer = Timer(const Duration(seconds: 30), () { - // dispose on timeout - link.close(); - }); - }); - // If the provider is listened again after it was paused, cancel the timer - ref.onResume(() { - timer?.cancel(); - }); + // // get the [KeepAliveLink] + // var link = ref.keepAlive(); + // // a timer to be used by the callbacks below + // Timer? timer; + // // An object from package:dio that allows cancelling http requests + // // When the provider is destroyed, cancel the http request and the timer + // ref.onDispose(() { + // timer?.cancel(); + // }); + // // When the last listener is removed, start a timer to dispose the cached data + // ref.onCancel(() { + // // start a 30 second timer + // timer = Timer(const Duration(seconds: 30), () { + // // dispose on timeout + // link.close(); + // }); + // }); + // // If the provider is listened again after it was paused, cancel the timer + // ref.onResume(() { + // timer?.cancel(); + // }); var data = await HashtagRepository.instance.getHashtagView(); return data; @@ -42,6 +43,24 @@ class HashtagView extends _$HashtagView { return fetchItem(); } + Future deleteContent({required String contentId, required}) async { + state = const AsyncValue.loading(); + + state = await AsyncValue.guard(() async { + await ContentRepository.instance.deleteContent(contentId: contentId); + var value = state.value; + if (value != null) { + var (list, count) = value; + var filteredList = + list.where((element) => element.contentId != contentId).toList(); + + return (filteredList, count); + } + + return value!; + }); + } + Future<(List, int)> loadMoreData({ int? page, int? size, diff --git a/lib/repositories/content_repository.dart b/lib/repositories/content_repository.dart index aa7100c..00c657e 100644 --- a/lib/repositories/content_repository.dart +++ b/lib/repositories/content_repository.dart @@ -15,6 +15,16 @@ abstract class IContentRepository { Future getContentDetail({ required String contentId, }); + + Future editContent({ + required String contentId, + required ContentModel content, + required String hashTagStringList, + }); + + Future deleteContent({ + required String contentId, + }); } class ContentRepository implements IContentRepository { @@ -100,4 +110,30 @@ class ContentRepository implements IContentRepository { rethrow; } } + + @override + Future editContent({ + required String contentId, + required ContentModel content, + required String hashTagStringList, + }) async { + throw UnimplementedError(); + } + + @override + Future deleteContent({required String contentId}) async { + var token = await TokenRepository.instance.getToken(); + + await dio.post( + '/api/v1/content/delete', + data: { + 'ids': [contentId] + }, + options: Options( + headers: { + 'Authorization': 'Bearer $token', + }, + ), + ); + } } diff --git a/lib/screens/home/content_view.dart b/lib/screens/home/content_view.dart index 7f28064..d5a2557 100644 --- a/lib/screens/home/content_view.dart +++ b/lib/screens/home/content_view.dart @@ -7,6 +7,7 @@ import 'package:moa_app/constants/file_constants.dart'; import 'package:moa_app/constants/font_constants.dart'; import 'package:moa_app/models/content_model.dart'; import 'package:moa_app/providers/content_detail_provider.dart'; +import 'package:moa_app/providers/hashtag_view_provider.dart'; import 'package:moa_app/screens/home/edit_content_view.dart'; import 'package:moa_app/utils/general.dart'; import 'package:moa_app/widgets/app_bar.dart'; @@ -27,6 +28,7 @@ class ContentView extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { var contentNotifier = ref.watch(contentDetailProvider.notifier); + var hashtagAsync = ref.watch(hashtagViewProvider.notifier); var isEditMode = useState(false); void pressGoToLink() {} @@ -37,7 +39,12 @@ class ContentView extends HookConsumerWidget { isEditMode.value = true; } - void deleteContent() {} + void deleteContent() async { + await hashtagAsync.deleteContent(contentId: id); + if (context.mounted) { + context.pop(); + } + } void showContentModal() { General.instance.showBottomSheet( @@ -149,6 +156,7 @@ class ContentView extends HookConsumerWidget { ), child: ImageOnNetwork( imageURL: content.contentImageUrl, + fit: BoxFit.contain, ), ), ), diff --git a/lib/screens/home/folder_detail_view.dart b/lib/screens/home/folder_detail_view.dart index 4d2ab78..520f8db 100644 --- a/lib/screens/home/folder_detail_view.dart +++ b/lib/screens/home/folder_detail_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:moa_app/constants/color_constants.dart'; import 'package:moa_app/constants/file_constants.dart'; import 'package:moa_app/models/content_model.dart'; @@ -13,12 +13,12 @@ import 'package:moa_app/widgets/moa_widgets/dynamic_grid_list.dart'; import 'package:moa_app/widgets/moa_widgets/empty_content.dart'; import 'package:share_plus/share_plus.dart'; -class FolderDetailView extends HookWidget { +class FolderDetailView extends HookConsumerWidget { const FolderDetailView({super.key, required this.folderName}); final String folderName; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { Future pullToRefresh() async { return Future.delayed( const Duration(seconds: 2), @@ -57,9 +57,11 @@ class FolderDetailView extends HookWidget { .getFolderDetailList(folderName: folderName), builder: (context, snapshot) { var contentList = snapshot.data ?? []; + if (snapshot.connectionState == ConnectionState.waiting) { return const LoadingIndicator(); } + if (snapshot.hasData && contentList.isNotEmpty) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 15), diff --git a/lib/screens/home/hashtag_detail_view.dart b/lib/screens/home/hashtag_detail_view.dart index 6ceebda..d48c4f0 100644 --- a/lib/screens/home/hashtag_detail_view.dart +++ b/lib/screens/home/hashtag_detail_view.dart @@ -21,7 +21,6 @@ class HashtagDetailView extends HookWidget { Widget build(BuildContext context) { var gridController = useScrollController(); var gridPageNum = useState(20); - var updatedHashtagName = useState(''); Future pullToRefresh() async { @@ -31,10 +30,6 @@ class HashtagDetailView extends HookWidget { ); } - // Future getHashtagDetailViewList() async { - // hashTagList.value = await - // } - void showEditHashtagModal() { General.instance.showBottomSheet( context: context, diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index f91147b..b366cbe 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -26,6 +26,7 @@ class Home extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { var folderAsync = ref.watch(folderViewProvider.notifier); var hashtagAsync = ref.watch(hashtagViewProvider.notifier); + var isClick = ref.watch(buttonClickStateProvider); var tabIdx = useState(0); TabController tabController = useTabController(initialLength: 2); @@ -42,7 +43,7 @@ class Home extends HookConsumerWidget { tabIdx.value = 1; } }); - return null; + return () => tabController.dispose(); }, []); return Container( @@ -52,6 +53,7 @@ class Home extends HookConsumerWidget { body: DefaultTabController( length: 2, child: ExtendedNestedScrollView( + physics: const BouncingScrollPhysics(), onlyOneScrollInBody: true, floatHeaderSlivers: true, headerSliverBuilder: (context, innerBoxIsScrolled) { @@ -108,7 +110,6 @@ class Home extends HookConsumerWidget { ]; }, body: TabBarView( - // physics: const NeverScrollableScrollPhysics(), controller: tabController, children: [ TabViewItem( @@ -268,9 +269,14 @@ class PersistentTabBar extends SliverPersistentHeaderDelegate { } class FolderSource extends LoadingMoreBase { - FolderSource({required this.folderCount, required this.futureList}); + FolderSource( + {required this.folderCount, + required this.futureList, + required this.context}); final ValueNotifier folderCount; final FolderView? futureList; + final BuildContext context; + var count = 0; int pageIndex = 1; bool _hasMore = false; @@ -309,10 +315,9 @@ class FolderSource extends LoadingMoreBase { _hasMore = false; pageIndex++; isSuccess = true; - } catch (e) { + } catch (error) { isSuccess = false; - - logger.d(e); + logger.d(error); } return isSuccess; } @@ -322,6 +327,7 @@ class HashtagSource extends LoadingMoreBase { HashtagSource({required this.contentCount, required this.futureList}); final ValueNotifier contentCount; final HashtagView? futureList; + int pageIndex = 1; int size = 10; bool _hasMore = true; @@ -346,7 +352,6 @@ class HashtagSource extends LoadingMoreBase { @override Future loadData([bool isloadMoreAction = false]) async { bool isSuccess = false; - try { if (pageIndex == 0) { var (initialList, count) = await futureList!.future; @@ -399,6 +404,7 @@ class TabViewItem extends StatefulWidget { class TabViewItemState extends State with AutomaticKeepAliveClientMixin { late final FolderSource folderSource = FolderSource( + context: context, futureList: widget.folderList, folderCount: widget.folderCount, ); diff --git a/lib/screens/home/tab_view/folder_tab_view.dart b/lib/screens/home/tab_view/folder_tab_view.dart index 5ee9bc7..930c0c4 100644 --- a/lib/screens/home/tab_view/folder_tab_view.dart +++ b/lib/screens/home/tab_view/folder_tab_view.dart @@ -1,16 +1,18 @@ +import 'package:dio/dio.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:loading_more_list/loading_more_list.dart'; import 'package:moa_app/constants/app_constants.dart'; import 'package:moa_app/constants/file_constants.dart'; import 'package:moa_app/models/folder_model.dart'; +import 'package:moa_app/providers/folder_view_provider.dart'; import 'package:moa_app/repositories/folder_repository.dart'; import 'package:moa_app/screens/home/home.dart'; import 'package:moa_app/utils/general.dart'; -import 'package:moa_app/utils/logger.dart'; import 'package:moa_app/utils/router_provider.dart'; import 'package:moa_app/widgets/loading_indicator.dart'; import 'package:moa_app/widgets/moa_widgets/add_folder.dart'; @@ -20,14 +22,14 @@ import 'package:moa_app/widgets/moa_widgets/edit_content.dart'; import 'package:moa_app/widgets/moa_widgets/folder_list.dart'; import 'package:moa_app/widgets/snackbar.dart'; -class FolderTabView extends HookWidget { +class FolderTabView extends HookConsumerWidget { const FolderTabView( {super.key, required this.uniqueKey, required this.source}); final Key uniqueKey; final FolderSource source; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { var width = MediaQuery.of(context).size.width; var updatedContentName = useState(''); @@ -59,17 +61,21 @@ class FolderTabView extends HookWidget { onPressed: () async { try { await FolderRepository.instance.editFolderName( - currentFolderName: folderName, - editFolderName: updatedContentName.value, - ); + currentFolderName: folderName, + editFolderName: updatedContentName.value); + await ref.read(folderViewProvider.notifier).editFolderName( + currentFolderName: folderName, + editFolderName: updatedContentName.value, + ); await source.refresh(true); - // todo 폴더명 중복 체크후 중복이면 에러메세지 - // if 중복이면 - // return '이미 가지고 있는 폴더이름이에요!'; - // 중복 아니면 - return ''; - } catch (error) { - logger.d(error); + if (context.mounted) { + context.pop(); + } + } on DioError catch (error) { + // todo 폴더 중복 에러 처리 + if (error.response!.statusCode == 409) { + snackbar.alert(context, '이미 가지고 있는 폴더이름이에요!'); + } } }, ), @@ -91,10 +97,15 @@ class FolderTabView extends HookWidget { try { await FolderRepository.instance .deleteFolder(folderName: folderName); + await ref.read(folderViewProvider.notifier).deleteFolder( + folderName: folderName, + ); await source.refresh(true); } catch (error) { - snackbar.alert(context, - kDebugMode ? error.toString() : '오류가 발생했어요 다시 시도해주세요.'); + if (context.mounted) { + snackbar.alert(context, + kDebugMode ? error.toString() : '오류가 발생했어요 다시 시도해주세요.'); + } } }, ), @@ -137,11 +148,13 @@ class FolderTabView extends HookWidget { return ExtendedVisibilityDetector( uniqueKey: uniqueKey, child: RefreshIndicator( - onRefresh: () { - return source.refresh(true); + onRefresh: () async { + ref.refresh(folderViewProvider).value; + await source.refresh(true); }, child: LoadingMoreList( ListConfig( + physics: const BouncingScrollPhysics(), addRepaintBoundaries: true, padding: const EdgeInsets.only( left: 15, diff --git a/lib/screens/home/tab_view/hashtag_tab_view.dart b/lib/screens/home/tab_view/hashtag_tab_view.dart index 84cd817..e88d913 100644 --- a/lib/screens/home/tab_view/hashtag_tab_view.dart +++ b/lib/screens/home/tab_view/hashtag_tab_view.dart @@ -1,22 +1,23 @@ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:loading_more_list/loading_more_list.dart'; import 'package:moa_app/constants/app_constants.dart'; import 'package:moa_app/constants/file_constants.dart'; import 'package:moa_app/models/content_model.dart'; +import 'package:moa_app/providers/hashtag_view_provider.dart'; import 'package:moa_app/screens/home/content_view.dart'; import 'package:moa_app/screens/home/home.dart'; import 'package:moa_app/screens/home/widgets/content_card.dart'; import 'package:moa_app/screens/home/widgets/type_header.dart'; import 'package:moa_app/utils/router_provider.dart'; -import 'package:moa_app/utils/utils.dart'; import 'package:moa_app/widgets/button.dart'; import 'package:moa_app/widgets/edit_text.dart'; +import 'package:moa_app/widgets/image.dart'; import 'package:moa_app/widgets/loading_indicator.dart'; -class HashtagTabView extends HookWidget { +class HashtagTabView extends HookConsumerWidget { const HashtagTabView({ super.key, required this.uniqueKey, @@ -28,7 +29,7 @@ class HashtagTabView extends HookWidget { final int count; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { var width = MediaQuery.of(context).size.width; void goContentView(String contentId) { @@ -39,9 +40,9 @@ class HashtagTabView extends HookWidget { } void goHashtagDetailView(String tag) { - context.go( - '${GoRoutes.hashtag.fullPath}/$tag', - ); + // context.go( + // '${GoRoutes.hashtag.fullPath}/$tag', + // ); } return Column( @@ -77,6 +78,7 @@ class HashtagTabView extends HookWidget { uniqueKey: uniqueKey, child: RefreshIndicator( onRefresh: () { + ref.refresh(hashtagViewProvider).value; return source.refresh(true); }, child: LoadingMoreList( @@ -90,6 +92,7 @@ class HashtagTabView extends HookWidget { return false; }, ListConfig( + physics: const BouncingScrollPhysics(), addRepaintBoundaries: true, padding: const EdgeInsets.only( left: 15, @@ -112,24 +115,26 @@ class HashtagTabView extends HookWidget { return const SizedBox(); }, itemBuilder: (c, item, index) { - return FutureBuilder( - future: getImageSize(imageURL: item.contentImageUrl), - builder: (context, snapshot) { - var rate = snapshot.data?.toDouble() ?? 1.4; - - return AspectRatio( - aspectRatio: rate == 1.9 - ? 0.6 - : rate == 1.2 - ? 0.95 - : 0.7, - child: ContentCard( - onPressContent: () => goContentView(item.contentId), + return InkWell( + splashColor: Colors.transparent, + borderRadius: BorderRadius.circular(10), + onTap: () => goContentView(item.contentId), + child: Column( + children: [ + item.contentImageUrl == '' + ? const Text('이미지 없을 경우 모아 이미지로 대체') + : AspectRatio( + aspectRatio: 1, + child: ImageOnNetwork( + imageURL: item.contentImageUrl, + ), + ), + ContentCard( content: item, onPressHashtag: (tag) => goHashtagDetailView(tag), ), - ); - }, + ], + ), ); }, ), diff --git a/lib/screens/home/widgets/content_card.dart b/lib/screens/home/widgets/content_card.dart index 85f1ea4..a9dab95 100644 --- a/lib/screens/home/widgets/content_card.dart +++ b/lib/screens/home/widgets/content_card.dart @@ -9,11 +9,9 @@ class ContentCard extends HookWidget { const ContentCard({ super.key, required this.content, - required this.onPressContent, required this.onPressHashtag, }); final ContentModel content; - final Function() onPressContent; final Function(String) onPressHashtag; @override @@ -22,62 +20,45 @@ class ContentCard extends HookWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), ), - child: InkWell( - splashColor: Colors.transparent, - borderRadius: BorderRadius.circular(10), - onTap: onPressContent, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: content.contentImageUrl == '' - ? const Text('이미지 없을 경우 모아 이미지로 대체') - : Container( - width: double.infinity, - decoration: BoxDecoration( - border: Border.all(color: AppColors.moaOpacity30), - borderRadius: BorderRadius.circular(10), - image: DecorationImage( - image: NetworkImage(content.contentImageUrl), - fit: BoxFit.contain, - ), - ), - ), - ), - const SizedBox(height: 10), - Text( - content.contentName, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, - fontFamily: FontConstants.pretendard, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Text( + content.contentName, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + fontFamily: FontConstants.pretendard, ), - const SizedBox(height: 2), - Text( - content.contentMemo ?? '', - style: const TextStyle( - fontSize: 14, - fontFamily: FontConstants.pretendard, - color: AppColors.moaDescription, - ), + ), + const SizedBox(height: 2), + Text( + content.contentMemo ?? '', + style: const TextStyle( + fontSize: 14, + fontFamily: FontConstants.pretendard, + color: AppColors.moaDescription, ), - const SizedBox(height: 15), - Row( + ), + SizedBox(height: content.contentMemo == null ? 0 : 15), + SizedBox( + width: double.infinity, + child: Wrap( + spacing: 10, + runSpacing: 10, + crossAxisAlignment: WrapCrossAlignment.center, children: [ ...content.contentHashTag.map((tag) { - return Container( - margin: const EdgeInsets.only(right: 5), - child: HashtagButton( - onPress: () => onPressHashtag(tag.hashTag), - text: tag.hashTag, - ), + return HashtagButton( + onPress: () => onPressHashtag(tag.hashTag), + text: tag.hashTag, ); }).toList(), ], - ) - ], - ), + ), + ) + ], ), ); } diff --git a/lib/screens/home/widgets/hashtag_button.dart b/lib/screens/home/widgets/hashtag_button.dart index eac3027..fc9c852 100644 --- a/lib/screens/home/widgets/hashtag_button.dart +++ b/lib/screens/home/widgets/hashtag_button.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:moa_app/constants/color_constants.dart'; import 'package:moa_app/constants/font_constants.dart'; -import 'package:moa_app/widgets/button.dart'; class HashtagButton extends StatelessWidget { const HashtagButton({super.key, this.onPress, required this.text}); @@ -10,19 +9,36 @@ class HashtagButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Button( + return Container( height: 22, - onPress: onPress, - text: text, - textStyle: const TextStyle( - color: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w700, - fontFamily: FontConstants.pretendard, + decoration: BoxDecoration( + color: AppColors.moaSecondary, + borderRadius: BorderRadius.circular(50), ), padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5), - backgroundColor: AppColors.moaSecondary, - borderRadius: 50, + child: Text( + text, + style: const TextStyle( + color: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w700, + fontFamily: FontConstants.pretendard, + ), + ), ); + // return Button( + // height: 22, + // onPress: onPress, + // text: text, + // textStyle: const TextStyle( + // color: AppColors.blackColor, + // fontSize: 10, + // fontWeight: FontWeight.w700, + // fontFamily: FontConstants.pretendard, + // ), + // padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5), + // backgroundColor: AppColors.moaSecondary, + // borderRadius: 50, + // ); } } diff --git a/lib/utils/router_provider.dart b/lib/utils/router_provider.dart index d261bf8..8c06821 100644 --- a/lib/utils/router_provider.dart +++ b/lib/utils/router_provider.dart @@ -130,9 +130,12 @@ final routeProvider = Provider( } var token = ref.read(tokenStateProvider); + if (token.value == null) { + return GoRoutes.signIn.fullPath; + } + var user = await UserRepository.instance.getUser(); - if ((token.value == null || user?.nickname == null) && - state.matchedLocation != GoRoutes.signIn.fullPath) { + if (user?.nickname == null) { return GoRoutes.signIn.fullPath; } return null; diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0eb085d..18f2481 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -11,7 +11,7 @@ Future xFileToBase64(XFile xFile) async { return base64Encode(bytes); } -Future getImageSize({required String imageURL}) async { +Future getImageSize({required String imageURL}) async { if (imageURL == '') { return 1.4; } @@ -24,10 +24,10 @@ Future getImageSize({required String imageURL}) async { var imageRate = size.width / size.height; return imageRate > 1.9 - ? 1.9 + ? 1 : imageRate < 1.2 - ? 1.2 - : 1.4; + ? 0.8 + : 1.2; } bool isStringEncoded(String value) { diff --git a/lib/widgets/image.dart b/lib/widgets/image.dart index c0ba579..043c841 100644 --- a/lib/widgets/image.dart +++ b/lib/widgets/image.dart @@ -38,14 +38,18 @@ class ImageOnNetwork extends HookWidget { const ImageOnNetwork({ super.key, required this.imageURL, - this.borderRadius = 25, + this.borderRadius = 10, this.height = 50, this.width = 50, + this.border, + this.fit = BoxFit.cover, }); final String imageURL; final double width; final double height; final double borderRadius; + final Border? border; + final BoxFit? fit; @override Widget build(BuildContext context) { @@ -53,16 +57,18 @@ class ImageOnNetwork extends HookWidget { ? ImagePlaceholder( borderRadius: borderRadius, width: width, height: height) : CachedNetworkImage( - placeholder: (context, url) => const ImagePlaceholder(), + placeholder: (context, url) => ImagePlaceholder( + borderRadius: borderRadius, width: width, height: height), imageUrl: imageURL, imageBuilder: (context, imageProvider) => Container( height: height, width: width, decoration: BoxDecoration( + border: border ?? Border.all(color: AppColors.moaOpacity30), borderRadius: BorderRadius.all(Radius.circular(borderRadius)), image: DecorationImage( image: imageProvider, - fit: BoxFit.contain, + fit: fit, ), ), ), diff --git a/lib/widgets/moa_widgets/add_folder.dart b/lib/widgets/moa_widgets/add_folder.dart index 63f8c89..19e6a61 100644 --- a/lib/widgets/moa_widgets/add_folder.dart +++ b/lib/widgets/moa_widgets/add_folder.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:moa_app/constants/color_constants.dart'; import 'package:moa_app/constants/file_constants.dart'; import 'package:moa_app/constants/font_constants.dart'; +import 'package:moa_app/providers/folder_view_provider.dart'; import 'package:moa_app/repositories/folder_repository.dart'; import 'package:moa_app/utils/logger.dart'; import 'package:moa_app/widgets/button.dart'; @@ -12,12 +14,13 @@ import 'package:moa_app/widgets/snackbar.dart'; final GlobalKey formKey = GlobalKey(); -class AddFolder extends HookWidget { +class AddFolder extends HookConsumerWidget { const AddFolder({super.key, required this.onRefresh}); final Function onRefresh; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + var folderAsync = ref.watch(folderViewProvider); var folderNameController = useTextEditingController(); var folderName = useState(''); var loading = useState(false); @@ -32,6 +35,10 @@ class AddFolder extends HookWidget { } void addFolder() async { + if ((folderAsync.value?.length ?? 0) >= 20) { + snackbar.alert(context, '폴더는 최대 20개까지 추가할 수 있습니다.'); + return; + } if (folderNameController.text.isEmpty) { return; } @@ -39,6 +46,9 @@ class AddFolder extends HookWidget { try { loading.value = true; await FolderRepository.instance.addFolder(folderName: folderName.value); + await ref + .read(folderViewProvider.notifier) + .addFolder(folderName: folderName.value); onRefresh(); if (context.mounted) { context.pop(); @@ -46,6 +56,8 @@ class AddFolder extends HookWidget { emptyFolderName(); } catch (e) { logger.d(e); + + // todo 폴더 중복 에러 처리 snackbar.alert(context, '폴더 추가에 실패했습니다.'); } finally { loading.value = false; diff --git a/lib/widgets/moa_widgets/dynamic_grid_list.dart b/lib/widgets/moa_widgets/dynamic_grid_list.dart index 5b78518..1bd3a57 100644 --- a/lib/widgets/moa_widgets/dynamic_grid_list.dart +++ b/lib/widgets/moa_widgets/dynamic_grid_list.dart @@ -3,11 +3,13 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:go_router/go_router.dart'; import 'package:moa_app/constants/app_constants.dart'; +import 'package:moa_app/constants/color_constants.dart'; import 'package:moa_app/models/content_model.dart'; import 'package:moa_app/screens/home/content_view.dart'; import 'package:moa_app/screens/home/widgets/content_card.dart'; import 'package:moa_app/utils/router_provider.dart'; import 'package:moa_app/utils/utils.dart'; +import 'package:moa_app/widgets/image.dart'; class DynamicGridList extends HookWidget { const DynamicGridList({ @@ -42,31 +44,67 @@ class DynamicGridList extends HookWidget { children: [ ...List.generate( contentList.length, - (i) => FutureBuilder( - future: getImageSize(imageURL: contentList[i].contentImageUrl), - builder: (context, snapshot) { - var rate = snapshot.data ?? 1.4; + (i) => InkWell( + splashColor: Colors.transparent, + borderRadius: BorderRadius.circular(10), + onTap: () => goContentView(contentList[i].contentId), + child: Column( + children: [ + FutureBuilder( + future: getImageSize( + imageURL: contentList[i].contentImageUrl), + builder: (context, snapshot) { + var rate = snapshot.data; - if (snapshot.hasData) { - return StaggeredGridTile.count( - crossAxisCellCount: 1, - mainAxisCellCount: rate, - child: ContentCard( - onPressContent: () => - goContentView(contentList[i].contentId), - onPressHashtag: (tag) {}, - content: ContentModel( - contentId: contentList[i].contentId, - contentImageUrl: contentList[i].contentImageUrl, - contentName: contentList[i].contentName, - contentMemo: contentList[i].contentMemo, - contentHashTag: contentList[i].contentHashTag, - ), + return AnimatedSwitcher( + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, child: child); + }, + duration: const Duration(milliseconds: 100), + child: () { + if (snapshot.connectionState == + ConnectionState.waiting) { + return const AspectRatio( + aspectRatio: 1, + child: ImagePlaceholder(borderRadius: 10), + ); + } + + if (snapshot.hasData) { + return contentList[i].contentImageUrl == '' + ? const Text('이미지 없을 경우 모아 이미지로 대체') + : AspectRatio( + aspectRatio: rate!, + child: ImageOnNetwork( + width: double.infinity, + border: Border.all( + color: AppColors.moaOpacity30, + width: 0.5, + ), + borderRadius: 10, + imageURL: + contentList[i].contentImageUrl, + ), + ); + } + return const SizedBox(); + }(), + ); + }, + ), + ContentCard( + onPressHashtag: (tag) {}, + content: ContentModel( + contentId: contentList[i].contentId, + contentImageUrl: contentList[i].contentImageUrl, + contentName: contentList[i].contentName, + contentMemo: contentList[i].contentMemo, + contentHashTag: contentList[i].contentHashTag, ), - ); - } - return const SizedBox(); - }, + ), + ], + ), ), ) ], diff --git a/lib/widgets/moa_widgets/edit_content.dart b/lib/widgets/moa_widgets/edit_content.dart index 5b76a7d..60a6384 100644 --- a/lib/widgets/moa_widgets/edit_content.dart +++ b/lib/widgets/moa_widgets/edit_content.dart @@ -48,15 +48,7 @@ class EditContent extends HookWidget { } void editContent() async { - var error = await onPressed(); - errorText.value = error ?? ''; - if (controller.text.isEmpty || error != '') { - return; - } - - if (context.mounted) { - context.pop(); - } + onPressed(); emptyContentName(); } @@ -67,7 +59,7 @@ class EditContent extends HookWidget { return Container( decoration: const BoxDecoration( - color: Colors.white, + color: AppColors.whiteColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), diff --git a/lib/widgets/moa_widgets/folder_list.dart b/lib/widgets/moa_widgets/folder_list.dart index f19c974..27b17f7 100644 --- a/lib/widgets/moa_widgets/folder_list.dart +++ b/lib/widgets/moa_widgets/folder_list.dart @@ -53,6 +53,10 @@ class FolderList extends HookWidget { width: 32, height: 32, child: ImageOnNetwork( + border: Border.all( + color: AppColors.moaOpacity30, + width: 0.1, + ), imageURL: folder.thumbnailUrl ?? '', borderRadius: 30, ),