Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions lib/app/config/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ import 'package:grimity/presentation/profile_edit/profile_edit_page.dart';
import 'package:grimity/presentation/ranking/ranking_page.dart';
import 'package:grimity/presentation/report/report_page.dart';
import 'package:grimity/presentation/search/search_page.dart';
import 'package:grimity/presentation/setting/page/setting_account_page.dart';
import 'package:grimity/presentation/setting/page/setting_contact_page.dart';
import 'package:grimity/presentation/setting/page/setting_nickname_page.dart';
import 'package:grimity/presentation/setting/page/setting_notification_page.dart';
import 'package:grimity/presentation/setting/page/setting_profile_url_page.dart';
import 'package:grimity/presentation/setting/page/setting_theme_page.dart';
import 'package:grimity/presentation/setting/setting_page.dart';
import 'package:grimity/presentation/sign_in/sign_in_page.dart';
import 'package:grimity/presentation/sign_up/sign_up_page.dart';
Expand Down Expand Up @@ -510,6 +516,72 @@ class SettingRoute extends GoRouteData with $SettingRoute {
Widget build(BuildContext context, GoRouterState state) => SettingPage();
}

@TypedGoRoute<SettingAccountRoute>(path: SettingAccountRoute.path, name: SettingAccountRoute.name)
class SettingAccountRoute extends GoRouteData with $SettingAccountRoute {
const SettingAccountRoute();

static const String path = '/setting/account';
static const String name = 'setting/account';

@override
Widget build(BuildContext context, GoRouterState state) => SettingAccountPage();
}

@TypedGoRoute<SettingNicknameRoute>(path: SettingNicknameRoute.path, name: SettingNicknameRoute.name)
class SettingNicknameRoute extends GoRouteData with $SettingNicknameRoute {
const SettingNicknameRoute();

static const String path = '/setting/account/nickname';
static const String name = 'setting/account/nickname';

@override
Widget build(BuildContext context, GoRouterState state) => SettingNicknamePage();
}

@TypedGoRoute<SettingProfileUrlRoute>(path: SettingProfileUrlRoute.path, name: SettingProfileUrlRoute.name)
class SettingProfileUrlRoute extends GoRouteData with $SettingProfileUrlRoute {
const SettingProfileUrlRoute();

static const String path = '/setting/account/profile_url';
static const String name = 'setting/account/profile_url';

@override
Widget build(BuildContext context, GoRouterState state) => SettingProfileUrlPage();
}

@TypedGoRoute<SettingThemeRoute>(path: SettingThemeRoute.path, name: SettingThemeRoute.name)
class SettingThemeRoute extends GoRouteData with $SettingThemeRoute {
const SettingThemeRoute();

static const String path = '/setting/theme';
static const String name = 'setting/theme';

@override
Widget build(BuildContext context, GoRouterState state) => SettingThemePage();
}

@TypedGoRoute<SettingNotificationRoute>(path: SettingNotificationRoute.path, name: SettingNotificationRoute.name)
class SettingNotificationRoute extends GoRouteData with $SettingNotificationRoute {
const SettingNotificationRoute();

static const String path = '/setting/notification';
static const String name = 'setting/notification';

@override
Widget build(BuildContext context, GoRouterState state) => SettingNotificationPage();
}

@TypedGoRoute<SettingContactRoute>(path: SettingContactRoute.path, name: SettingContactRoute.name)
class SettingContactRoute extends GoRouteData with $SettingContactRoute {
const SettingContactRoute();

static const String path = '/setting/contact';
static const String name = 'setting/contact';

@override
Widget build(BuildContext context, GoRouterState state) => SettingContactPage();
}

