Merge branch 'redesign-with-components' into collaboration_view
This commit is contained in:
commit
1aadc08fdf
26 changed files with 1551 additions and 126 deletions
|
@ -102,6 +102,9 @@ PODS:
|
|||
- Flutter
|
||||
- in_app_purchase_storekit (0.0.1):
|
||||
- Flutter
|
||||
- keyboard_visibility (0.5.0):
|
||||
- Flutter
|
||||
- Reachability
|
||||
- libwebp (1.2.3):
|
||||
- libwebp/demux (= 1.2.3)
|
||||
- libwebp/mux (= 1.2.3)
|
||||
|
@ -193,6 +196,7 @@ DEPENDENCIES:
|
|||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- image_editor (from `.symlinks/plugins/image_editor/ios`)
|
||||
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/ios`)
|
||||
- keyboard_visibility (from `.symlinks/plugins/keyboard_visibility/ios`)
|
||||
- local_auth (from `.symlinks/plugins/local_auth/ios`)
|
||||
- media_extension (from `.symlinks/plugins/media_extension/ios`)
|
||||
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
|
||||
|
@ -271,6 +275,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/image_editor/ios"
|
||||
in_app_purchase_storekit:
|
||||
:path: ".symlinks/plugins/in_app_purchase_storekit/ios"
|
||||
keyboard_visibility:
|
||||
:path: ".symlinks/plugins/keyboard_visibility/ios"
|
||||
local_auth:
|
||||
:path: ".symlinks/plugins/local_auth/ios"
|
||||
media_extension:
|
||||
|
@ -321,7 +327,7 @@ SPEC CHECKSUMS:
|
|||
FirebaseInstallations: 0a115432c4e223c5ab20b0dbbe4cbefa793a0e8e
|
||||
FirebaseMessaging: 732623518591384f61c287e3d8f65294beb7ffb3
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433
|
||||
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
|
||||
|
@ -336,6 +342,7 @@ SPEC CHECKSUMS:
|
|||
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
|
||||
image_editor: eab82a302a6623a866da5145b7c4c0ee8a4ffbb4
|
||||
in_app_purchase_storekit: d7fcf4646136ec258e237872755da8ea6c1b6096
|
||||
keyboard_visibility: 96a24de806fe6823c3ad956c01ba2ec6d056616f
|
||||
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
|
||||
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
DA6BE5E826B3BC8600656280 /* (null) in Resources */ = {isa = PBXBuildFile; };
|
||||
DA6BE5E826B3BC8600656280 /* BuildFile in Resources */ = {isa = PBXBuildFile; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
|
@ -213,7 +213,7 @@
|
|||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
DA6BE5E826B3BC8600656280 /* (null) in Resources */,
|
||||
DA6BE5E826B3BC8600656280 /* BuildFile in Resources */,
|
||||
277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -287,6 +287,7 @@
|
|||
"${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/image_editor/image_editor.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/keyboard_visibility/keyboard_visibility.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/local_auth/local_auth.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework",
|
||||
|
@ -341,6 +342,7 @@
|
|||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/keyboard_visibility.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",
|
||||
|
@ -499,7 +501,10 @@
|
|||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
|
@ -656,7 +661,10 @@
|
|||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
|
@ -690,7 +698,10 @@
|
|||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
|
|
|
@ -44,9 +44,11 @@ const supportEmail = 'support@ente.io';
|
|||
class FFDefault {
|
||||
static const bool enableStripe = true;
|
||||
static const bool disableCFWorker = false;
|
||||
static const bool enableCollect = false;
|
||||
}
|
||||
|
||||
const kDefaultProductionEndpoint = 'https://api.ente.io';
|
||||
|
||||
const int intMaxValue = 9223372036854775807;
|
||||
|
||||
//Screen width of iPhone 14 pro max in points is taken as maximum
|
||||
const double restrictedMaxWidth = 430;
|
||||
|
|
|
@ -343,6 +343,9 @@ extension CustomColorScheme on ColorScheme {
|
|||
|
||||
EnteTheme get enteTheme =>
|
||||
brightness == Brightness.light ? lightTheme : darkTheme;
|
||||
|
||||
EnteTheme get inverseEnteTheme =>
|
||||
brightness == Brightness.light ? darkTheme : lightTheme;
|
||||
}
|
||||
|
||||
OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
||||
|
|
|
@ -62,18 +62,6 @@ class FeatureFlagService {
|
|||
}
|
||||
}
|
||||
|
||||
bool enableCollect() {
|
||||
if (isInternalUserOrDebugBuild()) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return _getFeatureFlags().enableCollect;
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
return FFDefault.enableCollect;
|
||||
}
|
||||
}
|
||||
|
||||
bool isInternalUserOrDebugBuild() {
|
||||
final String? email = Configuration.instance.getEmail();
|
||||
return (email != null && email.endsWith("@ente.io")) || kDebugMode;
|
||||
|
@ -97,24 +85,20 @@ class FeatureFlags {
|
|||
static FeatureFlags defaultFlags = FeatureFlags(
|
||||
disableCFWorker: FFDefault.disableCFWorker,
|
||||
enableStripe: FFDefault.enableStripe,
|
||||
enableCollect: FFDefault.enableCollect,
|
||||
);
|
||||
|
||||
final bool disableCFWorker;
|
||||
final bool enableStripe;
|
||||
final bool enableCollect;
|
||||
|
||||
FeatureFlags({
|
||||
required this.disableCFWorker,
|
||||
required this.enableStripe,
|
||||
required this.enableCollect,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
"disableCFWorker": disableCFWorker,
|
||||
"enableStripe": enableStripe,
|
||||
"enableCollect": enableCollect,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -127,7 +111,6 @@ class FeatureFlags {
|
|||
return FeatureFlags(
|
||||
disableCFWorker: json["disableCFWorker"] ?? FFDefault.disableCFWorker,
|
||||
enableStripe: json["enableStripe"] ?? FFDefault.enableStripe,
|
||||
enableCollect: json["enableCollect"] ?? FFDefault.enableCollect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ class EnteColorScheme {
|
|||
final Color fillBase;
|
||||
final Color fillMuted;
|
||||
final Color fillFaint;
|
||||
final Color fillFaintPressed;
|
||||
|
||||
// Stroke Colors
|
||||
final Color strokeBase;
|
||||
|
@ -63,6 +64,7 @@ class EnteColorScheme {
|
|||
this.fillBase,
|
||||
this.fillMuted,
|
||||
this.fillFaint,
|
||||
this.fillFaintPressed,
|
||||
this.strokeBase,
|
||||
this.strokeMuted,
|
||||
this.strokeFaint,
|
||||
|
@ -84,28 +86,30 @@ class EnteColorScheme {
|
|||
}
|
||||
|
||||
const EnteColorScheme lightScheme = EnteColorScheme(
|
||||
backgroundBaseLight,
|
||||
backgroundElevatedLight,
|
||||
backgroundElevated2Light,
|
||||
backdropBaseLight,
|
||||
backdropMutedLight,
|
||||
backdropFaintLight,
|
||||
textBaseLight,
|
||||
textMutedLight,
|
||||
textFaintLight,
|
||||
blurTextBaseLight,
|
||||
fillBaseLight,
|
||||
fillMutedLight,
|
||||
fillFaintLight,
|
||||
strokeBaseLight,
|
||||
strokeMutedLight,
|
||||
strokeFaintLight,
|
||||
strokeFainterLight,
|
||||
blurStrokeBaseLight,
|
||||
blurStrokeFaintLight,
|
||||
blurStrokePressedLight,
|
||||
tabIconLight,
|
||||
avatarLight);
|
||||
backgroundBaseLight,
|
||||
backgroundElevatedLight,
|
||||
backgroundElevated2Light,
|
||||
backdropBaseLight,
|
||||
backdropMutedLight,
|
||||
backdropFaintLight,
|
||||
textBaseLight,
|
||||
textMutedLight,
|
||||
textFaintLight,
|
||||
blurTextBaseLight,
|
||||
fillBaseLight,
|
||||
fillMutedLight,
|
||||
fillFaintLight,
|
||||
fillFaintPressedLight,
|
||||
strokeBaseLight,
|
||||
strokeMutedLight,
|
||||
strokeFaintLight,
|
||||
strokeFainterLight,
|
||||
blurStrokeBaseLight,
|
||||
blurStrokeFaintLight,
|
||||
blurStrokePressedLight,
|
||||
tabIconLight,
|
||||
avatarLight,
|
||||
);
|
||||
|
||||
const EnteColorScheme darkScheme = EnteColorScheme(
|
||||
backgroundBaseDark,
|
||||
|
@ -121,6 +125,7 @@ const EnteColorScheme darkScheme = EnteColorScheme(
|
|||
fillBaseDark,
|
||||
fillMutedDark,
|
||||
fillFaintDark,
|
||||
fillFaintPressedDark,
|
||||
strokeBaseDark,
|
||||
strokeMutedDark,
|
||||
strokeFaintDark,
|
||||
|
@ -165,10 +170,12 @@ const Color blurTextBaseDark = Color.fromRGBO(255, 255, 255, 0.95);
|
|||
const Color fillBaseLight = Color.fromRGBO(0, 0, 0, 1);
|
||||
const Color fillMutedLight = Color.fromRGBO(0, 0, 0, 0.12);
|
||||
const Color fillFaintLight = Color.fromRGBO(0, 0, 0, 0.04);
|
||||
const Color fillFaintPressedLight = Color.fromRGBO(0, 0, 0, 0.08);
|
||||
|
||||
const Color fillBaseDark = Color.fromRGBO(255, 255, 255, 1);
|
||||
const Color fillMutedDark = Color.fromRGBO(255, 255, 255, 0.16);
|
||||
const Color fillFaintDark = Color.fromRGBO(255, 255, 255, 0.12);
|
||||
const Color fillFaintPressedDark = Color.fromRGBO(255, 255, 255, 0.06);
|
||||
|
||||
// Stroke Colors
|
||||
const Color strokeBaseLight = Color.fromRGBO(0, 0, 0, 1);
|
||||
|
|
|
@ -36,10 +36,20 @@ EnteTheme darkTheme = EnteTheme(
|
|||
shadowButton: shadowButtonDark,
|
||||
);
|
||||
|
||||
EnteColorScheme getEnteColorScheme(BuildContext context) {
|
||||
return Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||
EnteColorScheme getEnteColorScheme(
|
||||
BuildContext context, {
|
||||
bool inverse = false,
|
||||
}) {
|
||||
return inverse
|
||||
? Theme.of(context).colorScheme.inverseEnteTheme.colorScheme
|
||||
: Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||
}
|
||||
|
||||
EnteTextTheme getEnteTextTheme(BuildContext context) {
|
||||
return Theme.of(context).colorScheme.enteTheme.textTheme;
|
||||
EnteTextTheme getEnteTextTheme(
|
||||
BuildContext context, {
|
||||
bool inverse = false,
|
||||
}) {
|
||||
return inverse
|
||||
? Theme.of(context).colorScheme.inverseEnteTheme.textTheme
|
||||
: Theme.of(context).colorScheme.enteTheme.textTheme;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
|
||||
class EnteLoadingWidget extends StatelessWidget {
|
||||
final Color? color;
|
||||
const EnteLoadingWidget({this.color, Key? key}) : super(key: key);
|
||||
final bool is20pts;
|
||||
const EnteLoadingWidget({this.is20pts = false, this.color, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
padding: EdgeInsets.all(is20pts ? 3 : 5),
|
||||
child: SizedBox.fromSize(
|
||||
size: const Size.square(14),
|
||||
child: CircularProgressIndicator(
|
||||
|
|
181
lib/ui/components/action_sheet_widget.dart
Normal file
181
lib/ui/components/action_sheet_widget.dart
Normal file
|
@ -0,0 +1,181 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
import 'package:photos/theme/colors.dart';
|
||||
import 'package:photos/theme/effects.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/utils/separators_util.dart';
|
||||
|
||||
enum ActionSheetType {
|
||||
defaultActionSheet,
|
||||
iconOnly,
|
||||
}
|
||||
|
||||
void showActionSheet({
|
||||
required BuildContext context,
|
||||
required List<Widget> buttons,
|
||||
required ActionSheetType actionSheetType,
|
||||
bool isCheckIconGreen = false,
|
||||
String? title,
|
||||
String? body,
|
||||
}) {
|
||||
showMaterialModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: backdropMutedDark,
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return ActionSheetWidget(
|
||||
title: title,
|
||||
body: body,
|
||||
actionButtons: buttons,
|
||||
actionSheetType: actionSheetType,
|
||||
isCheckIconGreen: isCheckIconGreen,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class ActionSheetWidget extends StatelessWidget {
|
||||
final String? title;
|
||||
final String? body;
|
||||
final List<Widget> actionButtons;
|
||||
final ActionSheetType actionSheetType;
|
||||
final bool isCheckIconGreen;
|
||||
|
||||
const ActionSheetWidget({
|
||||
required this.actionButtons,
|
||||
required this.actionSheetType,
|
||||
required this.isCheckIconGreen,
|
||||
this.title,
|
||||
this.body,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isTitleAndBodyNull = title == null && body == null;
|
||||
final blur = MediaQuery.of(context).platformBrightness == Brightness.light
|
||||
? blurMuted
|
||||
: blurBase;
|
||||
final extraWidth = MediaQuery.of(context).size.width - restrictedMaxWidth;
|
||||
final double? horizontalPadding = extraWidth > 0 ? extraWidth / 2 : null;
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
horizontalPadding ?? 12,
|
||||
12,
|
||||
horizontalPadding ?? 12,
|
||||
32,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(boxShadow: shadowMenuLight),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
|
||||
child: Container(
|
||||
color: backdropBaseDark,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
24,
|
||||
24,
|
||||
24,
|
||||
isTitleAndBodyNull ? 24 : 28,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
isTitleAndBodyNull
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 36),
|
||||
child: ContentContainerWidget(
|
||||
title: title,
|
||||
body: body,
|
||||
actionSheetType: actionSheetType,
|
||||
isCheckIconGreen: isCheckIconGreen,
|
||||
),
|
||||
),
|
||||
ActionButtons(
|
||||
actionButtons,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContentContainerWidget extends StatelessWidget {
|
||||
final String? title;
|
||||
final String? body;
|
||||
final ActionSheetType actionSheetType;
|
||||
final bool isCheckIconGreen;
|
||||
const ContentContainerWidget({
|
||||
required this.actionSheetType,
|
||||
required this.isCheckIconGreen,
|
||||
this.title,
|
||||
this.body,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
//todo: set cross axis to center when icon should be shown in place of body
|
||||
crossAxisAlignment: actionSheetType == ActionSheetType.defaultActionSheet
|
||||
? CrossAxisAlignment.stretch
|
||||
: CrossAxisAlignment.center,
|
||||
children: [
|
||||
title == null
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
title!,
|
||||
style: textTheme.h3Bold
|
||||
.copyWith(color: textBaseDark), //constant color
|
||||
),
|
||||
title == null || body == null
|
||||
? const SizedBox.shrink()
|
||||
: const SizedBox(height: 19),
|
||||
actionSheetType == ActionSheetType.defaultActionSheet
|
||||
? body == null
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
body!,
|
||||
style: textTheme.body
|
||||
.copyWith(color: textMutedDark), //constant color
|
||||
)
|
||||
: Icon(Icons.check_outlined,
|
||||
size: 48,
|
||||
color: isCheckIconGreen
|
||||
? getEnteColorScheme(context).primary700
|
||||
: strokeBaseDark)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ActionButtons extends StatelessWidget {
|
||||
final List<Widget> actionButtons;
|
||||
const ActionButtons(this.actionButtons, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actionButtonsWithSeparators = actionButtons;
|
||||
return Column(
|
||||
children:
|
||||
//Separator height is 8pts in figma. -2pts here as the action
|
||||
//buttons are 2pts extra in height in code compared to figma because
|
||||
//of the border(1pt top + 1pt bottom) of action buttons.
|
||||
addSeparators(actionButtonsWithSeparators, const SizedBox(height: 6)),
|
||||
);
|
||||
}
|
||||
}
|
111
lib/ui/components/blur_menu_item_widget.dart
Normal file
111
lib/ui/components/blur_menu_item_widget.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
|
||||
class BlurMenuItemWidget extends StatefulWidget {
|
||||
final IconData? leadingIcon;
|
||||
final String? labelText;
|
||||
final Color? menuItemColor;
|
||||
final Color? pressedColor;
|
||||
final VoidCallback? onTap;
|
||||
const BlurMenuItemWidget({
|
||||
this.leadingIcon,
|
||||
this.labelText,
|
||||
this.menuItemColor,
|
||||
this.pressedColor,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<BlurMenuItemWidget> createState() => _BlurMenuItemWidgetState();
|
||||
}
|
||||
|
||||
class _BlurMenuItemWidgetState extends State<BlurMenuItemWidget> {
|
||||
Color? menuItemColor;
|
||||
@override
|
||||
void initState() {
|
||||
menuItemColor = widget.menuItemColor;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
menuItemColor = widget.menuItemColor;
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onCancel,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 20),
|
||||
color: menuItemColor,
|
||||
padding: const EdgeInsets.only(left: 16, right: 12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
child: Row(
|
||||
children: [
|
||||
widget.leadingIcon != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Icon(
|
||||
widget.leadingIcon,
|
||||
size: 20,
|
||||
color: colorScheme.blurStrokeBase,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
widget.labelText != null
|
||||
? Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.labelText!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: getEnteTextTheme(context)
|
||||
.bodyBold
|
||||
.copyWith(color: colorScheme.blurTextBase),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onTapDown(details) {
|
||||
setState(() {
|
||||
menuItemColor = widget.pressedColor ?? widget.menuItemColor;
|
||||
});
|
||||
}
|
||||
|
||||
void _onTapUp(details) {
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
() => setState(() {
|
||||
menuItemColor = widget.menuItemColor;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void _onCancel() {
|
||||
setState(() {
|
||||
menuItemColor = widget.menuItemColor;
|
||||
});
|
||||
}
|
||||
}
|
107
lib/ui/components/bottom_action_bar/action_bar_widget.dart
Normal file
107
lib/ui/components/bottom_action_bar/action_bar_widget.dart
Normal file
|
@ -0,0 +1,107 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
|
||||
class ActionBarWidget extends StatefulWidget {
|
||||
final String? text;
|
||||
final List<Widget> iconButtons;
|
||||
final SelectedFiles? selectedFiles;
|
||||
const ActionBarWidget({
|
||||
required this.iconButtons,
|
||||
this.text,
|
||||
this.selectedFiles,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ActionBarWidget> createState() => _ActionBarWidgetState();
|
||||
}
|
||||
|
||||
class _ActionBarWidgetState extends State<ActionBarWidget> {
|
||||
final ValueNotifier<int> _selectedFilesNotifier = ValueNotifier(0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
widget.selectedFiles?.addListener(_selectedFilesListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.selectedFiles?.removeListener(_selectedFilesListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: _actionBarWidgets(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _actionBarWidgets(BuildContext context) {
|
||||
final actionBarWidgets = <Widget>[];
|
||||
final initialLength = widget.iconButtons.length;
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
|
||||
actionBarWidgets.addAll(widget.iconButtons);
|
||||
if (widget.text != null) {
|
||||
//adds 12 px spacing at the start and between iconButton elements
|
||||
for (var i = 0; i < initialLength; i++) {
|
||||
actionBarWidgets.insert(
|
||||
2 * i,
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
);
|
||||
}
|
||||
actionBarWidgets.insertAll(0, [
|
||||
const SizedBox(width: 20),
|
||||
Flexible(
|
||||
child: Row(
|
||||
children: [
|
||||
widget.selectedFiles != null
|
||||
? ValueListenableBuilder(
|
||||
valueListenable: _selectedFilesNotifier,
|
||||
builder: (context, value, child) {
|
||||
return Text(
|
||||
"${_selectedFilesNotifier.value} selectedsss",
|
||||
style: textTheme.small.copyWith(
|
||||
color: colorScheme.blurTextBase,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Text(
|
||||
widget.text!,
|
||||
style: textTheme.small
|
||||
.copyWith(color: colorScheme.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]);
|
||||
//to add whitespace of 8pts or 12 pts at the end
|
||||
if (widget.iconButtons.length > 1) {
|
||||
actionBarWidgets.add(
|
||||
const SizedBox(width: 8),
|
||||
);
|
||||
} else {
|
||||
actionBarWidgets.add(
|
||||
const SizedBox(width: 12),
|
||||
);
|
||||
}
|
||||
}
|
||||
return actionBarWidgets;
|
||||
}
|
||||
|
||||
void _selectedFilesListener() {
|
||||
if (widget.selectedFiles!.files.isNotEmpty) {
|
||||
_selectedFilesNotifier.value = widget.selectedFiles!.files.length;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
import 'package:photos/theme/effects.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/components/bottom_action_bar/action_bar_widget.dart';
|
||||
import 'package:photos/ui/components/icon_button_widget.dart';
|
||||
|
||||
class BottomActionBarWidget extends StatelessWidget {
|
||||
final String? text;
|
||||
final List<Widget>? iconButtons;
|
||||
final Widget expandedMenu;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final VoidCallback? onCancel;
|
||||
final bool hasSmallerBottomPadding;
|
||||
|
||||
BottomActionBarWidget({
|
||||
required this.expandedMenu,
|
||||
required this.hasSmallerBottomPadding,
|
||||
this.selectedFiles,
|
||||
this.text,
|
||||
this.iconButtons,
|
||||
this.onCancel,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ExpandableController _expandableController =
|
||||
ExpandableController(initialExpanded: false);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final widthOfScreen = MediaQuery.of(context).size.width;
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
final double leftRightPadding = widthOfScreen > restrictedMaxWidth
|
||||
? (widthOfScreen - restrictedMaxWidth) / 2
|
||||
: 0;
|
||||
return ClipRRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: blurBase, sigmaY: blurBase),
|
||||
child: Container(
|
||||
color: colorScheme.backdropBase,
|
||||
padding: EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: hasSmallerBottomPadding ? 24 : 36,
|
||||
right: leftRightPadding,
|
||||
left: leftRightPadding,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ExpandableNotifier(
|
||||
controller: _expandableController,
|
||||
child: ExpandablePanel(
|
||||
theme: _getExpandableTheme(),
|
||||
header: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: text == null ? 12 : 0,
|
||||
),
|
||||
child: ActionBarWidget(
|
||||
selectedFiles: selectedFiles,
|
||||
text: text,
|
||||
iconButtons: _iconButtons(context),
|
||||
),
|
||||
),
|
||||
expanded: expandedMenu,
|
||||
collapsed: const SizedBox.shrink(),
|
||||
controller: _expandableController,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
onCancel?.call();
|
||||
_expandableController.value = false;
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: textTheme.bodyBold
|
||||
.copyWith(color: colorScheme.blurTextBase),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _iconButtons(BuildContext context) {
|
||||
final iconButtonsWithExpansionIcon = <Widget?>[
|
||||
...?iconButtons,
|
||||
ExpansionIconWidget(expandableController: _expandableController)
|
||||
];
|
||||
iconButtonsWithExpansionIcon.removeWhere((element) => element == null);
|
||||
return iconButtonsWithExpansionIcon as List<Widget>;
|
||||
}
|
||||
|
||||
ExpandableThemeData _getExpandableTheme() {
|
||||
return const ExpandableThemeData(
|
||||
hasIcon: false,
|
||||
useInkWell: false,
|
||||
tapBodyToCollapse: false,
|
||||
tapBodyToExpand: false,
|
||||
tapHeaderToExpand: false,
|
||||
animationDuration: Duration(milliseconds: 400),
|
||||
crossFadePoint: 0.5,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExpansionIconWidget extends StatefulWidget {
|
||||
final ExpandableController expandableController;
|
||||
const ExpansionIconWidget({required this.expandableController, super.key});
|
||||
|
||||
@override
|
||||
State<ExpansionIconWidget> createState() => _ExpansionIconWidgetState();
|
||||
}
|
||||
|
||||
class _ExpansionIconWidgetState extends State<ExpansionIconWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
widget.expandableController.addListener(_expandableListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.expandableController.removeListener(_expandableListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.easeInOutExpo,
|
||||
child: widget.expandableController.value
|
||||
? IconButtonWidget(
|
||||
key: const ValueKey<bool>(false),
|
||||
onTap: () {
|
||||
widget.expandableController.value = false;
|
||||
setState(() {});
|
||||
},
|
||||
icon: Icons.expand_more_outlined,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
)
|
||||
: IconButtonWidget(
|
||||
key: const ValueKey<bool>(true),
|
||||
onTap: () {
|
||||
widget.expandableController.value = true;
|
||||
setState(() {});
|
||||
},
|
||||
icon: Icons.more_horiz_outlined,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_expandableListener() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ui/components/blur_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/divider_widget.dart';
|
||||
|
||||
class ExpandedMenuWidget extends StatelessWidget {
|
||||
final List<List<BlurMenuItemWidget>> items;
|
||||
const ExpandedMenuWidget({
|
||||
required this.items,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double itemHeight = 48;
|
||||
const double whiteSpaceBetweenSections = 16;
|
||||
const double dividerHeightBetweenItems = 1;
|
||||
int numberOfDividers = 0;
|
||||
double combinedHeightOfItems = 0;
|
||||
|
||||
for (List<BlurMenuItemWidget> group in items) {
|
||||
//no divider if there is only one item in the section/group
|
||||
if (group.length != 1) {
|
||||
numberOfDividers += (group.length - 1);
|
||||
}
|
||||
combinedHeightOfItems += group.length * itemHeight;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: SizedBox(
|
||||
height: combinedHeightOfItems +
|
||||
(dividerHeightBetweenItems * numberOfDividers) +
|
||||
(whiteSpaceBetweenSections * (items.length - 1)),
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, sectionIndex) {
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: SizedBox(
|
||||
height: itemHeight * items[sectionIndex].length +
|
||||
(dividerHeightBetweenItems *
|
||||
(items[sectionIndex].length - 1)),
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, itemIndex) {
|
||||
return items[sectionIndex][itemIndex];
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const DividerWidget(
|
||||
dividerType: DividerType.bottomBar,
|
||||
);
|
||||
},
|
||||
itemCount: items[sectionIndex].length,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: whiteSpaceBetweenSections);
|
||||
},
|
||||
itemCount: items.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -71,7 +71,7 @@ class _ExpandableMenuItemWidgetState extends State<ExpandableMenuItemWidget> {
|
|||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: widget.selectionOptionsWidget,
|
||||
),
|
||||
theme: getExpandableTheme(context),
|
||||
theme: getExpandableTheme(),
|
||||
controller: expandableController,
|
||||
),
|
||||
),
|
||||
|
|
320
lib/ui/components/large_button_widget.dart
Normal file
320
lib/ui/components/large_button_widget.dart
Normal file
|
@ -0,0 +1,320 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/theme/colors.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/theme/text_style.dart';
|
||||
import 'package:photos/ui/common/loading_widget.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
import 'package:photos/ui/components/models/large_button_style.dart';
|
||||
import 'package:photos/utils/debouncer.dart';
|
||||
|
||||
enum ExecutionState {
|
||||
idle,
|
||||
inProgress,
|
||||
successful,
|
||||
}
|
||||
|
||||
typedef FutureVoidCallback = Future<void> Function();
|
||||
|
||||
class LargeButtonWidget extends StatelessWidget {
|
||||
final IconData? icon;
|
||||
final String? labelText;
|
||||
final ButtonType buttonType;
|
||||
final FutureVoidCallback? onTap;
|
||||
final bool isDisabled;
|
||||
|
||||
///setting this flag to true will make the button appear like how it would
|
||||
///on dark theme irrespective of the app's theme.
|
||||
final bool isInActionSheet;
|
||||
const LargeButtonWidget({
|
||||
required this.buttonType,
|
||||
this.icon,
|
||||
this.labelText,
|
||||
this.onTap,
|
||||
this.isInActionSheet = false,
|
||||
this.isDisabled = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme =
|
||||
isInActionSheet ? darkScheme : getEnteColorScheme(context);
|
||||
final inverseColorScheme = isInActionSheet
|
||||
? lightScheme
|
||||
: getEnteColorScheme(context, inverse: true);
|
||||
final textTheme =
|
||||
isInActionSheet ? darkTextTheme : getEnteTextTheme(context);
|
||||
final inverseTextTheme = isInActionSheet
|
||||
? lightTextTheme
|
||||
: getEnteTextTheme(context, inverse: true);
|
||||
final buttonStyle = LargeButtonStyle(
|
||||
//Dummy default values since we need to keep these properties non-nullable
|
||||
defaultButtonColor: Colors.transparent,
|
||||
defaultBorderColor: Colors.transparent,
|
||||
defaultIconColor: Colors.transparent,
|
||||
defaultLabelStyle: textTheme.body,
|
||||
);
|
||||
buttonStyle.defaultButtonColor = buttonType.defaultButtonColor(colorScheme);
|
||||
buttonStyle.pressedButtonColor = buttonType.pressedButtonColor(colorScheme);
|
||||
buttonStyle.disabledButtonColor =
|
||||
buttonType.disabledButtonColor(colorScheme);
|
||||
buttonStyle.defaultBorderColor = buttonType.defaultBorderColor(colorScheme);
|
||||
buttonStyle.pressedBorderColor = buttonType.pressedBorderColor(colorScheme);
|
||||
buttonStyle.disabledBorderColor =
|
||||
buttonType.disabledBorderColor(colorScheme);
|
||||
buttonStyle.defaultIconColor = buttonType.defaultIconColor(
|
||||
colorScheme: colorScheme,
|
||||
inverseColorScheme: inverseColorScheme,
|
||||
);
|
||||
buttonStyle.pressedIconColor = buttonType.pressedIconColor(colorScheme);
|
||||
buttonStyle.disabledIconColor = buttonType.disabledIconColor(colorScheme);
|
||||
buttonStyle.defaultLabelStyle = buttonType.defaultLabelStyle(
|
||||
textTheme: textTheme,
|
||||
inverseTextTheme: inverseTextTheme,
|
||||
);
|
||||
buttonStyle.pressedLabelStyle =
|
||||
buttonType.pressedLabelStyle(textTheme, colorScheme);
|
||||
buttonStyle.disabledLabelStyle =
|
||||
buttonType.disabledLabelStyle(textTheme, colorScheme);
|
||||
buttonStyle.checkIconColor = buttonType.checkIconColor(colorScheme);
|
||||
|
||||
return LargeButtonChildWidget(
|
||||
buttonStyle: buttonStyle,
|
||||
buttonType: buttonType,
|
||||
isDisabled: isDisabled,
|
||||
onTap: onTap,
|
||||
labelText: labelText,
|
||||
icon: icon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LargeButtonChildWidget extends StatefulWidget {
|
||||
final LargeButtonStyle buttonStyle;
|
||||
final FutureVoidCallback? onTap;
|
||||
final ButtonType buttonType;
|
||||
final String? labelText;
|
||||
final IconData? icon;
|
||||
final bool isDisabled;
|
||||
const LargeButtonChildWidget({
|
||||
required this.buttonStyle,
|
||||
required this.buttonType,
|
||||
required this.isDisabled,
|
||||
this.onTap,
|
||||
this.labelText,
|
||||
this.icon,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LargeButtonChildWidget> createState() => _LargeButtonChildWidgetState();
|
||||
}
|
||||
|
||||
class _LargeButtonChildWidgetState extends State<LargeButtonChildWidget> {
|
||||
late Color buttonColor;
|
||||
late Color borderColor;
|
||||
late Color iconColor;
|
||||
late TextStyle labelStyle;
|
||||
late Color checkIconColor;
|
||||
late Color loadingIconColor;
|
||||
late bool hasExecutionStates;
|
||||
final _debouncer = Debouncer(const Duration(milliseconds: 300));
|
||||
ExecutionState executionState = ExecutionState.idle;
|
||||
@override
|
||||
void initState() {
|
||||
checkIconColor = widget.buttonStyle.checkIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
loadingIconColor = widget.buttonStyle.defaultIconColor;
|
||||
hasExecutionStates = widget.buttonType.hasExecutionStates;
|
||||
if (widget.isDisabled) {
|
||||
buttonColor = widget.buttonStyle.disabledButtonColor ??
|
||||
widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.disabledBorderColor ??
|
||||
widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.disabledIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.disabledLabelStyle ??
|
||||
widget.buttonStyle.defaultLabelStyle;
|
||||
} else {
|
||||
buttonColor = widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.defaultLabelStyle;
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _shouldRegisterGestures ? _onTap : null,
|
||||
onTapDown: _shouldRegisterGestures ? _onTapDown : null,
|
||||
onTapUp: _shouldRegisterGestures ? _onTapUp : null,
|
||||
onTapCancel: _shouldRegisterGestures ? _onTapCancel : null,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 16),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
color: buttonColor,
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 175),
|
||||
switchInCurve: Curves.easeInOutExpo,
|
||||
switchOutCurve: Curves.easeInOutExpo,
|
||||
child: executionState == ExecutionState.idle
|
||||
? widget.buttonType.hasTrailingIcon
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
widget.labelText == null
|
||||
? const SizedBox.shrink()
|
||||
: Flexible(
|
||||
child: Padding(
|
||||
padding: widget.icon == null
|
||||
? const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
)
|
||||
: const EdgeInsets.only(right: 16),
|
||||
child: Text(
|
||||
widget.labelText!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
style: labelStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.icon == null
|
||||
? const SizedBox.shrink()
|
||||
: Icon(
|
||||
widget.icon,
|
||||
size: 20,
|
||||
color: iconColor,
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
widget.icon == null
|
||||
? const SizedBox.shrink()
|
||||
: Icon(
|
||||
widget.icon,
|
||||
size: 20,
|
||||
color: iconColor,
|
||||
),
|
||||
widget.icon == null || widget.labelText == null
|
||||
? const SizedBox.shrink()
|
||||
: const SizedBox(width: 8),
|
||||
widget.labelText == null
|
||||
? const SizedBox.shrink()
|
||||
: Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Text(
|
||||
widget.labelText!,
|
||||
style: labelStyle,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: executionState == ExecutionState.inProgress
|
||||
? EnteLoadingWidget(
|
||||
is20pts: true,
|
||||
color: loadingIconColor,
|
||||
)
|
||||
: executionState == ExecutionState.successful
|
||||
? Icon(
|
||||
Icons.check_outlined,
|
||||
size: 20,
|
||||
color: checkIconColor,
|
||||
)
|
||||
: const SizedBox.shrink(), //fallback
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool get _shouldRegisterGestures =>
|
||||
!widget.isDisabled &&
|
||||
(widget.onTap != null) &&
|
||||
executionState == ExecutionState.idle;
|
||||
|
||||
void _onTap() async {
|
||||
if (hasExecutionStates) {
|
||||
_debouncer.run(
|
||||
() => Future(() {
|
||||
setState(() {
|
||||
executionState = ExecutionState.inProgress;
|
||||
});
|
||||
}),
|
||||
);
|
||||
await widget.onTap!
|
||||
.call()
|
||||
.onError((error, stackTrace) => _debouncer.cancelDebounce());
|
||||
_debouncer.cancelDebounce();
|
||||
// when the time taken by widget.onTap is approximately equal to the debounce
|
||||
// time, the callback is getting executed when/after the if condition
|
||||
// below is executing/executed which results in execution state stuck at
|
||||
// idle state. This Future is for delaying the execution of the if
|
||||
// condition so that the calback in the debouncer finishes execution before.
|
||||
await Future.delayed(const Duration(milliseconds: 5));
|
||||
if (executionState == ExecutionState.inProgress) {
|
||||
setState(() {
|
||||
executionState = ExecutionState.successful;
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
setState(() {
|
||||
executionState = ExecutionState.idle;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
widget.onTap!.call();
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapDown(details) {
|
||||
setState(() {
|
||||
buttonColor = widget.buttonStyle.pressedButtonColor ??
|
||||
widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.pressedBorderColor ??
|
||||
widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.pressedIconColor ??
|
||||
widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.pressedLabelStyle ??
|
||||
widget.buttonStyle.defaultLabelStyle;
|
||||
});
|
||||
}
|
||||
|
||||
void _onTapUp(details) {
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 84),
|
||||
() => setState(() {
|
||||
setAllStylesToDefault();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void _onTapCancel() {
|
||||
setState(() {
|
||||
setAllStylesToDefault();
|
||||
});
|
||||
}
|
||||
|
||||
void setAllStylesToDefault() {
|
||||
buttonColor = widget.buttonStyle.defaultButtonColor;
|
||||
borderColor = widget.buttonStyle.defaultBorderColor;
|
||||
iconColor = widget.buttonStyle.defaultIconColor;
|
||||
labelStyle = widget.buttonStyle.defaultLabelStyle;
|
||||
}
|
||||
}
|
197
lib/ui/components/models/button_type.dart
Normal file
197
lib/ui/components/models/button_type.dart
Normal file
|
@ -0,0 +1,197 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/theme/colors.dart';
|
||||
import 'package:photos/theme/text_style.dart';
|
||||
|
||||
enum ButtonType {
|
||||
primary,
|
||||
secondary,
|
||||
neutral,
|
||||
trailingIcon,
|
||||
critical,
|
||||
tertiaryCritical,
|
||||
trailingIconPrimary,
|
||||
trailingIconSecondary;
|
||||
|
||||
bool get isPrimary =>
|
||||
this == ButtonType.primary || this == ButtonType.trailingIconPrimary;
|
||||
|
||||
bool get hasTrailingIcon =>
|
||||
this == ButtonType.trailingIcon ||
|
||||
this == ButtonType.trailingIconPrimary ||
|
||||
this == ButtonType.trailingIconSecondary;
|
||||
|
||||
bool get isSecondary =>
|
||||
this == ButtonType.secondary || this == ButtonType.trailingIconSecondary;
|
||||
|
||||
bool get isCritical =>
|
||||
this == ButtonType.critical || this == ButtonType.tertiaryCritical;
|
||||
|
||||
Color defaultButtonColor(EnteColorScheme colorScheme) {
|
||||
if (isPrimary) {
|
||||
return colorScheme.primary500;
|
||||
}
|
||||
if (isSecondary) {
|
||||
return colorScheme.fillFaint;
|
||||
}
|
||||
if (this == ButtonType.neutral || this == ButtonType.trailingIcon) {
|
||||
return colorScheme.fillBase;
|
||||
}
|
||||
if (this == ButtonType.critical) {
|
||||
return colorScheme.warning700;
|
||||
}
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return Colors.transparent;
|
||||
}
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
Color? pressedButtonColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.primary) {
|
||||
return colorScheme.primary700;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
Color? disabledButtonColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.primary || this == ButtonType.critical) {
|
||||
return colorScheme.fillFaint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Color defaultBorderColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return colorScheme.warning700;
|
||||
}
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
Color? pressedBorderColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.primary) {
|
||||
return colorScheme.strokeMuted;
|
||||
}
|
||||
if (this == ButtonType.secondary ||
|
||||
this == ButtonType.critical ||
|
||||
this == ButtonType.tertiaryCritical) {
|
||||
return colorScheme.strokeBase;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
Color? disabledBorderColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.primary ||
|
||||
this == ButtonType.secondary ||
|
||||
this == ButtonType.critical) {
|
||||
return Colors.transparent;
|
||||
}
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return colorScheme.strokeMuted;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Color defaultIconColor({
|
||||
required EnteColorScheme colorScheme,
|
||||
required EnteColorScheme inverseColorScheme,
|
||||
}) {
|
||||
if (isPrimary || this == ButtonType.critical) {
|
||||
return strokeBaseDark;
|
||||
}
|
||||
if (isSecondary) {
|
||||
return colorScheme.strokeBase;
|
||||
}
|
||||
if (this == ButtonType.neutral || this == ButtonType.trailingIcon) {
|
||||
return inverseColorScheme.strokeBase;
|
||||
}
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return colorScheme.warning500;
|
||||
}
|
||||
//fallback
|
||||
return colorScheme.strokeBase;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
Color? pressedIconColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return colorScheme.strokeBase;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
Color? disabledIconColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.primary || this == ButtonType.secondary) {
|
||||
return colorScheme.strokeMuted;
|
||||
}
|
||||
if (this == ButtonType.critical || this == ButtonType.tertiaryCritical) {
|
||||
return colorScheme.strokeFaint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
TextStyle defaultLabelStyle({
|
||||
required EnteTextTheme textTheme,
|
||||
required EnteTextTheme inverseTextTheme,
|
||||
}) {
|
||||
if (isPrimary || this == ButtonType.critical) {
|
||||
return textTheme.bodyBold.copyWith(color: textBaseDark);
|
||||
}
|
||||
if (isSecondary) {
|
||||
return textTheme.bodyBold;
|
||||
}
|
||||
if (this == ButtonType.neutral || this == ButtonType.trailingIcon) {
|
||||
return inverseTextTheme.bodyBold;
|
||||
}
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return textTheme.bodyBold.copyWith(color: warning500);
|
||||
}
|
||||
//fallback
|
||||
return textTheme.bodyBold;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
TextStyle? pressedLabelStyle(
|
||||
EnteTextTheme textTheme,
|
||||
EnteColorScheme colorScheme,
|
||||
) {
|
||||
if (this == ButtonType.tertiaryCritical) {
|
||||
return textTheme.bodyBold.copyWith(color: colorScheme.strokeBase);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Returning null to fallback to default color
|
||||
TextStyle? disabledLabelStyle(
|
||||
EnteTextTheme textTheme,
|
||||
EnteColorScheme colorScheme,
|
||||
) {
|
||||
if (this == ButtonType.primary ||
|
||||
this == ButtonType.secondary ||
|
||||
this == ButtonType.critical ||
|
||||
this == ButtonType.tertiaryCritical) {
|
||||
return textTheme.bodyBold.copyWith(color: colorScheme.textFaint);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Color? checkIconColor(EnteColorScheme colorScheme) {
|
||||
if (this == ButtonType.secondary) {
|
||||
return colorScheme.primary500;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get hasExecutionStates {
|
||||
if (this == ButtonType.primary ||
|
||||
this == ButtonType.secondary ||
|
||||
this == ButtonType.neutral) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
33
lib/ui/components/models/large_button_style.dart
Normal file
33
lib/ui/components/models/large_button_style.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class LargeButtonStyle {
|
||||
Color defaultButtonColor;
|
||||
Color? pressedButtonColor;
|
||||
Color? disabledButtonColor;
|
||||
Color defaultBorderColor;
|
||||
Color? pressedBorderColor;
|
||||
Color? disabledBorderColor;
|
||||
Color defaultIconColor;
|
||||
Color? pressedIconColor;
|
||||
Color? disabledIconColor;
|
||||
TextStyle defaultLabelStyle;
|
||||
TextStyle? pressedLabelStyle;
|
||||
TextStyle? disabledLabelStyle;
|
||||
Color? checkIconColor;
|
||||
|
||||
LargeButtonStyle({
|
||||
required this.defaultButtonColor,
|
||||
this.pressedButtonColor,
|
||||
this.disabledButtonColor,
|
||||
required this.defaultBorderColor,
|
||||
this.pressedBorderColor,
|
||||
this.disabledBorderColor,
|
||||
required this.defaultIconColor,
|
||||
this.pressedIconColor,
|
||||
this.disabledIconColor,
|
||||
required this.defaultLabelStyle,
|
||||
this.pressedLabelStyle,
|
||||
this.disabledLabelStyle,
|
||||
this.checkIconColor,
|
||||
});
|
||||
}
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
Widget sectionOptionSpacing = const SizedBox(height: 6);
|
||||
|
||||
ExpandableThemeData getExpandableTheme(BuildContext context) {
|
||||
ExpandableThemeData getExpandableTheme() {
|
||||
return const ExpandableThemeData(
|
||||
hasIcon: false,
|
||||
useInkWell: false,
|
||||
|
|
|
@ -10,7 +10,6 @@ import 'package:flutter_sodium/flutter_sodium.dart';
|
|||
import 'package:photos/ente_theme_data.dart';
|
||||
import 'package:photos/models/collection.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/services/feature_flag_service.dart';
|
||||
import 'package:photos/theme/colors.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
|
||||
|
@ -62,42 +61,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
|||
Widget build(BuildContext context) {
|
||||
final enteColorScheme = getEnteColorScheme(context);
|
||||
final PublicURL url = widget.collection?.publicURLs?.firstOrNull;
|
||||
|
||||
final enableCollectFeature = FeatureFlagService.instance.enableCollect();
|
||||
final Widget collect = enableCollectFeature
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Allow adding photos",
|
||||
),
|
||||
alignCaptionedTextToLeft: true,
|
||||
menuItemColor: getEnteColorScheme(context).fillFaint,
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingWidget: Switch.adaptive(
|
||||
value: widget
|
||||
.collection.publicURLs?.firstOrNull?.enableCollect ??
|
||||
false,
|
||||
onChanged: (value) async {
|
||||
await _updateUrlSettings(
|
||||
context,
|
||||
{'enableCollect': value},
|
||||
);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
const MenuSectionDescriptionWidget(
|
||||
content:
|
||||
"Allow people with the link to also add photos to the shared "
|
||||
"album.",
|
||||
),
|
||||
const SizedBox(height: 24)
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
appBar: AppBar(
|
||||
|
@ -114,7 +77,33 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
collect,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Allow adding photos",
|
||||
),
|
||||
alignCaptionedTextToLeft: true,
|
||||
menuItemColor: getEnteColorScheme(context).fillFaint,
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingWidget: Switch.adaptive(
|
||||
value: widget.collection.publicURLs?.firstOrNull
|
||||
?.enableCollect ??
|
||||
false,
|
||||
onChanged: (value) async {
|
||||
await _updateUrlSettings(
|
||||
context,
|
||||
{'enableCollect': value},
|
||||
);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
const MenuSectionDescriptionWidget(
|
||||
content:
|
||||
"Allow people with the link to also add photos to the shared "
|
||||
"album.",
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
MenuItemWidget(
|
||||
alignCaptionedTextToLeft: true,
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
|
|
|
@ -23,7 +23,7 @@ class UserAvatarWidget extends StatelessWidget {
|
|||
final enteTextTheme = getEnteTextTheme(context);
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final displayChar = (user.name == null || user.name!.isEmpty)
|
||||
? ((user.email.isEmpty ?? true) ? " " : user.email.substring(0, 1))
|
||||
? ((user.email.isEmpty) ? " " : user.email.substring(0, 1))
|
||||
: user.name!.substring(0, 1);
|
||||
final randomColor = colorScheme.avatarColors[
|
||||
(user.id ?? 0).remainder(colorScheme.avatarColors.length)];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/db/files_db.dart';
|
||||
import 'package:photos/events/collection_updated_event.dart';
|
||||
|
@ -10,19 +11,24 @@ import 'package:photos/models/file_load_result.dart';
|
|||
import 'package:photos/models/gallery_type.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
import 'package:photos/services/ignored_files_service.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/components/blur_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/bottom_action_bar/bottom_action_bar_widget.dart';
|
||||
import 'package:photos/ui/components/bottom_action_bar/expanded_menu_widget.dart';
|
||||
import 'package:photos/ui/components/icon_button_widget.dart';
|
||||
import 'package:photos/ui/viewer/gallery/empty_state.dart';
|
||||
import 'package:photos/ui/viewer/gallery/gallery.dart';
|
||||
import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
|
||||
import 'package:photos/ui/viewer/gallery/gallery_overlay_widget.dart';
|
||||
import 'package:photos/utils/delete_file_util.dart';
|
||||
import 'package:photos/utils/share_util.dart';
|
||||
|
||||
class CollectionPage extends StatelessWidget {
|
||||
class CollectionPage extends StatefulWidget {
|
||||
final CollectionWithThumbnail c;
|
||||
final String tagPrefix;
|
||||
final GalleryType appBarType;
|
||||
final _selectedFiles = SelectedFiles();
|
||||
final bool hasVerifiedLock;
|
||||
|
||||
CollectionPage(
|
||||
const CollectionPage(
|
||||
this.c, {
|
||||
this.tagPrefix = "collection",
|
||||
this.appBarType = GalleryType.ownedCollection,
|
||||
|
@ -30,17 +36,41 @@ class CollectionPage extends StatelessWidget {
|
|||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CollectionPage> createState() => _CollectionPageState();
|
||||
}
|
||||
|
||||
class _CollectionPageState extends State<CollectionPage> {
|
||||
final _selectedFiles = SelectedFiles();
|
||||
final GlobalKey shareButtonKey = GlobalKey();
|
||||
|
||||
final ValueNotifier<double> _bottomPosition = ValueNotifier(-150.0);
|
||||
@override
|
||||
void initState() {
|
||||
_selectedFiles.addListener(_selectedFilesListener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_selectedFiles.removeListener(_selectedFilesListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(Object context) {
|
||||
if (hasVerifiedLock == false && c.collection.isHidden()) {
|
||||
if (widget.hasVerifiedLock == false && widget.c.collection.isHidden()) {
|
||||
return const EmptyState();
|
||||
}
|
||||
final initialFiles = c.thumbnail != null ? [c.thumbnail] : null;
|
||||
final int ownerID = Configuration.instance.getUserID();
|
||||
final int collectionOwner = widget.c.collection.owner.id;
|
||||
final initialFiles =
|
||||
widget.c.thumbnail != null ? [widget.c.thumbnail] : null;
|
||||
final gallery = Gallery(
|
||||
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
|
||||
final FileLoadResult result =
|
||||
await FilesDB.instance.getFilesInCollection(
|
||||
c.collection.id,
|
||||
widget.c.collection.id,
|
||||
creationStartTime,
|
||||
creationEndTime,
|
||||
limit: limit,
|
||||
|
@ -57,38 +87,89 @@ class CollectionPage extends StatelessWidget {
|
|||
},
|
||||
reloadEvent: Bus.instance
|
||||
.on<CollectionUpdatedEvent>()
|
||||
.where((event) => event.collectionID == c.collection.id),
|
||||
.where((event) => event.collectionID == widget.c.collection.id),
|
||||
removalEventTypes: const {
|
||||
EventType.deletedFromRemote,
|
||||
EventType.deletedFromEverywhere,
|
||||
EventType.hide,
|
||||
},
|
||||
tagPrefix: tagPrefix,
|
||||
tagPrefix: widget.tagPrefix,
|
||||
selectedFiles: _selectedFiles,
|
||||
initialFiles: initialFiles,
|
||||
albumName: c.collection.name,
|
||||
albumName: widget.c.collection.name,
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(50.0),
|
||||
child: GalleryAppBarWidget(
|
||||
appBarType,
|
||||
c.collection.name,
|
||||
widget.appBarType,
|
||||
widget.c.collection.name,
|
||||
_selectedFiles,
|
||||
collection: c.collection,
|
||||
collection: widget.c.collection,
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
gallery,
|
||||
GalleryOverlayWidget(
|
||||
appBarType,
|
||||
_selectedFiles,
|
||||
collection: c.collection,
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _bottomPosition,
|
||||
builder: (context, value, child) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return AnimatedPositioned(
|
||||
curve: Curves.easeInOutExpo,
|
||||
bottom: _bottomPosition.value,
|
||||
right: 0,
|
||||
left: 0,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: BottomActionBarWidget(
|
||||
selectedFiles: _selectedFiles,
|
||||
hasSmallerBottomPadding: true,
|
||||
expandedMenu: ExpandedMenuWidget(
|
||||
items: [
|
||||
[
|
||||
BlurMenuItemWidget(
|
||||
leadingIcon: Icons.add_outlined,
|
||||
labelText: "One",
|
||||
menuItemColor: colorScheme.fillFaint,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
text: _selectedFiles.files.length.toString() + ' selected',
|
||||
onCancel: () {
|
||||
if (_selectedFiles.files.isNotEmpty) {
|
||||
_selectedFiles.clearAll();
|
||||
}
|
||||
},
|
||||
iconButtons: [
|
||||
IconButtonWidget(
|
||||
icon: Icons.delete_outlined,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
onTap: () => showDeleteSheet(context, _selectedFiles),
|
||||
),
|
||||
IconButtonWidget(
|
||||
icon: Icons.ios_share_outlined,
|
||||
iconButtonType: IconButtonType.primary,
|
||||
onTap: () => shareSelected(
|
||||
context,
|
||||
shareButtonKey,
|
||||
_selectedFiles.files,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_selectedFilesListener() {
|
||||
_selectedFiles.files.isNotEmpty
|
||||
? _bottomPosition.value = 0.0
|
||||
: _bottomPosition.value = -150.0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'dart:io';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
@ -16,6 +17,7 @@ import 'package:photos/events/collection_updated_event.dart';
|
|||
import 'package:photos/events/files_updated_event.dart';
|
||||
import 'package:photos/events/local_photos_updated_event.dart';
|
||||
import 'package:photos/models/file.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
import 'package:photos/models/trash_item_request.dart';
|
||||
import 'package:photos/services/remote_sync_service.dart';
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
|
@ -485,3 +487,100 @@ Future<bool> shouldProceedWithDeletion(BuildContext context) async {
|
|||
);
|
||||
return choice == DialogUserChoice.secondChoice;
|
||||
}
|
||||
|
||||
void showDeleteSheet(BuildContext context, SelectedFiles selectedFiles) {
|
||||
final count = selectedFiles.files.length;
|
||||
bool containsUploadedFile = false, containsLocalFile = false;
|
||||
for (final file in selectedFiles.files) {
|
||||
if (file.uploadedFileID != null) {
|
||||
containsUploadedFile = true;
|
||||
}
|
||||
if (file.localID != null) {
|
||||
containsLocalFile = true;
|
||||
}
|
||||
}
|
||||
final actions = <Widget>[];
|
||||
if (containsUploadedFile && containsLocalFile) {
|
||||
actions.add(
|
||||
CupertinoActionSheetAction(
|
||||
isDestructiveAction: true,
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
await deleteFilesOnDeviceOnly(
|
||||
context,
|
||||
selectedFiles.files.toList(),
|
||||
);
|
||||
selectedFiles.clearAll();
|
||||
showToast(context, "Files deleted from device");
|
||||
},
|
||||
child: const Text("Device"),
|
||||
),
|
||||
);
|
||||
actions.add(
|
||||
CupertinoActionSheetAction(
|
||||
isDestructiveAction: true,
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
await deleteFilesFromRemoteOnly(
|
||||
context,
|
||||
selectedFiles.files.toList(),
|
||||
);
|
||||
selectedFiles.clearAll();
|
||||
|
||||
showShortToast(context, "Moved to trash");
|
||||
},
|
||||
child: const Text("ente"),
|
||||
),
|
||||
);
|
||||
actions.add(
|
||||
CupertinoActionSheetAction(
|
||||
isDestructiveAction: true,
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
await deleteFilesFromEverywhere(
|
||||
context,
|
||||
selectedFiles.files.toList(),
|
||||
);
|
||||
selectedFiles.clearAll();
|
||||
},
|
||||
child: const Text("Everywhere"),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
actions.add(
|
||||
CupertinoActionSheetAction(
|
||||
isDestructiveAction: true,
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
await deleteFilesFromEverywhere(
|
||||
context,
|
||||
selectedFiles.files.toList(),
|
||||
);
|
||||
selectedFiles.clearAll();
|
||||
},
|
||||
child: const Text("Delete"),
|
||||
),
|
||||
);
|
||||
}
|
||||
final action = CupertinoActionSheet(
|
||||
title: Text(
|
||||
"Delete " +
|
||||
count.toString() +
|
||||
" file" +
|
||||
(count == 1 ? "" : "s") +
|
||||
(containsUploadedFile && containsLocalFile ? " from" : "?"),
|
||||
),
|
||||
actions: actions,
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
child: const Text("Cancel"),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) => action,
|
||||
barrierColor: Colors.black.withOpacity(0.75),
|
||||
);
|
||||
}
|
||||
|
|
13
lib/utils/separators_util.dart
Normal file
13
lib/utils/separators_util.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
//This method returns a newly declared list with separators. It will not
|
||||
//modify the original list
|
||||
List<Widget> addSeparators(List<Widget> listOfWidgets, Widget separator) {
|
||||
final int initialLength = listOfWidgets.length;
|
||||
final listOfWidgetsWithSeparators = <Widget>[];
|
||||
listOfWidgetsWithSeparators.addAll(listOfWidgets);
|
||||
for (var i = 1; i < initialLength; i++) {
|
||||
listOfWidgetsWithSeparators.insert((2 * i) - 1, separator);
|
||||
}
|
||||
return listOfWidgetsWithSeparators;
|
||||
}
|
|
@ -146,3 +146,15 @@ DateTime parseDateFromFileNam1e(String fileName) {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
void shareSelected(
|
||||
BuildContext context,
|
||||
GlobalKey shareButtonKey,
|
||||
Set<File> selectedFiles,
|
||||
) {
|
||||
share(
|
||||
context,
|
||||
selectedFiles.toList(),
|
||||
shareButtonKey: shareButtonKey,
|
||||
);
|
||||
}
|
||||
|
|
42
pubspec.lock
42
pubspec.lock
|
@ -49,7 +49,7 @@ packages:
|
|||
name: async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
version: "2.8.2"
|
||||
background_fetch:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -98,7 +98,14 @@ packages:
|
|||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
chewie:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -112,7 +119,7 @@ packages:
|
|||
name: clock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.0"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -301,7 +308,7 @@ packages:
|
|||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.0"
|
||||
fast_base58:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -700,6 +707,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
keyboard_visibility:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: keyboard_visibility
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.6"
|
||||
like_button:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -755,14 +769,14 @@ packages:
|
|||
name: matcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.12"
|
||||
version: "0.12.11"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
version: "0.1.4"
|
||||
media_extension:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -778,7 +792,7 @@ packages:
|
|||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
version: "1.7.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -906,7 +920,7 @@ packages:
|
|||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.8.1"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1247,7 +1261,7 @@ packages:
|
|||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
version: "1.8.2"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1303,7 +1317,7 @@ packages:
|
|||
name: string_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.0"
|
||||
syncfusion_flutter_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1331,28 +1345,28 @@ packages:
|
|||
name: term_glyph
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.0"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.21.4"
|
||||
version: "1.21.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.12"
|
||||
version: "0.4.9"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
version: "0.4.13"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -12,7 +12,7 @@ description: ente photos application
|
|||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 0.6.72+392
|
||||
version: 0.6.74+394
|
||||
|
||||
environment:
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
|
|
Loading…
Add table
Reference in a new issue