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
|
- Flutter
|
||||||
- in_app_purchase_storekit (0.0.1):
|
- in_app_purchase_storekit (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- keyboard_visibility (0.5.0):
|
||||||
|
- Flutter
|
||||||
|
- Reachability
|
||||||
- libwebp (1.2.3):
|
- libwebp (1.2.3):
|
||||||
- libwebp/demux (= 1.2.3)
|
- libwebp/demux (= 1.2.3)
|
||||||
- libwebp/mux (= 1.2.3)
|
- libwebp/mux (= 1.2.3)
|
||||||
|
@ -193,6 +196,7 @@ DEPENDENCIES:
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- image_editor (from `.symlinks/plugins/image_editor/ios`)
|
- image_editor (from `.symlinks/plugins/image_editor/ios`)
|
||||||
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/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`)
|
- local_auth (from `.symlinks/plugins/local_auth/ios`)
|
||||||
- media_extension (from `.symlinks/plugins/media_extension/ios`)
|
- media_extension (from `.symlinks/plugins/media_extension/ios`)
|
||||||
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
|
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
|
||||||
|
@ -271,6 +275,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/image_editor/ios"
|
:path: ".symlinks/plugins/image_editor/ios"
|
||||||
in_app_purchase_storekit:
|
in_app_purchase_storekit:
|
||||||
:path: ".symlinks/plugins/in_app_purchase_storekit/ios"
|
:path: ".symlinks/plugins/in_app_purchase_storekit/ios"
|
||||||
|
keyboard_visibility:
|
||||||
|
:path: ".symlinks/plugins/keyboard_visibility/ios"
|
||||||
local_auth:
|
local_auth:
|
||||||
:path: ".symlinks/plugins/local_auth/ios"
|
:path: ".symlinks/plugins/local_auth/ios"
|
||||||
media_extension:
|
media_extension:
|
||||||
|
@ -321,7 +327,7 @@ SPEC CHECKSUMS:
|
||||||
FirebaseInstallations: 0a115432c4e223c5ab20b0dbbe4cbefa793a0e8e
|
FirebaseInstallations: 0a115432c4e223c5ab20b0dbbe4cbefa793a0e8e
|
||||||
FirebaseMessaging: 732623518591384f61c287e3d8f65294beb7ffb3
|
FirebaseMessaging: 732623518591384f61c287e3d8f65294beb7ffb3
|
||||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||||
flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433
|
flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433
|
||||||
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
|
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
|
||||||
|
@ -336,6 +342,7 @@ SPEC CHECKSUMS:
|
||||||
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
|
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
|
||||||
image_editor: eab82a302a6623a866da5145b7c4c0ee8a4ffbb4
|
image_editor: eab82a302a6623a866da5145b7c4c0ee8a4ffbb4
|
||||||
in_app_purchase_storekit: d7fcf4646136ec258e237872755da8ea6c1b6096
|
in_app_purchase_storekit: d7fcf4646136ec258e237872755da8ea6c1b6096
|
||||||
|
keyboard_visibility: 96a24de806fe6823c3ad956c01ba2ec6d056616f
|
||||||
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
|
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
|
||||||
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
|
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
|
||||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
@ -213,7 +213,7 @@
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
DA6BE5E826B3BC8600656280 /* (null) in Resources */,
|
DA6BE5E826B3BC8600656280 /* BuildFile in Resources */,
|
||||||
277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */,
|
277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -287,6 +287,7 @@
|
||||||
"${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework",
|
"${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/image_editor/image_editor.framework",
|
"${BUILT_PRODUCTS_DIR}/image_editor/image_editor.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.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}/libwebp/libwebp.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/local_auth/local_auth.framework",
|
"${BUILT_PRODUCTS_DIR}/local_auth/local_auth.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/media_extension/media_extension.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}/fluttertoast.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor.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}/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}/libwebp.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",
|
||||||
|
@ -499,7 +501,10 @@
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
|
@ -656,7 +661,10 @@
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
|
@ -690,7 +698,10 @@
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
|
|
|
@ -44,9 +44,11 @@ const supportEmail = 'support@ente.io';
|
||||||
class FFDefault {
|
class FFDefault {
|
||||||
static const bool enableStripe = true;
|
static const bool enableStripe = true;
|
||||||
static const bool disableCFWorker = false;
|
static const bool disableCFWorker = false;
|
||||||
static const bool enableCollect = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const kDefaultProductionEndpoint = 'https://api.ente.io';
|
const kDefaultProductionEndpoint = 'https://api.ente.io';
|
||||||
|
|
||||||
const int intMaxValue = 9223372036854775807;
|
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 =>
|
EnteTheme get enteTheme =>
|
||||||
brightness == Brightness.light ? lightTheme : darkTheme;
|
brightness == Brightness.light ? lightTheme : darkTheme;
|
||||||
|
|
||||||
|
EnteTheme get inverseEnteTheme =>
|
||||||
|
brightness == Brightness.light ? darkTheme : lightTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
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() {
|
bool isInternalUserOrDebugBuild() {
|
||||||
final String? email = Configuration.instance.getEmail();
|
final String? email = Configuration.instance.getEmail();
|
||||||
return (email != null && email.endsWith("@ente.io")) || kDebugMode;
|
return (email != null && email.endsWith("@ente.io")) || kDebugMode;
|
||||||
|
@ -97,24 +85,20 @@ class FeatureFlags {
|
||||||
static FeatureFlags defaultFlags = FeatureFlags(
|
static FeatureFlags defaultFlags = FeatureFlags(
|
||||||
disableCFWorker: FFDefault.disableCFWorker,
|
disableCFWorker: FFDefault.disableCFWorker,
|
||||||
enableStripe: FFDefault.enableStripe,
|
enableStripe: FFDefault.enableStripe,
|
||||||
enableCollect: FFDefault.enableCollect,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final bool disableCFWorker;
|
final bool disableCFWorker;
|
||||||
final bool enableStripe;
|
final bool enableStripe;
|
||||||
final bool enableCollect;
|
|
||||||
|
|
||||||
FeatureFlags({
|
FeatureFlags({
|
||||||
required this.disableCFWorker,
|
required this.disableCFWorker,
|
||||||
required this.enableStripe,
|
required this.enableStripe,
|
||||||
required this.enableCollect,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
"disableCFWorker": disableCFWorker,
|
"disableCFWorker": disableCFWorker,
|
||||||
"enableStripe": enableStripe,
|
"enableStripe": enableStripe,
|
||||||
"enableCollect": enableCollect,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +111,6 @@ class FeatureFlags {
|
||||||
return FeatureFlags(
|
return FeatureFlags(
|
||||||
disableCFWorker: json["disableCFWorker"] ?? FFDefault.disableCFWorker,
|
disableCFWorker: json["disableCFWorker"] ?? FFDefault.disableCFWorker,
|
||||||
enableStripe: json["enableStripe"] ?? FFDefault.enableStripe,
|
enableStripe: json["enableStripe"] ?? FFDefault.enableStripe,
|
||||||
enableCollect: json["enableCollect"] ?? FFDefault.enableCollect,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ class EnteColorScheme {
|
||||||
final Color fillBase;
|
final Color fillBase;
|
||||||
final Color fillMuted;
|
final Color fillMuted;
|
||||||
final Color fillFaint;
|
final Color fillFaint;
|
||||||
|
final Color fillFaintPressed;
|
||||||
|
|
||||||
// Stroke Colors
|
// Stroke Colors
|
||||||
final Color strokeBase;
|
final Color strokeBase;
|
||||||
|
@ -63,6 +64,7 @@ class EnteColorScheme {
|
||||||
this.fillBase,
|
this.fillBase,
|
||||||
this.fillMuted,
|
this.fillMuted,
|
||||||
this.fillFaint,
|
this.fillFaint,
|
||||||
|
this.fillFaintPressed,
|
||||||
this.strokeBase,
|
this.strokeBase,
|
||||||
this.strokeMuted,
|
this.strokeMuted,
|
||||||
this.strokeFaint,
|
this.strokeFaint,
|
||||||
|
@ -84,28 +86,30 @@ class EnteColorScheme {
|
||||||
}
|
}
|
||||||
|
|
||||||
const EnteColorScheme lightScheme = EnteColorScheme(
|
const EnteColorScheme lightScheme = EnteColorScheme(
|
||||||
backgroundBaseLight,
|
backgroundBaseLight,
|
||||||
backgroundElevatedLight,
|
backgroundElevatedLight,
|
||||||
backgroundElevated2Light,
|
backgroundElevated2Light,
|
||||||
backdropBaseLight,
|
backdropBaseLight,
|
||||||
backdropMutedLight,
|
backdropMutedLight,
|
||||||
backdropFaintLight,
|
backdropFaintLight,
|
||||||
textBaseLight,
|
textBaseLight,
|
||||||
textMutedLight,
|
textMutedLight,
|
||||||
textFaintLight,
|
textFaintLight,
|
||||||
blurTextBaseLight,
|
blurTextBaseLight,
|
||||||
fillBaseLight,
|
fillBaseLight,
|
||||||
fillMutedLight,
|
fillMutedLight,
|
||||||
fillFaintLight,
|
fillFaintLight,
|
||||||
strokeBaseLight,
|
fillFaintPressedLight,
|
||||||
strokeMutedLight,
|
strokeBaseLight,
|
||||||
strokeFaintLight,
|
strokeMutedLight,
|
||||||
strokeFainterLight,
|
strokeFaintLight,
|
||||||
blurStrokeBaseLight,
|
strokeFainterLight,
|
||||||
blurStrokeFaintLight,
|
blurStrokeBaseLight,
|
||||||
blurStrokePressedLight,
|
blurStrokeFaintLight,
|
||||||
tabIconLight,
|
blurStrokePressedLight,
|
||||||
avatarLight);
|
tabIconLight,
|
||||||
|
avatarLight,
|
||||||
|
);
|
||||||
|
|
||||||
const EnteColorScheme darkScheme = EnteColorScheme(
|
const EnteColorScheme darkScheme = EnteColorScheme(
|
||||||
backgroundBaseDark,
|
backgroundBaseDark,
|
||||||
|
@ -121,6 +125,7 @@ const EnteColorScheme darkScheme = EnteColorScheme(
|
||||||
fillBaseDark,
|
fillBaseDark,
|
||||||
fillMutedDark,
|
fillMutedDark,
|
||||||
fillFaintDark,
|
fillFaintDark,
|
||||||
|
fillFaintPressedDark,
|
||||||
strokeBaseDark,
|
strokeBaseDark,
|
||||||
strokeMutedDark,
|
strokeMutedDark,
|
||||||
strokeFaintDark,
|
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 fillBaseLight = Color.fromRGBO(0, 0, 0, 1);
|
||||||
const Color fillMutedLight = Color.fromRGBO(0, 0, 0, 0.12);
|
const Color fillMutedLight = Color.fromRGBO(0, 0, 0, 0.12);
|
||||||
const Color fillFaintLight = Color.fromRGBO(0, 0, 0, 0.04);
|
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 fillBaseDark = Color.fromRGBO(255, 255, 255, 1);
|
||||||
const Color fillMutedDark = Color.fromRGBO(255, 255, 255, 0.16);
|
const Color fillMutedDark = Color.fromRGBO(255, 255, 255, 0.16);
|
||||||
const Color fillFaintDark = Color.fromRGBO(255, 255, 255, 0.12);
|
const Color fillFaintDark = Color.fromRGBO(255, 255, 255, 0.12);
|
||||||
|
const Color fillFaintPressedDark = Color.fromRGBO(255, 255, 255, 0.06);
|
||||||
|
|
||||||
// Stroke Colors
|
// Stroke Colors
|
||||||
const Color strokeBaseLight = Color.fromRGBO(0, 0, 0, 1);
|
const Color strokeBaseLight = Color.fromRGBO(0, 0, 0, 1);
|
||||||
|
|
|
@ -36,10 +36,20 @@ EnteTheme darkTheme = EnteTheme(
|
||||||
shadowButton: shadowButtonDark,
|
shadowButton: shadowButtonDark,
|
||||||
);
|
);
|
||||||
|
|
||||||
EnteColorScheme getEnteColorScheme(BuildContext context) {
|
EnteColorScheme getEnteColorScheme(
|
||||||
return Theme.of(context).colorScheme.enteTheme.colorScheme;
|
BuildContext context, {
|
||||||
|
bool inverse = false,
|
||||||
|
}) {
|
||||||
|
return inverse
|
||||||
|
? Theme.of(context).colorScheme.inverseEnteTheme.colorScheme
|
||||||
|
: Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnteTextTheme getEnteTextTheme(BuildContext context) {
|
EnteTextTheme getEnteTextTheme(
|
||||||
return Theme.of(context).colorScheme.enteTheme.textTheme;
|
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:flutter/material.dart';
|
||||||
import 'package:photos/theme/ente_theme.dart';
|
import 'package:photos/theme/ente_theme.dart';
|
||||||
|
|
||||||
class EnteLoadingWidget extends StatelessWidget {
|
class EnteLoadingWidget extends StatelessWidget {
|
||||||
final Color? color;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(5),
|
padding: EdgeInsets.all(is20pts ? 3 : 5),
|
||||||
child: SizedBox.fromSize(
|
child: SizedBox.fromSize(
|
||||||
size: const Size.square(14),
|
size: const Size.square(14),
|
||||||
child: CircularProgressIndicator(
|
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),
|
padding: const EdgeInsets.only(bottom: 4),
|
||||||
child: widget.selectionOptionsWidget,
|
child: widget.selectionOptionsWidget,
|
||||||
),
|
),
|
||||||
theme: getExpandableTheme(context),
|
theme: getExpandableTheme(),
|
||||||
controller: expandableController,
|
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);
|
Widget sectionOptionSpacing = const SizedBox(height: 6);
|
||||||
|
|
||||||
ExpandableThemeData getExpandableTheme(BuildContext context) {
|
ExpandableThemeData getExpandableTheme() {
|
||||||
return const ExpandableThemeData(
|
return const ExpandableThemeData(
|
||||||
hasIcon: false,
|
hasIcon: false,
|
||||||
useInkWell: false,
|
useInkWell: false,
|
||||||
|
|
|
@ -10,7 +10,6 @@ import 'package:flutter_sodium/flutter_sodium.dart';
|
||||||
import 'package:photos/ente_theme_data.dart';
|
import 'package:photos/ente_theme_data.dart';
|
||||||
import 'package:photos/models/collection.dart';
|
import 'package:photos/models/collection.dart';
|
||||||
import 'package:photos/services/collections_service.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/colors.dart';
|
||||||
import 'package:photos/theme/ente_theme.dart';
|
import 'package:photos/theme/ente_theme.dart';
|
||||||
import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
|
import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
|
||||||
|
@ -62,42 +61,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final enteColorScheme = getEnteColorScheme(context);
|
final enteColorScheme = getEnteColorScheme(context);
|
||||||
final PublicURL url = widget.collection?.publicURLs?.firstOrNull;
|
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(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).backgroundColor,
|
backgroundColor: Theme.of(context).backgroundColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -114,7 +77,33 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
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(
|
MenuItemWidget(
|
||||||
alignCaptionedTextToLeft: true,
|
alignCaptionedTextToLeft: true,
|
||||||
captionedTextWidget: CaptionedTextWidget(
|
captionedTextWidget: CaptionedTextWidget(
|
||||||
|
|
|
@ -23,7 +23,7 @@ class UserAvatarWidget extends StatelessWidget {
|
||||||
final enteTextTheme = getEnteTextTheme(context);
|
final enteTextTheme = getEnteTextTheme(context);
|
||||||
final colorScheme = getEnteColorScheme(context);
|
final colorScheme = getEnteColorScheme(context);
|
||||||
final displayChar = (user.name == null || user.name!.isEmpty)
|
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);
|
: user.name!.substring(0, 1);
|
||||||
final randomColor = colorScheme.avatarColors[
|
final randomColor = colorScheme.avatarColors[
|
||||||
(user.id ?? 0).remainder(colorScheme.avatarColors.length)];
|
(user.id ?? 0).remainder(colorScheme.avatarColors.length)];
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @dart=2.9
|
// @dart=2.9
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:photos/core/configuration.dart';
|
||||||
import 'package:photos/core/event_bus.dart';
|
import 'package:photos/core/event_bus.dart';
|
||||||
import 'package:photos/db/files_db.dart';
|
import 'package:photos/db/files_db.dart';
|
||||||
import 'package:photos/events/collection_updated_event.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/gallery_type.dart';
|
||||||
import 'package:photos/models/selected_files.dart';
|
import 'package:photos/models/selected_files.dart';
|
||||||
import 'package:photos/services/ignored_files_service.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/empty_state.dart';
|
||||||
import 'package:photos/ui/viewer/gallery/gallery.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_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 CollectionWithThumbnail c;
|
||||||
final String tagPrefix;
|
final String tagPrefix;
|
||||||
final GalleryType appBarType;
|
final GalleryType appBarType;
|
||||||
final _selectedFiles = SelectedFiles();
|
|
||||||
final bool hasVerifiedLock;
|
final bool hasVerifiedLock;
|
||||||
|
|
||||||
CollectionPage(
|
const CollectionPage(
|
||||||
this.c, {
|
this.c, {
|
||||||
this.tagPrefix = "collection",
|
this.tagPrefix = "collection",
|
||||||
this.appBarType = GalleryType.ownedCollection,
|
this.appBarType = GalleryType.ownedCollection,
|
||||||
|
@ -30,17 +36,41 @@ class CollectionPage extends StatelessWidget {
|
||||||
Key key,
|
Key key,
|
||||||
}) : super(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
|
@override
|
||||||
Widget build(Object context) {
|
Widget build(Object context) {
|
||||||
if (hasVerifiedLock == false && c.collection.isHidden()) {
|
if (widget.hasVerifiedLock == false && widget.c.collection.isHidden()) {
|
||||||
return const EmptyState();
|
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(
|
final gallery = Gallery(
|
||||||
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
|
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
|
||||||
final FileLoadResult result =
|
final FileLoadResult result =
|
||||||
await FilesDB.instance.getFilesInCollection(
|
await FilesDB.instance.getFilesInCollection(
|
||||||
c.collection.id,
|
widget.c.collection.id,
|
||||||
creationStartTime,
|
creationStartTime,
|
||||||
creationEndTime,
|
creationEndTime,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
|
@ -57,38 +87,89 @@ class CollectionPage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
reloadEvent: Bus.instance
|
reloadEvent: Bus.instance
|
||||||
.on<CollectionUpdatedEvent>()
|
.on<CollectionUpdatedEvent>()
|
||||||
.where((event) => event.collectionID == c.collection.id),
|
.where((event) => event.collectionID == widget.c.collection.id),
|
||||||
removalEventTypes: const {
|
removalEventTypes: const {
|
||||||
EventType.deletedFromRemote,
|
EventType.deletedFromRemote,
|
||||||
EventType.deletedFromEverywhere,
|
EventType.deletedFromEverywhere,
|
||||||
EventType.hide,
|
EventType.hide,
|
||||||
},
|
},
|
||||||
tagPrefix: tagPrefix,
|
tagPrefix: widget.tagPrefix,
|
||||||
selectedFiles: _selectedFiles,
|
selectedFiles: _selectedFiles,
|
||||||
initialFiles: initialFiles,
|
initialFiles: initialFiles,
|
||||||
albumName: c.collection.name,
|
albumName: widget.c.collection.name,
|
||||||
);
|
);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(50.0),
|
preferredSize: const Size.fromHeight(50.0),
|
||||||
child: GalleryAppBarWidget(
|
child: GalleryAppBarWidget(
|
||||||
appBarType,
|
widget.appBarType,
|
||||||
c.collection.name,
|
widget.c.collection.name,
|
||||||
_selectedFiles,
|
_selectedFiles,
|
||||||
collection: c.collection,
|
collection: widget.c.collection,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
children: [
|
children: [
|
||||||
gallery,
|
gallery,
|
||||||
GalleryOverlayWidget(
|
ValueListenableBuilder(
|
||||||
appBarType,
|
valueListenable: _bottomPosition,
|
||||||
_selectedFiles,
|
builder: (context, value, child) {
|
||||||
collection: c.collection,
|
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 'dart:math';
|
||||||
|
|
||||||
import 'package:device_info/device_info.dart';
|
import 'package:device_info/device_info.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:photo_manager/photo_manager.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/files_updated_event.dart';
|
||||||
import 'package:photos/events/local_photos_updated_event.dart';
|
import 'package:photos/events/local_photos_updated_event.dart';
|
||||||
import 'package:photos/models/file.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/models/trash_item_request.dart';
|
||||||
import 'package:photos/services/remote_sync_service.dart';
|
import 'package:photos/services/remote_sync_service.dart';
|
||||||
import 'package:photos/services/sync_service.dart';
|
import 'package:photos/services/sync_service.dart';
|
||||||
|
@ -485,3 +487,100 @@ Future<bool> shouldProceedWithDeletion(BuildContext context) async {
|
||||||
);
|
);
|
||||||
return choice == DialogUserChoice.secondChoice;
|
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
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.0"
|
version: "2.8.2"
|
||||||
background_fetch:
|
background_fetch:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -98,7 +98,14 @@ packages:
|
||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
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:
|
chewie:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -112,7 +119,7 @@ packages:
|
||||||
name: clock
|
name: clock
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -301,7 +308,7 @@ packages:
|
||||||
name: fake_async
|
name: fake_async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.0"
|
||||||
fast_base58:
|
fast_base58:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -700,6 +707,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.0"
|
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:
|
like_button:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -755,14 +769,14 @@ packages:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.12"
|
version: "0.12.11"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5"
|
version: "0.1.4"
|
||||||
media_extension:
|
media_extension:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -778,7 +792,7 @@ packages:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.7.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -906,7 +920,7 @@ packages:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.8.1"
|
||||||
path_drawing:
|
path_drawing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1247,7 +1261,7 @@ packages:
|
||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.8.2"
|
||||||
sprintf:
|
sprintf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1303,7 +1317,7 @@ packages:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.0"
|
||||||
syncfusion_flutter_core:
|
syncfusion_flutter_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1331,28 +1345,28 @@ packages:
|
||||||
name: term_glyph
|
name: term_glyph
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.0"
|
||||||
test:
|
test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.21.4"
|
version: "1.21.1"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.12"
|
version: "0.4.9"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.16"
|
version: "0.4.13"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -12,7 +12,7 @@ description: ente photos application
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
|
|
||||||
version: 0.6.72+392
|
version: 0.6.74+394
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.17.0 <3.0.0'
|
sdk: '>=2.17.0 <3.0.0'
|
||||||
|
|
Loading…
Add table
Reference in a new issue