@TypedGoRoute<NotificationRoute>(path: NotificationRoute.path, name: NotificationRoute.name)
class NotificationRoute extends GoRouteData with $NotificationRoute {
const NotificationRoute();
Expand Down
13 changes: 13 additions & 0 deletions lib/app/enum/theme_type.enum.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';

enum ThemeType {
device,
light,
dark;

ThemeMode get themeMode => switch (this) {
device => ThemeMode.system,
light => ThemeMode.light,
dark => ThemeMode.dark,
};
}
9 changes: 9 additions & 0 deletions lib/app/setting/setting.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:flutter/widgets.dart';

abstract class Setting<T> extends ChangeNotifier {
String get key;

T getValue();

Future<void> setValue(T newValue);
}
17 changes: 17 additions & 0 deletions lib/app/setting/setting_binding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/widgets.dart';
import 'package:grimity/app/setting/theme_setting.dart';
import 'package:shared_preferences/shared_preferences.dart';

class SettingBinding {
const SettingBinding._();

static Listenable get listenable => Listenable.merge([theme]);

static late final SharedPreferences prefs;
static late final ThemeSetting theme;

static Future<void> setup() async {
prefs = await SharedPreferences.getInstance();
theme = ThemeSetting();
}
}
20 changes: 20 additions & 0 deletions lib/app/setting/setting_with_enum.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:collection/collection.dart';
import 'package:grimity/app/setting/setting.dart';
import 'package:grimity/app/setting/setting_binding.dart';

abstract class SettingWithEnum<T extends Enum> extends Setting<T> {
T get initialValue;
List<T> get values;

@override
T getValue() {
final value = SettingBinding.prefs.getString(key);
return values.firstWhereOrNull((e) => e.name == value) ?? initialValue;
}

@override
Future<void> setValue(T newValue) async {
await SettingBinding.prefs.setString(key, newValue.name);
notifyListeners();
}
}
13 changes: 13 additions & 0 deletions lib/app/setting/theme_setting.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:grimity/app/enum/theme_type.enum.dart';
import 'package:grimity/app/setting/setting_with_enum.dart';

class ThemeSetting extends SettingWithEnum<ThemeType> {
@override
String get key => 'theme';

@override
ThemeType get initialValue => ThemeType.device;

@override
List<ThemeType> get values => ThemeType.values;
}
34 changes: 21 additions & 13 deletions lib/presentation/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:grimity/app/config/app_theme.dart';
import 'package:grimity/app/environment/flavor.dart';
import 'package:grimity/app/linking/initialize_app_provider.dart';
import 'package:grimity/app/linking/pending_deep_link_provider.dart';
import 'package:grimity/app/setting/setting_binding.dart';
import 'package:grimity/app/static/push_notification.dart';
import 'package:grimity/app/util/pointer_event_filter.dart';
import 'package:grimity/presentation/common/provider/user_auth_provider.dart';
Expand All @@ -22,6 +23,9 @@ void runFlavoredApp() async {
// Initialize talker
final talker = TalkerFlutter.init();

// 앱 설정을 위한 초기화.
await SettingBinding.setup();

// 포그라운드 푸시 알림 구현을 위해 관련 플러그인 초기화.
await PushNotification.initializePlugin();

Expand Down Expand Up @@ -99,19 +103,23 @@ class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
}
});

return MaterialApp.router(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
FlutterQuillLocalizations.delegate,
],
routerConfig: ref.watch(routerProvider),
theme: AppTheme.appTheme.copyWith(
// TODO: 개발 중에는 앱 전체적으로 다크 모드로 고정. (시스템 다크 모드와 무관하게)
brightness: Brightness.dark,
),
builder: routerBuilder,
return ListenableBuilder(
listenable: SettingBinding.listenable,
builder: (context, _) {
return MaterialApp.router(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
FlutterQuillLocalizations.delegate,
],
routerConfig: ref.watch(routerProvider),
theme: AppTheme.appTheme.copyWith(brightness: Brightness.light),
darkTheme: AppTheme.appTheme.copyWith(brightness: Brightness.dark),
themeMode: SettingBinding.theme.getValue().themeMode,
builder: routerBuilder,
);
},
);
}
}
14 changes: 14 additions & 0 deletions lib/presentation/common/provider/user_auth_provider.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:flutter/widgets.dart';
import 'package:grimity/app/config/app_router.dart';
import 'package:grimity/app/enum/login_provider.enum.dart';
import 'package:grimity/app/static/push_notification.dart';
import 'package:grimity/domain/entity/user.dart';
Expand Down Expand Up @@ -78,4 +80,16 @@ class UserAuth extends _$UserAuth {
await completeLogoutProcessUseCase.execute(provider);
state = null;
}

Future<void> performSignOut(BuildContext context) async {
final user = ref.read(userAuthProvider);
if (user == null) return;

final provider = LoginProvider.fromString(user.provider ?? '');
await logout(provider);

if (context.mounted) {
SignInRoute().go(context);
}
}
}
11 changes: 1 addition & 10 deletions lib/presentation/common/widget/navigation/grimity_drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:gds/gds.dart';
import 'package:go_router/go_router.dart';
import 'package:grimity/app/config/app_const.dart';
import 'package:grimity/app/config/app_router.dart';
import 'package:grimity/app/enum/login_provider.enum.dart';
import 'package:grimity/presentation/common/provider/user_auth_provider.dart';
import 'package:url_launcher/url_launcher.dart';

