dialog_util.dart 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import 'dart:math';
  2. import 'package:confetti/confetti.dart';
  3. import "package:dio/dio.dart";
  4. import 'package:flutter/material.dart';
  5. import 'package:photos/core/constants.dart';
  6. import "package:photos/models/search/button_result.dart";
  7. import 'package:photos/models/typedefs.dart';
  8. import 'package:photos/theme/colors.dart';
  9. import 'package:photos/ui/common/loading_widget.dart';
  10. import 'package:photos/ui/common/progress_dialog.dart';
  11. import 'package:photos/ui/components/action_sheet_widget.dart';
  12. import 'package:photos/ui/components/button_widget.dart';
  13. import 'package:photos/ui/components/dialog_widget.dart';
  14. import 'package:photos/ui/components/models/button_type.dart';
  15. typedef DialogBuilder = DialogWidget Function(BuildContext context);
  16. ///Will return null if dismissed by tapping outside
  17. Future<ButtonResult?> showErrorDialog(
  18. BuildContext context,
  19. String title,
  20. String? body, {
  21. bool isDismissable = true,
  22. }) async {
  23. return showDialogWidget(
  24. context: context,
  25. title: title,
  26. body: body,
  27. isDismissible: isDismissable,
  28. buttons: const [
  29. ButtonWidget(
  30. buttonType: ButtonType.secondary,
  31. labelText: "OK",
  32. isInAlert: true,
  33. buttonAction: ButtonAction.first,
  34. ),
  35. ],
  36. );
  37. }
  38. Future<ButtonResult?> showErrorDialogForException({
  39. required BuildContext context,
  40. required Exception exception,
  41. bool isDismissible = true,
  42. String apiErrorPrefix = "It looks like something went wrong.",
  43. }) async {
  44. String errorMessage =
  45. "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.";
  46. if (exception is DioError &&
  47. exception.response != null &&
  48. exception.response!.data["code"] != null) {
  49. errorMessage =
  50. "$apiErrorPrefix\n\nReason: " + exception.response!.data["code"];
  51. }
  52. return showDialogWidget(
  53. context: context,
  54. title: "Error",
  55. icon: Icons.error_outline_outlined,
  56. body: errorMessage,
  57. isDismissible: isDismissible,
  58. buttons: const [
  59. ButtonWidget(
  60. buttonType: ButtonType.secondary,
  61. labelText: "OK",
  62. isInAlert: true,
  63. ),
  64. ],
  65. );
  66. }
  67. ///Will return null if dismissed by tapping outside
  68. Future<ButtonResult?> showGenericErrorDialog({
  69. required BuildContext context,
  70. bool isDismissible = true,
  71. }) async {
  72. return showDialogWidget(
  73. context: context,
  74. title: "Error",
  75. icon: Icons.error_outline_outlined,
  76. body:
  77. "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.",
  78. isDismissible: isDismissible,
  79. buttons: const [
  80. ButtonWidget(
  81. buttonType: ButtonType.secondary,
  82. labelText: "OK",
  83. isInAlert: true,
  84. ),
  85. ],
  86. );
  87. }
  88. DialogWidget choiceDialog({
  89. required String title,
  90. String? body,
  91. required String firstButtonLabel,
  92. String secondButtonLabel = "Cancel",
  93. ButtonType firstButtonType = ButtonType.neutral,
  94. ButtonType secondButtonType = ButtonType.secondary,
  95. ButtonAction firstButtonAction = ButtonAction.first,
  96. ButtonAction secondButtonAction = ButtonAction.cancel,
  97. FutureVoidCallback? firstButtonOnTap,
  98. FutureVoidCallback? secondButtonOnTap,
  99. bool isCritical = false,
  100. IconData? icon,
  101. }) {
  102. final buttons = [
  103. ButtonWidget(
  104. buttonType: isCritical ? ButtonType.critical : firstButtonType,
  105. labelText: firstButtonLabel,
  106. isInAlert: true,
  107. onTap: firstButtonOnTap,
  108. buttonAction: firstButtonAction,
  109. ),
  110. ButtonWidget(
  111. buttonType: secondButtonType,
  112. labelText: secondButtonLabel,
  113. isInAlert: true,
  114. onTap: secondButtonOnTap,
  115. buttonAction: secondButtonAction,
  116. ),
  117. ];
  118. return DialogWidget(title: title, body: body, buttons: buttons, icon: icon);
  119. }
  120. ///Will return null if dismissed by tapping outside
  121. Future<ButtonResult?> showChoiceDialog(
  122. BuildContext context, {
  123. required String title,
  124. String? body,
  125. required String firstButtonLabel,
  126. String secondButtonLabel = "Cancel",
  127. ButtonType firstButtonType = ButtonType.neutral,
  128. ButtonType secondButtonType = ButtonType.secondary,
  129. ButtonAction firstButtonAction = ButtonAction.first,
  130. ButtonAction secondButtonAction = ButtonAction.cancel,
  131. FutureVoidCallback? firstButtonOnTap,
  132. FutureVoidCallback? secondButtonOnTap,
  133. bool isCritical = false,
  134. IconData? icon,
  135. bool isDismissible = true,
  136. }) async {
  137. final buttons = [
  138. ButtonWidget(
  139. buttonType: isCritical ? ButtonType.critical : firstButtonType,
  140. labelText: firstButtonLabel,
  141. isInAlert: true,
  142. onTap: firstButtonOnTap,
  143. buttonAction: firstButtonAction,
  144. ),
  145. ButtonWidget(
  146. buttonType: secondButtonType,
  147. labelText: secondButtonLabel,
  148. isInAlert: true,
  149. onTap: secondButtonOnTap,
  150. buttonAction: secondButtonAction,
  151. ),
  152. ];
  153. return showDialogWidget(
  154. context: context,
  155. title: title,
  156. body: body,
  157. buttons: buttons,
  158. icon: icon,
  159. isDismissible: isDismissible,
  160. );
  161. }
  162. ///Will return null if dismissed by tapping outside
  163. Future<ButtonResult?> showChoiceActionSheet(
  164. BuildContext context, {
  165. required String title,
  166. String? body,
  167. required String firstButtonLabel,
  168. String secondButtonLabel = "Cancel",
  169. ButtonType firstButtonType = ButtonType.neutral,
  170. ButtonType secondButtonType = ButtonType.secondary,
  171. ButtonAction firstButtonAction = ButtonAction.first,
  172. ButtonAction secondButtonAction = ButtonAction.cancel,
  173. FutureVoidCallback? firstButtonOnTap,
  174. FutureVoidCallback? secondButtonOnTap,
  175. bool isCritical = false,
  176. IconData? icon,
  177. bool isDismissible = true,
  178. }) async {
  179. final buttons = [
  180. ButtonWidget(
  181. buttonType: isCritical ? ButtonType.critical : firstButtonType,
  182. labelText: firstButtonLabel,
  183. isInAlert: true,
  184. onTap: firstButtonOnTap,
  185. buttonAction: firstButtonAction,
  186. shouldStickToDarkTheme: true,
  187. ),
  188. ButtonWidget(
  189. buttonType: secondButtonType,
  190. labelText: secondButtonLabel,
  191. isInAlert: true,
  192. onTap: secondButtonOnTap,
  193. buttonAction: secondButtonAction,
  194. shouldStickToDarkTheme: true,
  195. ),
  196. ];
  197. return showActionSheet(
  198. context: context,
  199. title: title,
  200. body: body,
  201. buttons: buttons,
  202. isDismissible: isDismissible,
  203. );
  204. }
  205. ProgressDialog createProgressDialog(
  206. BuildContext context,
  207. String message, {
  208. isDismissible = false,
  209. }) {
  210. final dialog = ProgressDialog(
  211. context,
  212. type: ProgressDialogType.normal,
  213. isDismissible: isDismissible,
  214. barrierColor: Colors.black12,
  215. );
  216. dialog.style(
  217. message: message,
  218. messageTextStyle: Theme.of(context).textTheme.caption,
  219. backgroundColor: Theme.of(context).dialogTheme.backgroundColor,
  220. progressWidget: const EnteLoadingWidget(),
  221. borderRadius: 10,
  222. elevation: 10.0,
  223. insetAnimCurve: Curves.easeInOut,
  224. );
  225. return dialog;
  226. }
  227. Future<ButtonResult?> showConfettiDialog<T>({
  228. required BuildContext context,
  229. required DialogBuilder dialogBuilder,
  230. bool barrierDismissible = true,
  231. Color? barrierColor,
  232. bool useSafeArea = true,
  233. bool useRootNavigator = true,
  234. RouteSettings? routeSettings,
  235. Alignment confettiAlignment = Alignment.center,
  236. }) {
  237. final widthOfScreen = MediaQuery.of(context).size.width;
  238. final isMobileSmall = widthOfScreen <= mobileSmallThreshold;
  239. final pageBuilder = Builder(
  240. builder: dialogBuilder,
  241. );
  242. final ConfettiController confettiController =
  243. ConfettiController(duration: const Duration(seconds: 1));
  244. confettiController.play();
  245. return showDialog(
  246. context: context,
  247. builder: (BuildContext buildContext) {
  248. return Padding(
  249. padding: EdgeInsets.symmetric(horizontal: isMobileSmall ? 8 : 0),
  250. child: Stack(
  251. children: [
  252. Align(alignment: Alignment.center, child: pageBuilder),
  253. Align(
  254. alignment: confettiAlignment,
  255. child: ConfettiWidget(
  256. confettiController: confettiController,
  257. blastDirection: pi / 2,
  258. emissionFrequency: 0,
  259. numberOfParticles: 100,
  260. // a lot of particles at once
  261. gravity: 1,
  262. blastDirectionality: BlastDirectionality.explosive,
  263. ),
  264. ),
  265. ],
  266. ),
  267. );
  268. },
  269. barrierDismissible: barrierDismissible,
  270. barrierColor: barrierColor,
  271. useSafeArea: useSafeArea,
  272. useRootNavigator: useRootNavigator,
  273. routeSettings: routeSettings,
  274. );
  275. }
  276. //Can return ButtonResult? from ButtonWidget or Exception? from TextInputDialog
  277. Future<dynamic> showTextInputDialog(
  278. BuildContext context, {
  279. required String title,
  280. String? body,
  281. required String submitButtonLabel,
  282. IconData? icon,
  283. String? label,
  284. String? message,
  285. String? hintText,
  286. required FutureVoidCallbackParamStr onSubmit,
  287. IconData? prefixIcon,
  288. String? initialValue,
  289. Alignment? alignMessage,
  290. int? maxLength,
  291. bool showOnlyLoadingState = false,
  292. TextCapitalization textCapitalization = TextCapitalization.none,
  293. bool alwaysShowSuccessState = false,
  294. bool isPasswordInput = false,
  295. }) {
  296. return showDialog(
  297. barrierColor: backdropFaintDark,
  298. context: context,
  299. builder: (context) {
  300. final bottomInset = MediaQuery.of(context).viewInsets.bottom;
  301. final isKeyboardUp = bottomInset > 100;
  302. return Center(
  303. child: Padding(
  304. padding: EdgeInsets.only(bottom: isKeyboardUp ? bottomInset : 0),
  305. child: TextInputDialog(
  306. title: title,
  307. message: message,
  308. label: label,
  309. body: body,
  310. icon: icon,
  311. submitButtonLabel: submitButtonLabel,
  312. onSubmit: onSubmit,
  313. hintText: hintText,
  314. prefixIcon: prefixIcon,
  315. initialValue: initialValue,
  316. alignMessage: alignMessage,
  317. maxLength: maxLength,
  318. showOnlyLoadingState: showOnlyLoadingState,
  319. textCapitalization: textCapitalization,
  320. alwaysShowSuccessState: alwaysShowSuccessState,
  321. isPasswordInput: isPasswordInput,
  322. ),
  323. ),
  324. );
  325. },
  326. );
  327. }