Skip to content

Commit

Permalink
feat: 컨텐츠 상세 수정 ui
Browse files Browse the repository at this point in the history
  • Loading branch information
nain93 committed Aug 1, 2023
1 parent ca5e000 commit 76e5f59
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 147 deletions.
1 change: 1 addition & 0 deletions lib/constants/color_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AppColors {
static const Color subTitle = Color(0xFF818181);
static const Color grayBackground = Color(0xFFD9D9D9);
static const Color hashtagBackground = Color(0xFFF8F8F8);
static const Color contentHashtagBackground = Color(0xFFFFF3EE);

///font color
static const Color fontTitleColor = Color(0xff000000);
Expand Down
43 changes: 43 additions & 0 deletions lib/providers/content_detail_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'dart:async';

import 'package:moa_app/models/content_model.dart';
import 'package:moa_app/repositories/content_repository.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'content_detail_provider.g.dart';

@riverpod
class ContentDetail extends _$ContentDetail {
Future<ContentModel> fetchItem({required String contentId}) 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 =
await ContentRepository.instance.getContentDetail(contentId: contentId);
return data;
}

@override
Future<ContentModel?> build() async {
return null;
}
}
38 changes: 21 additions & 17 deletions lib/repositories/content_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,27 @@ class ContentRepository implements IContentRepository {
Future<ContentModel> getContentDetail({required String contentId}) async {
var token = await TokenRepository.instance.getToken();

var res = await dio.get(
'/api/v1/content/detail/view?contentId=$contentId',
options: Options(
headers: {
'Authorization': 'Bearer $token',
},
),
);
try {
var res = await dio.get(
'/api/v1/content/detail/view?contentId=$contentId',
options: Options(
headers: {
'Authorization': 'Bearer $token',
},
),
);

/// 백엔드 ContentModel 타입이 통일되지 않았으므로 임시로 타입 변환해서 넣어줌
return ContentModel.fromJson({
'contentId': '0',
'contentImageUrl': res.data['data']['thumbnail_image_url'],
'contentUrl': res.data['data']['contentUrl'] ?? '',
'contentMemo': res.data['data']['memo'],
'contentName': res.data['data']['contentName'],
'contentHashTag': res.data['data']['hashTags'],
});
/// 백엔드 ContentModel 타입이 통일되지 않았으므로 임시로 타입 변환해서 넣어줌
return ContentModel.fromJson({
'contentId': '0',
'contentImageUrl': res.data['data']['thumbnail_image_url'],
'contentUrl': res.data['data']['contentUrl'] ?? '',
'contentMemo': res.data['data']['memo'],
'contentName': res.data['data']['contentName'],
'contentHashTag': res.data['data']['hashTags'],
});
} catch (e) {
rethrow;
}
}
}
250 changes: 149 additions & 101 deletions lib/screens/home/content_view.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
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/models/content_model.dart';
import 'package:moa_app/repositories/content_repository.dart';
import 'package:moa_app/providers/content_detail_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';
import 'package:moa_app/widgets/button.dart';
import 'package:moa_app/widgets/image.dart';
import 'package:moa_app/widgets/loading_indicator.dart';
import 'package:moa_app/widgets/moa_widgets/bottom_modal_item.dart';