Expand Down Expand Up @@ -90,14 +89,6 @@ class GrimityDrawer extends ConsumerWidget {
}

Future<void> _signOut(BuildContext context, WidgetRef ref) async {
final user = ref.read(userAuthProvider);
if (user == null) return;

final provider = LoginProvider.fromString(user.provider ?? '');
await ref.read(userAuthProvider.notifier).logout(provider);

if (context.mounted) {
SignInRoute().go(context);
}
return ref.read(userAuthProvider.notifier).performSignOut(context);
}
}
47 changes: 29 additions & 18 deletions lib/presentation/profile_edit/provider/profile_edit_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,36 @@ class ProfileEdit extends _$ProfileEdit {
return false;
}

final request = UpdateUserRequest(
name: state.nickname,
url: state.url,
description: state.description,
links: state.links,
);
try {
state = state.copyWith(isLoading: true);

final result = await updateUserUseCase.execute(request);
final request = UpdateUserRequest(
name: state.nickname,
url: state.url,
description: state.description,
links: state.links,
);

return result.fold(
onSuccess: (data) {
ToastService.showSuccess('프로필 수정이 완료되었어요');
return true;
},
onFailure: (error) {
ToastService.showFailure('프로필 수정에 실패했어요');
return false;
},
);
final result = await updateUserUseCase.execute(request);

// 새로운 사용자 정보로 새로고침
if (result.isSuccess) {
await ref.read(userAuthProvider.notifier).getUser();
}

return result.fold(
onSuccess: (data) {
ToastService.showSuccess('프로필 수정이 완료되었어요');
return true;
},
onFailure: (error) {
ToastService.showFailure('프로필 수정에 실패했어요');
return false;
},
);
} finally {
state = state.copyWith(isLoading: false);
}
}

/// 유효성 체크
Expand Down Expand Up @@ -272,7 +283,7 @@ abstract class ProfileEditState with _$ProfileEditState {
final hasChanges =
image != initialState!.image ||
backgroundImage != initialState!.backgroundImage ||
nickname != initialState!.nickname ||
(nickname.isNotEmpty && nickname != initialState!.nickname) ||
description != initialState!.description ||
url != initialState!.url ||
_isLinksChanged(links, initialState!.links);
Expand Down
16 changes: 16 additions & 0 deletions lib/presentation/setting/page/setting_account_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter/widgets.dart';
import 'package:gds/gds.dart';
import 'package:grimity/presentation/setting/view/setting_account_view.dart';
import 'package:grimity/presentation/setting/widget/setting_app_bar.dart';

class SettingAccountPage extends StatelessWidget {
const SettingAccountPage({super.key});

@override
Widget build(BuildContext context) {
return GdsScaffold(
appBar: SettingAppBar(title: '내 계정'),
body: SettingAccountView(),
);
}
}
16 changes: 16 additions & 0 deletions lib/presentation/setting/page/setting_contact_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter/widgets.dart';
import 'package:gds/gds.dart';
import 'package:grimity/presentation/setting/view/setting_contact_view.dart';
import 'package:grimity/presentation/setting/widget/setting_app_bar.dart';

class SettingContactPage extends StatelessWidget {
const SettingContactPage({super.key});

@override
Widget build(BuildContext context) {
return GdsScaffold(
appBar: SettingAppBar(title: '문의하기'),
body: SettingContactView(),
);
}
}
20 changes: 20 additions & 0 deletions lib/presentation/setting/page/setting_entry_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter/widgets.dart';
import 'package:gds/gds.dart';
import 'package:grimity/presentation/setting/view/setting_entry_view.dart';
import 'package:grimity/presentation/setting/widget/setting_app_bar.dart';

class SettingEntryPage extends StatelessWidget {
const SettingEntryPage({super.key});

@override
Widget build(BuildContext context) {
return GdsScaffold(
appBar: createAppBar(),
body: SettingEntryView(),
);
}

static Widget createAppBar() {
return SettingAppBar(title: '설정');
}
}
16 changes: 16 additions & 0 deletions lib/presentation/setting/page/setting_nickname_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter/widgets.dart';
import 'package:gds/gds.dart';
import 'package:grimity/presentation/setting/view/setting_nickname_view.dart';
import 'package:grimity/presentation/setting/widget/setting_app_bar.dart';

class SettingNicknamePage extends StatelessWidget {
const SettingNicknamePage({super.key});

@override
Widget build(BuildContext context) {
return GdsScaffold(
appBar: SettingAppBar(title: '닉네임'),
body: SettingNicknameView(),
);
}
}
Loading
Loading