Skip to content

Commit

Permalink
Merge pull request #78 from sparcs-kaist/#77-inAppNotification
Browse files Browse the repository at this point in the history
InAppNotification
  • Loading branch information
happycastle114 authored Sep 19, 2023
2 parents 8f3d69c + b294f0e commit be759a3
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 39 deletions.
123 changes: 86 additions & 37 deletions lib/constants/theme.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

//primaryColor 지정 (색상코드: #6E3647)
final Map<int, Color> primaryColor1 = {
Expand All @@ -22,10 +23,9 @@ const Color toastTextColor = Colors.black;
const Color notiColor = Color(0x66C8C8C8);
final Color dialogBarrierColor = Colors.black.withOpacity(0.6);

//아래의 상수들은 피그마 기준 상의 패딩 픽셀과는 차이를 두고 있지만,
//이는 모바일 환경상 웹뷰와 같은 간격을 제시하기 위해 설정한 값들입니다.
double devicePixelRatio = 3.0;
const double dialogPadding = 15.0;
const double taxiDialogPadding = 15.0;
const double taxiNotificationPadding = 20.0;
final defaultDialogUpperTitlePadding =
Padding(padding: EdgeInsets.symmetric(vertical: 36.0 / devicePixelRatio));

Expand All @@ -37,22 +37,42 @@ final defaultDialogLowerTitlePadding =

final defaultDialogVerticalMedianButtonPadding = Padding(
padding:
EdgeInsets.symmetric(horizontal: dialogPadding / devicePixelRatio));
EdgeInsets.symmetric(horizontal: taxiDialogPadding / devicePixelRatio));

final defaultDialogLowerButtonPadding = Padding(
padding: EdgeInsets.only(bottom: (dialogPadding / 2) / devicePixelRatio));
padding:
EdgeInsets.only(bottom: (taxiDialogPadding / 2) / devicePixelRatio));

final defaultDialogPadding =
Padding(padding: EdgeInsets.all(dialogPadding / devicePixelRatio));
Padding(padding: EdgeInsets.all(taxiDialogPadding / devicePixelRatio));

final defaultDialogButtonSize = Size(147.50, 35);

final defaultDialogButtonInnerPadding =
EdgeInsets.symmetric(vertical: 9, horizontal: 15);
final defaultDialogButtonInnerPadding = EdgeInsets.only(top: 9, bottom: 9);

final defaultDialogButtonBorderRadius = BorderRadius.circular(8.0);