class ContentView extends HookWidget {
class ContentView extends HookConsumerWidget {
const ContentView({
super.key,
required this.id,
Expand All @@ -21,17 +25,23 @@ class ContentView extends HookWidget {
final String folderName;

@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
var contentNotifier = ref.watch(contentDetailProvider.notifier);
var isEditMode = useState(false);

void pressGoToLink() {}

void pressConfirm() {}

void editContent() {}
void editContent() {
isEditMode.value = true;
}

void deleteContent() {}

void showContentModal() {
General.instance.showBottomSheet(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
context: context,
height: 150 + kBottomNavigationBarHeight,
child: Column(
Expand Down Expand Up @@ -59,6 +69,7 @@ class ContentView extends HookWidget {

return Scaffold(
appBar: AppBarBack(
title: folderName,
isBottomBorderDisplayed: false,
actions: [
CircleIconButton(
Expand All @@ -72,108 +83,145 @@ class ContentView extends HookWidget {
),
],
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 15, bottom: kBottomNavigationBarHeight),
child: FutureBuilder<ContentModel>(
future: ContentRepository.instance.getContentDetail(contentId: id),
builder: (context, snapshot) {
var content = snapshot.data;
body: SafeArea(
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 15, bottom: kBottomNavigationBarHeight),
child: FutureBuilder<ContentModel>(
future: contentNotifier.fetchItem(contentId: id),
builder: (context, snapshot) {
var content = snapshot.data;

if (snapshot.hasData && content != null) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image(
image: Assets.smallFolder,
width: 42,
height: 42,
),
Text(
folderName,
style: const H2TextStyle(),
),
],
),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
...content.contentHashTag.map(
(e) => Container(
margin: const EdgeInsets.only(top: 30, bottom: 40),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: AppColors.primaryColor,
return AnimatedSwitcher(
transitionBuilder: (child, animation) {
return FadeTransition(opacity: animation, child: child);
},
duration: const Duration(milliseconds: 300),
child: () {
if (snapshot.connectionState == ConnectionState.waiting) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.9,
child: Container(
margin: const EdgeInsets.only(bottom: 100),
child: const LoadingIndicator()),
);
}
if (snapshot.hasError) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.9,
child: const Center(
child: Text(
'취향을 불러오는데 실패했습니다.',
style: TextStyle(
color: AppColors.blackColor,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.pretendard,
),
child: Text(
'#${e.hashTag}',
style: const TextStyle(
color: AppColors.whiteColor,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.pretendard,
),
),
),
),
],
),
Container(
height: 225,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColors.grayBackground,
width: 1,
),
image: DecorationImage(
image: NetworkImage(content.contentImageUrl),
fit: BoxFit.contain,
),
),
),
const SizedBox(height: 10),
Text(
content.contentName,
style: const H2TextStyle(),
),
const SizedBox(height: 30),
Text(
content.contentMemo ?? '',
style: const Hash1TextStyle().merge(
const TextStyle(height: 1.4),
),
),
const SizedBox(height: 80),
Row(
children: [
Expanded(
child: Button(
backgroundColor: AppColors.linkButton,
text: '링크 바로가기',
onPress: pressGoToLink,
),
),
const SizedBox(width: 15),
Expanded(
child: Button(
text: '확인',
onPress: pressConfirm,
),
),
],
),
],
);
}

if (snapshot.hasData && content != null) {
return isEditMode.value
? EditContentView(
content: content,
isEditMode: isEditMode,
)
: SizedBox(
height: MediaQuery.of(context).size.height * 0.9,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 1,
child: Container(
margin: const EdgeInsets.only(top: 20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColors.grayBackground,
width: 0.5,
),
),
child: ImageOnNetwork(
imageURL: content.contentImageUrl,
),
),
),
const SizedBox(height: 10),
Text(
content.contentName,
style: const H2TextStyle(),
),
const SizedBox(height: 30),
Text(
content.contentMemo ?? '',
style: const Hash1TextStyle().merge(
const TextStyle(height: 1.4),
),
),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
...content.contentHashTag.map(
(e) => Container(
margin:
const EdgeInsets.only(top: 30),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(50),
color: AppColors
.contentHashtagBackground,
),
child: Text(
'#${e.hashTag}',
style: const TextStyle(
color: AppColors.subTitle,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily:
FontConstants.pretendard,
),
),
),
),
],
),
const SizedBox(height: 40),
Row(
children: [
Expanded(
child: Button(
backgroundColor: AppColors.linkButton,
text: '링크 바로가기',
onPress: pressGoToLink,
),
),
const SizedBox(width: 15),
Expanded(
child: Button(
text: '확인',
onPress: pressConfirm,
),
),
],
),
],
),
);
}
return const SizedBox();
}(),
);
}
return const SizedBox();
},
},
),
),
),
),
Expand Down
Loading

0 comments on commit 76e5f59

Please sign in to comment.