ThemeData buildTheme() {
final defaultTaxiMarginDouble = 20.0;

final defaultTaxiMargin =
EdgeInsets.symmetric(horizontal: defaultTaxiMarginDouble);

const defaultNotificationButtonSize = Size(90, 25);
const defaultNotificationButtonInnerPadding =
EdgeInsets.symmetric(horizontal: 15.0, vertical: 2.0);
final defaultNotificationButtonBorderRadius = BorderRadius.circular(30.0);
final defaultNotificatonOutlinedButtonStyle = OutlinedButton.styleFrom(
minimumSize: Size.zero,
fixedSize: defaultNotificationButtonSize,
padding: defaultNotificationButtonInnerPadding,
backgroundColor: taxiPrimaryMaterialColor,
shape: RoundedRectangleBorder(
borderRadius: defaultNotificationButtonBorderRadius,
side: const BorderSide(color: Colors.black),
),
); // TODO: ThemeData에 있는 OutlinedButtonThemeData 분리

ThemeData taxiTheme() {
final base = ThemeData(
primarySwatch: taxiPrimaryMaterialColor,
primaryColor: const Color(0xFF6E3678),
Expand All @@ -61,7 +81,7 @@ ThemeData buildTheme() {
dialogTheme: DialogTheme(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: Colors.white,
actionsPadding: EdgeInsets.all(dialogPadding / devicePixelRatio),
actionsPadding: const EdgeInsets.all(10.0),
surfaceTintColor: Colors.black,
),
dialogBackgroundColor: Colors.white,
Expand Down Expand Up @@ -89,36 +109,65 @@ ThemeData buildTheme() {
),
),
),
bannerTheme: MaterialBannerThemeData(
backgroundColor: Colors.white,
),

//텍스트 테마
textTheme: const TextTheme(
//Dialog 제목
titleSmall: TextStyle(
fontFamily: 'NanumSquare',
color: Color(0xFF323232),
fontSize: 16,
fontWeight: FontWeight.w400),

//Dialog 상세 설명
bodySmall: TextStyle(
fontFamily: 'NanumSquare_acB',
color: Color(0xFF888888),
fontSize: 10,
fontWeight: FontWeight.w700),

//Dialog Outlined 버튼 텍스트
labelLarge: TextStyle(
fontFamily: 'NanumSquare_acB',
color: Color(0xFFEEEEEE),
fontSize: 13,
fontWeight: FontWeight.w700),

//Dialog Elevated 버튼 텍스트
labelMedium: TextStyle(
fontFamily: 'NanumSquare',
color: Color.fromARGB(255, 129, 129, 129),
fontSize: 13,
fontWeight: FontWeight.w400)),
//Dialog 제목
titleSmall: TextStyle(
fontFamily: 'NanumSquare',
color: Color(0xFF323232),
fontSize: 16,
fontWeight: FontWeight.w400),

//Dialog 상세 설명
bodySmall: TextStyle(
fontFamily: 'NanumSquare_acB',
color: Color(0xFF888888),
fontSize: 10,
fontWeight: FontWeight.w700),

//Dialog Outlined 버튼 텍스트
labelLarge: TextStyle(
fontFamily: 'NanumSquare_acB',
color: Color(0xFFEEEEEE),
fontSize: 14,
fontWeight: FontWeight.w700),

//Dialog Elevated 버튼 텍스트
labelMedium: TextStyle(
fontFamily: 'NanumSquare',
color: Color.fromARGB(255, 129, 129, 129),
fontSize: 14,
fontWeight: FontWeight.w400),
labelSmall: TextStyle(
color: Color(0xFFEEEEEE),
fontFamily: 'NanumSquare_acB',
fontSize: 12,
fontWeight: FontWeight.w700,
letterSpacing: 0.4,
),
),

bottomNavigationBarTheme: BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.white,
selectedItemColor: Color(0xFF6E3678),
selectedLabelStyle: TextStyle(
fontFamily: 'NanumSquare',
fontSize: 12,
fontWeight: FontWeight.w700,
letterSpacing: 0.4,
),
unselectedLabelStyle: TextStyle(
fontFamily: 'NanumSquare',
fontSize: 12,
fontWeight: FontWeight.w700,
letterSpacing: 0.4,
),
),
);
return base;
}
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class MyHome extends HookWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Taxi App',
theme: buildTheme(),
theme: taxiTheme(),
home: Container(
color: Theme.of(context).primaryColor,
child: Container(
Expand Down
181 changes: 180 additions & 1 deletion lib/views/taxiView.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,27 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:taxiapp/views/taxiDialog.dart';
import 'package:app_links/app_links.dart';
import 'package:taxiapp/constants/theme.dart';
import 'dart:math';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:open_store/open_store.dart';

class TaxiView extends HookWidget {
final CookieManager _cookieManager = CookieManager.instance();
// late InAppWebViewController _controller;

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();

@override
Widget build(BuildContext context) {
String address = RemoteConfigController().frontUrl;
OverlayEntry? overlayEntry;
AnimationController _aniController =
useAnimationController(duration: const Duration(milliseconds: 300));
Animation<Offset> _animation =
Tween(begin: const Offset(0, -0.5), end: const Offset(0.0, 0)).animate(
CurvedAnimation(parent: _aniController, curve: Curves.decelerate));
bool isBannerShow = false;

// States
// 로딩 여부 확인
Expand Down Expand Up @@ -277,6 +285,156 @@ class TaxiView extends HookWidget {
return;
}, [isAuthLogin.value, isFcmInit.value]);

void removeOverlayNotification({required Uri? uri}) {
if (uri != Uri.parse("")) {
url.value = uri.toString();
LoadCount.value += 1;
}
overlayEntry?.remove();
overlayEntry = null;
}

void removeAnimation() {
_aniController.reverse(); //TODO: 일정 dy 미만시 배너 삭제 취소 및 애니메이션 다시 재생
isBannerShow = false;
// removeOverlayNotification();
}

void createOverlayNotification(
{required String title,
required String subTitle,
required String content,
required Map<String, Uri> button,
Uri? imageUrl}) {
print("asd");
if (overlayEntry != null) {
removeOverlayNotification(uri: Uri.parse(""));
}
assert(overlayEntry == null);
isBannerShow = true;

overlayEntry = OverlayEntry(builder: (BuildContext context) {
_aniController.reset();
_animation =
Tween(begin: const Offset(0, -0.5), end: const Offset(0, 0))
.animate(CurvedAnimation(
parent: _aniController, curve: Curves.decelerate));
_aniController.forward();

return SlideTransition(
position: _animation,
child: GestureDetector(
onPanUpdate: (details) {
if (details.delta.dy < -1 && isBannerShow) {
removeAnimation();
}
},
onPanEnd: (details) {
if (!isBannerShow) {
removeOverlayNotification(uri: button.values.first);
}
},
child: UnconstrainedBox(
alignment: Alignment.topCenter,
child: Container(
width: MediaQuery.of(context).size.width,
height: min(MediaQuery.of(context).size.height * 0.15, 200),
margin:
EdgeInsets.only(top: MediaQuery.of(context).padding.top),
color: Colors.white,
child: Stack(
children: [
Container(
alignment: Alignment.topCenter,
height: 5.0,
color: taxiPrimaryColor,
),
Positioned(
left: 20,
top: 25,
child: (imageUrl != Uri.parse(""))
? Image(
image: NetworkImage(imageUrl.toString()),
width: 40,
height: 40,
fit: BoxFit.cover,
)
: const Padding(padding: EdgeInsets.zero)),
Positioned(
left: 20 +
((imageUrl != Uri.parse(""))
? 60
: 0), // 이미지 없을 시 마진 20으로 변경
top: 25,
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: title,
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontSize: 12,
),
),
TextSpan(
text:
(subTitle.isNotEmpty) ? " / $subTitle" : "",
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontSize: 12,
fontWeight: FontWeight.w400)),
],
),
),
),
Positioned(
left: 20 + ((imageUrl != Uri.parse("")) ? 60 : 0),
top: 40,
child: Text(
content,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: 0.4),
),
),
Positioned(
bottom: 20 / devicePixelRatio,
right: 25 / devicePixelRatio,
child: OutlinedButton(
style: defaultNotificatonOutlinedButtonStyle,
child: Text(
button.keys.first,
style: Theme.of(context)
.textTheme
.labelSmall!
.copyWith(fontSize: 14),
),
onPressed: () {
removeAnimation();
Future.delayed(const Duration(milliseconds: 300),
() {
removeOverlayNotification(
uri: button.values.first);
});
}),
),
],
),
),
),
),
);
});
Overlay.of(context).insert(overlayEntry!);
}

return SafeArea(
child: Stack(children: [
WillPopScope(
Expand Down Expand Up @@ -405,6 +563,27 @@ class TaxiView extends HookWidget {
}
});

// Web -> App
_controller.value?.addJavaScriptHandler(
handlerName: "popup_inAppNotification",
callback: (args) async {
createOverlayNotification(
title: args[0]['title'].toString(),
subTitle: args[0]['subtitle'].toString(),
content: args[0]['content'].toString(),
button: {
args[0]['button']['text'].toString():
(args[0]['button']['path'].toString() != "")
? Uri.parse(
args[0]['button']['path'].toString())
: Uri.parse("")
},
imageUrl: (args[0]['type'].toString() ==
"default") //TODO: type showMaterialBanner 함수에서 관리
? Uri.parse(args[0]['imageUrl'].toString())
: Uri.parse(""));
});

_controller.value?.addJavaScriptHandler(
handlerName: "popup_instagram_story_share",
callback: (args) async {
Expand Down

0 comments on commit be759a3

Please sign in to comment.