Resolved merge conflicts

This commit is contained in:
ashilkn 2023-02-23 18:13:15 +05:30
commit afa0309f39
84 changed files with 1121 additions and 594 deletions

View file

@ -17,15 +17,15 @@
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<!-- <intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <data android:mimeType="image/*" />
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
</intent-filter> --> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PICK" /> <action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -42,66 +42,67 @@
<!-- file provider to share files having a file:// URI --> <!-- file provider to share files having a file:// URI -->
<!--Filter to support sharing images into our app--> <!--Filter
to support sharing images into our app-->
<intent-filter android:label="@string/backup"> <intent-filter android:label="@string/backup">
<action android:name="android.intent.action.SEND"/> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*"/> <data android:mimeType="image/*" />
</intent-filter> </intent-filter>
<intent-filter android:label="@string/backup"> <intent-filter android:label="@string/backup">
<action android:name="android.intent.action.SEND_MULTIPLE"/> <action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*"/> <data android:mimeType="image/*" />
</intent-filter> </intent-filter>
<intent-filter android:label="@string/backup"> <intent-filter android:label="@string/backup">
<action android:name="android.intent.action.SEND"/> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*"/> <data android:mimeType="video/*" />
</intent-filter> </intent-filter>
<intent-filter android:label="@string/backup"> <intent-filter android:label="@string/backup">
<action android:name="android.intent.action.SEND_MULTIPLE"/> <action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*"/> <data android:mimeType="video/*" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2"/> <meta-data android:name="flutterEmbedding" android:value="2" />
<meta-data android:name="asset_statements" <meta-data android:name="asset_statements"
android:resource="@string/asset_statements"/> android:resource="@string/asset_statements" />
<meta-data android:name="io.sentry.dsn" <meta-data android:name="io.sentry.dsn"
android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4"/> android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" />
<meta-data android:name="firebase_analytics_collection_deactivated" <meta-data android:name="firebase_analytics_collection_deactivated"
android:value="true"/> android:value="true" />
</application> </application>
<!-- Android 11: https://developer.android.com/preview/privacy/package-visibility --> <!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->
<!-- https://developer.android.com/training/package-visibility/use-cases --> <!-- https://developer.android.com/training/package-visibility/use-cases -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.SENDTO"/> <action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto"/> <data android:scheme="mailto" />
</intent> </intent>
</queries> </queries>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_MEDIA"/> <uses-permission android:name="android.permission.MANAGE_MEDIA" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission <uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"/> <!-- If you want to read images--> android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- If you want to read images-->
<uses-permission <uses-permission
android:name="android.permission.READ_MEDIA_VIDEO"/> <!-- If you want to read videos--> android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- If you want to read videos-->
<uses-permission <uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"/> android:maxSdkVersion="32" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" android:maxSdkVersion="29"
tools:ignore="ScopedStorage"/> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.android.vending.BILLING"/> <uses-permission android:name="com.android.vending.BILLING" />
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View file

@ -4,7 +4,9 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#000000</item> <item name="android:windowSplashScreenBackground">#000000</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View file

@ -7,6 +7,7 @@
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View file

@ -4,7 +4,9 @@
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#ffffff</item> <item name="android:windowSplashScreenBackground">#ffffff</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -3,9 +3,9 @@ PODS:
- Flutter - Flutter
- camera_avfoundation (0.0.1): - camera_avfoundation (0.0.1):
- Flutter - Flutter
- connectivity (0.0.1): - connectivity_plus (0.0.1):
- Flutter - Flutter
- Reachability - ReachabilitySwift
- device_info (0.0.1): - device_info (0.0.1):
- Flutter - Flutter
- Firebase/CoreOnly (10.3.0): - Firebase/CoreOnly (10.3.0):
@ -136,7 +136,7 @@ PODS:
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- PromisesObjC (2.1.1) - PromisesObjC (2.1.1)
- Reachability (3.2) - ReachabilitySwift (5.0.0)
- receive_sharing_intent (0.0.1): - receive_sharing_intent (0.0.1):
- Flutter - Flutter
- SDWebImage (5.15.2): - SDWebImage (5.15.2):
@ -178,7 +178,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- background_fetch (from `.symlinks/plugins/background_fetch/ios`) - background_fetch (from `.symlinks/plugins/background_fetch/ios`)
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- connectivity (from `.symlinks/plugins/connectivity/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info (from `.symlinks/plugins/device_info/ios`) - device_info (from `.symlinks/plugins/device_info/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
@ -231,7 +231,7 @@ SPEC REPOS:
- nanopb - nanopb
- OrderedSet - OrderedSet
- PromisesObjC - PromisesObjC
- Reachability - ReachabilitySwift
- SDWebImage - SDWebImage
- SDWebImageWebPCoder - SDWebImageWebPCoder
- Sentry - Sentry
@ -242,8 +242,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/background_fetch/ios" :path: ".symlinks/plugins/background_fetch/ios"
camera_avfoundation: camera_avfoundation:
:path: ".symlinks/plugins/camera_avfoundation/ios" :path: ".symlinks/plugins/camera_avfoundation/ios"
connectivity: connectivity_plus:
:path: ".symlinks/plugins/connectivity/ios" :path: ".symlinks/plugins/connectivity_plus/ios"
device_info: device_info:
:path: ".symlinks/plugins/device_info/ios" :path: ".symlinks/plugins/device_info/ios"
firebase_core: firebase_core:
@ -320,7 +320,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
background_fetch: bd64e544b303ee4cd4cf2fe8cb2187b72aecf9ca background_fetch: bd64e544b303ee4cd4cf2fe8cb2187b72aecf9ca
camera_avfoundation: 07c77549ea54ad95d8581be86617c094a46280d9 camera_avfoundation: 07c77549ea54ad95d8581be86617c094a46280d9
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 device_info: d7d233b645a32c40dfdc212de5cf646ca482f175
Firebase: f92fc551ead69c94168d36c2b26188263860acd9 Firebase: f92fc551ead69c94168d36c2b26188263860acd9
firebase_core: f95c8bbe65213d406d592573ad42a12d64849cb8 firebase_core: f95c8bbe65213d406d592573ad42a12d64849cb8
@ -358,7 +358,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1 receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
SDWebImage: 8ab87d4b3e5cc4927bd47f78db6ceb0b94442577 SDWebImage: 8ab87d4b3e5cc4927bd47f78db6ceb0b94442577
SDWebImageWebPCoder: 4851414d9f8894e328e8b97c93ea4f4f4e4418ae SDWebImageWebPCoder: 4851414d9f8894e328e8b97c93ea4f4f4e4418ae

View file

@ -268,14 +268,14 @@
"${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework", "${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework",
"${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.framework", "${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.framework",
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
"${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework", "${BUILT_PRODUCTS_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework",
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework", "${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
"${BUILT_PRODUCTS_DIR}/Toast/Toast.framework", "${BUILT_PRODUCTS_DIR}/Toast/Toast.framework",
"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework", "${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
"${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.framework", "${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.framework",
"${BUILT_PRODUCTS_DIR}/connectivity/connectivity.framework", "${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/device_info/device_info.framework", "${BUILT_PRODUCTS_DIR}/device_info/device_info.framework",
"${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework", "${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework",
"${BUILT_PRODUCTS_DIR}/flutter_email_sender/flutter_email_sender.framework", "${BUILT_PRODUCTS_DIR}/flutter_email_sender/flutter_email_sender.framework",
@ -331,7 +331,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Toast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Toast.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera_avfoundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera_avfoundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.framework",

View file

@ -2,8 +2,7 @@
"images" : [ "images" : [
{ {
"filename" : "background.png", "filename" : "background.png",
"idiom" : "universal", "idiom" : "universal"
"scale" : "1x"
}, },
{ {
"appearances" : [ "appearances" : [
@ -13,36 +12,7 @@
} }
], ],
"filename" : "darkbackground.png", "filename" : "darkbackground.png",
"idiom" : "universal", "idiom" : "universal"
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -38,7 +38,7 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="LaunchImage" width="1024" height="1024"/> <image name="LaunchImage" width="1152" height="1152"/>
<image name="LaunchBackground" width="1" height="1"/> <image name="LaunchBackground" width="1" height="1"/>
</resources> </resources>
</document> </document>

View file

@ -67,8 +67,7 @@
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>Please allow ente to lock itself with FaceID or TouchID</string> <string>Please allow ente to lock itself with FaceID or TouchID</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Please allow access to your camera so that you can take photos <string>Please allow access to your camera so that you can take photos within ente</string>
within ente</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>Please allow access to your photos so that ente can encrypt and back them up.</string> <string>Please allow access to your photos so that ente can encrypt and back them up.</string>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
@ -102,5 +101,5 @@
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View file

@ -4,16 +4,16 @@ import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:background_fetch/background_fetch.dart'; import 'package:background_fetch/background_fetch.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:media_extension/media_extension.dart';
import 'package:media_extension/media_extension_action_types.dart'; import 'package:media_extension/media_extension_action_types.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import 'package:photos/services/app_lifecycle_service.dart'; import 'package:photos/services/app_lifecycle_service.dart';
import 'package:photos/services/sync_service.dart'; import 'package:photos/services/sync_service.dart';
import 'package:photos/ui/home_widget.dart'; import 'package:photos/ui/home_widget.dart';
import "package:photos/ui/viewer/actions/file_viewer.dart";
import "package:photos/utils/intent_util.dart";
class EnteApp extends StatefulWidget { class EnteApp extends StatefulWidget {
final Future<void> Function(String) runBackgroundTask; final Future<void> Function(String) runBackgroundTask;
@ -31,45 +31,29 @@ class EnteApp extends StatefulWidget {
class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver { class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
final _logger = Logger("EnteAppState"); final _logger = Logger("EnteAppState");
final _mediaExtensionPlugin = MediaExtension();
@override @override
void initState() { void initState() {
_logger.info('init App'); _logger.info('init App');
super.initState(); super.initState();
setupIntentAction();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
} }
Future<bool> initIntentAction() async { void setupIntentAction() async {
if (!Platform.isAndroid) { final mediaExtentionAction = Platform.isAndroid
AppLifecycleService.instance.setIntentAction(IntentAction.main); ? await initIntentAction()
return true; : MediaExtentionAction(action: IntentAction.main);
} AppLifecycleService.instance.setMediaExtensionAction(mediaExtentionAction);
IntentAction intentAction = IntentAction.main; if (mediaExtentionAction.action == IntentAction.main) {
try {
final actionResult = await _mediaExtensionPlugin.getIntentAction();
intentAction = actionResult.action!;
} on PlatformException {
intentAction = IntentAction.main;
} catch (error) {
_logger.info(error);
intentAction = IntentAction.main;
}
if (intentAction == IntentAction.main) {
_configureBackgroundFetch(); _configureBackgroundFetch();
} }
AppLifecycleService.instance.setIntentAction(intentAction);
return true;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (Platform.isAndroid || kDebugMode) { if (Platform.isAndroid || kDebugMode) {
return FutureBuilder( return AdaptiveTheme(
future: initIntentAction(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return snapshot.data != null && snapshot.data == true
? AdaptiveTheme(
light: lightThemeData, light: lightThemeData,
dark: darkThemeData, dark: darkThemeData,
initial: AdaptiveThemeMode.system, initial: AdaptiveThemeMode.system,
@ -78,16 +62,15 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
theme: lightTheme, theme: lightTheme,
darkTheme: dartTheme, darkTheme: dartTheme,
home: const HomeWidget(), home: AppLifecycleService.instance.mediaExtensionAction.action ==
IntentAction.view
? const FileViewer()
: const HomeWidget(),
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
builder: EasyLoading.init(), builder: EasyLoading.init(),
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: localizationsDelegates: AppLocalizations.localizationsDelegates,
AppLocalizations.localizationsDelegates,
), ),
)
: Container();
},
); );
} else { } else {
return MaterialApp( return MaterialApp(

View file

@ -1091,13 +1091,17 @@ class FilesDB {
return count ?? 0; return count ?? 0;
} }
Future<int> fileCountWithVisibility(int visibility, int ownerID) async { Future<int> archivedFilesCount(
int visibility,
int ownerID,
Set<int> hiddenCollections,
) async {
final db = await instance.database; final db = await instance.database;
final count = Sqflite.firstIntValue( final count = Sqflite.firstIntValue(
await db.rawQuery( await db.rawQuery(
'SELECT COUNT(distinct($columnUploadedFileID)) FROM $filesTable where ' 'SELECT COUNT(distinct($columnUploadedFileID)) FROM $filesTable where '
'$columnMMdVisibility' '$columnMMdVisibility'
' = $visibility AND $columnOwnerID = $ownerID', ' = $visibility AND $columnOwnerID = $ownerID AND $columnCollectionID NOT IN (${hiddenCollections.join(', ')})',
), ),
); );
return count ?? 0; return count ?? 0;

View file

@ -3,7 +3,9 @@ import "package:flutter/services.dart";
class UpperCaseTextFormatter extends TextInputFormatter { class UpperCaseTextFormatter extends TextInputFormatter {
@override @override
TextEditingValue formatEditUpdate( TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) { TextEditingValue oldValue,
TextEditingValue newValue,
) {
return TextEditingValue( return TextEditingValue(
text: newValue.text.toUpperCase(), text: newValue.text.toUpperCase(),
selection: newValue.selection, selection: newValue.selection,

View file

@ -5,6 +5,7 @@ import 'package:background_fetch/background_fetch.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:flutter/rendering.dart";
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:photos/app.dart'; import 'package:photos/app.dart';
@ -54,6 +55,7 @@ const kFGTaskDeathTimeoutInMicroseconds = 5000000;
const kBackgroundLockLatency = Duration(seconds: 3); const kBackgroundLockLatency = Duration(seconds: 3);
void main() async { void main() async {
debugRepaintRainbowEnabled = false;
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await _runInForeground(); await _runInForeground();
BackgroundFetch.registerHeadlessTask(_headlessTaskHandler); BackgroundFetch.registerHeadlessTask(_headlessTaskHandler);

View file

@ -5,15 +5,17 @@ class AppLifecycleService {
final _logger = Logger("AppLifecycleService"); final _logger = Logger("AppLifecycleService");
bool isForeground = false; bool isForeground = false;
IntentAction intentAction = IntentAction.main; MediaExtentionAction mediaExtensionAction =
MediaExtentionAction(action: IntentAction.main);
static final AppLifecycleService instance = static final AppLifecycleService instance =
AppLifecycleService._privateConstructor(); AppLifecycleService._privateConstructor();
AppLifecycleService._privateConstructor(); AppLifecycleService._privateConstructor();
void setIntentAction(IntentAction intentAction) { void setMediaExtensionAction(MediaExtentionAction mediaExtensionAction) {
this.intentAction = intentAction; _logger.info("App invoked via ${mediaExtensionAction.action}");
this.mediaExtensionAction = mediaExtensionAction;
} }
void onAppInForeground(String reason) { void onAppInForeground(String reason) {

View file

@ -185,7 +185,9 @@ class CollectionsService {
Future<List<CollectionWithThumbnail>> getArchivedCollectionWithThumb() async { Future<List<CollectionWithThumbnail>> getArchivedCollectionWithThumb() async {
final allCollections = await getCollectionsWithThumbnails(); final allCollections = await getCollectionsWithThumbnails();
return allCollections return allCollections
.where((element) => element.collection.isArchived()) .where(
(c) => c.collection.isArchived() && !c.collection.isHidden(),
)
.toList(); .toList();
} }

View file

@ -1,8 +1,10 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:photos/core/constants.dart'; import 'package:photos/core/constants.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/db/memories_db.dart'; import 'package:photos/db/memories_db.dart';
import "package:photos/events/files_updated_event.dart";
import 'package:photos/models/filters/important_items_filter.dart'; import 'package:photos/models/filters/important_items_filter.dart';
import 'package:photos/models/memory.dart'; import 'package:photos/models/memory.dart';
import 'package:photos/services/collections_service.dart'; import 'package:photos/services/collections_service.dart';
@ -34,6 +36,17 @@ class MemoriesService extends ChangeNotifier {
DateTime.now().microsecondsSinceEpoch - (7 * microSecondsInDay), DateTime.now().microsecondsSinceEpoch - (7 * microSecondsInDay),
); );
}); });
Bus.instance.on<FilesUpdatedEvent>().where((event) {
return event.type == EventType.deletedFromEverywhere;
}).listen((event) {
final generatedIDs = event.updatedFiles
.where((element) => element.generatedID != null)
.map((e) => e.generatedID!)
.toSet();
_cachedMemories?.removeWhere((element) {
return generatedIDs.contains(element.file.generatedID);
});
});
} }
void clearCache() { void clearCache() {

View file

@ -6,7 +6,8 @@ class StorageBonusService {
late StorageBonusGateway gateway; late StorageBonusGateway gateway;
late SharedPreferences prefs; late SharedPreferences prefs;
final String _showStorageBonus = "showStorageBonus.showBanner"; final int minTapCountBeforeHidingBanner = 5;
final String _showStorageBonusTapCount = "showStorageBonus.tap_count";
void init(SharedPreferences preferences) { void init(SharedPreferences preferences) {
prefs = preferences; prefs = preferences;
@ -18,12 +19,15 @@ class StorageBonusService {
static StorageBonusService instance = static StorageBonusService instance =
StorageBonusService._privateConstructor(); StorageBonusService._privateConstructor();
// returns true if _showStorageBonusTapCount value is less than minTapCountBeforeHidingBanner
bool shouldShowStorageBonus() { bool shouldShowStorageBonus() {
return prefs.getBool(_showStorageBonus) ?? true; final tapCount = prefs.getInt(_showStorageBonusTapCount) ?? 0;
return tapCount <= minTapCountBeforeHidingBanner;
} }
void markStorageBonusAsDone() { void markStorageBonusAsDone() {
prefs.setBool(_showStorageBonus, false).ignore(); final tapCount = prefs.getInt(_showStorageBonusTapCount) ?? 0;
prefs.setInt(_showStorageBonusTapCount, tapCount + 1).ignore();
} }
// getter for gateway // getter for gateway

View file

@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:connectivity/connectivity.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.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';

View file

@ -48,6 +48,10 @@ class EnteColorScheme {
final Color warning800; final Color warning800;
final Color caution500; final Color caution500;
//golden colors
final Color golden700;
final Color golden500;
//other colors //other colors
final Color tabIcon; final Color tabIcon;
final List<Color> avatarColors; final List<Color> avatarColors;
@ -86,6 +90,8 @@ class EnteColorScheme {
this.warning500 = _warning500, this.warning500 = _warning500,
this.warning400 = _warning400, this.warning400 = _warning400,
this.caution500 = _caution500, this.caution500 = _caution500,
this.golden700 = _golden700,
this.golden500 = _golden500,
}); });
} }
@ -224,6 +230,9 @@ const Color _warning800 = Color(0xFFF53434);
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1); const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
const Color _golden700 = Color(0xFFFDB816);
const Color _golden500 = Color(0xFFFFC336);
const List<Color> avatarLight = [ const List<Color> avatarLight = [
Color.fromRGBO(118, 84, 154, 1), Color.fromRGBO(118, 84, 154, 1),
Color.fromRGBO(223, 120, 97, 1), Color.fromRGBO(223, 120, 97, 1),

View file

@ -86,6 +86,26 @@ class EnteTextTheme {
final TextStyle brandSmall; final TextStyle brandSmall;
final TextStyle brandMedium; final TextStyle brandMedium;
// textMuted variants
final TextStyle h1Muted;
final TextStyle h2Muted;
final TextStyle h3Muted;
final TextStyle largeMuted;
final TextStyle bodyMuted;
final TextStyle smallMuted;
final TextStyle miniMuted;
final TextStyle tinyMuted;
// textFaint variants
final TextStyle h1Faint;
final TextStyle h2Faint;
final TextStyle h3Faint;
final TextStyle largeFaint;
final TextStyle bodyFaint;
final TextStyle smallFaint;
final TextStyle miniFaint;
final TextStyle tinyFaint;
const EnteTextTheme({ const EnteTextTheme({
required this.h1, required this.h1,
required this.h1Bold, required this.h1Bold,
@ -105,13 +125,42 @@ class EnteTextTheme {
required this.tinyBold, required this.tinyBold,
required this.brandSmall, required this.brandSmall,
required this.brandMedium, required this.brandMedium,
required this.h1Muted,
required this.h2Muted,
required this.h3Muted,
required this.largeMuted,
required this.bodyMuted,
required this.smallMuted,
required this.miniMuted,
required this.tinyMuted,
required this.h1Faint,
required this.h2Faint,
required this.h3Faint,
required this.largeFaint,
required this.bodyFaint,
required this.smallFaint,
required this.miniFaint,
required this.tinyFaint,
}); });
} }
EnteTextTheme lightTextTheme = _buildEnteTextStyle(textBaseLight); EnteTextTheme lightTextTheme = _buildEnteTextStyle(
EnteTextTheme darkTextTheme = _buildEnteTextStyle(textBaseDark); textBaseLight,
textMutedLight,
textFaintLight,
);
EnteTextTheme _buildEnteTextStyle(Color color) { EnteTextTheme darkTextTheme = _buildEnteTextStyle(
textBaseDark,
textMutedDark,
textFaintDark,
);
EnteTextTheme _buildEnteTextStyle(
Color color,
Color textMuted,
Color textFaint,
) {
return EnteTextTheme( return EnteTextTheme(
h1: h1.copyWith(color: color), h1: h1.copyWith(color: color),
h1Bold: h1.copyWith(color: color, fontWeight: _boldWeight), h1Bold: h1.copyWith(color: color, fontWeight: _boldWeight),
@ -131,5 +180,21 @@ EnteTextTheme _buildEnteTextStyle(Color color) {
tinyBold: tiny.copyWith(color: color, fontWeight: _boldWeight), tinyBold: tiny.copyWith(color: color, fontWeight: _boldWeight),
brandSmall: brandStyleSmall.copyWith(color: color), brandSmall: brandStyleSmall.copyWith(color: color),
brandMedium: brandStyleMedium.copyWith(color: color), brandMedium: brandStyleMedium.copyWith(color: color),
h1Muted: h1.copyWith(color: textMuted),
h2Muted: h2.copyWith(color: textMuted),
h3Muted: h3.copyWith(color: textMuted),
largeMuted: large.copyWith(color: textMuted),
bodyMuted: body.copyWith(color: textMuted),
smallMuted: small.copyWith(color: textMuted),
miniMuted: mini.copyWith(color: textMuted),
tinyMuted: tiny.copyWith(color: textMuted),
h1Faint: h1.copyWith(color: textFaint),
h2Faint: h2.copyWith(color: textFaint),
h3Faint: h3.copyWith(color: textFaint),
largeFaint: large.copyWith(color: textFaint),
bodyFaint: body.copyWith(color: textFaint),
smallFaint: small.copyWith(color: textFaint),
miniFaint: mini.copyWith(color: textFaint),
tinyFaint: tiny.copyWith(color: textFaint),
); );
} }

View file

@ -0,0 +1,143 @@
import "package:flutter/cupertino.dart";
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
import "package:photos/models/file.dart";
import "package:photos/models/file_type.dart";
import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/action_sheet_widget.dart";
import "package:photos/ui/components/button_widget.dart";
import "package:photos/ui/components/models/button_type.dart";
import "package:photos/ui/viewer/file/file_info_widget.dart";
import "package:photos/utils/delete_file_util.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/toast_util.dart";
Future<void> showSingleFileDeleteSheet(
BuildContext context,
File file, {
Function(File)? onFileRemoved,
}) async {
final List<ButtonWidget> buttons = [];
final String fileType = file.fileType == FileType.video ? "video" : "photo";
final bool isBothLocalAndRemote =
file.uploadedFileID != null && file.localID != null;
final bool isLocalOnly = file.uploadedFileID == null && file.localID != null;
final bool isRemoteOnly = file.uploadedFileID != null && file.localID == null;
const String bodyHighlight = "It will be deleted from all albums.";
String body = "";
if (isBothLocalAndRemote) {
body = "This $fileType is in both ente and your device.";
} else if (isRemoteOnly) {
body = "This $fileType will be deleted from ente.";
} else if (isLocalOnly) {
body = "This $fileType will be deleted from your device.";
} else {
throw AssertionError("Unexpected state");
}
// Add option to delete from ente
if (isBothLocalAndRemote || isRemoteOnly) {
buttons.add(
ButtonWidget(
labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromRemoteOnly(context, [file]);
showShortToast(context, "Moved to trash");
if (isRemoteOnly) {
Navigator.of(context, rootNavigator: true).pop();
if (onFileRemoved != null) {
onFileRemoved(file);
}
}
},
),
);
}
// Add option to delete from local
if (isBothLocalAndRemote || isLocalOnly) {
buttons.add(
ButtonWidget(
labelText: isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.second,
shouldSurfaceExecutionStates: false,
isInAlert: true,
onTap: () async {
await deleteFilesOnDeviceOnly(context, [file]);
if (isLocalOnly) {
Navigator.of(context, rootNavigator: true).pop();
if (onFileRemoved != null) {
onFileRemoved(file);
}
}
},
),
);
}
if (isBothLocalAndRemote) {
buttons.add(
ButtonWidget(
labelText: "Delete from both",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.third,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
if (onFileRemoved != null) {
onFileRemoved(file);
}
},
),
);
}
buttons.add(
const ButtonWidget(
labelText: "Cancel",
buttonType: ButtonType.secondary,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.fourth,
isInAlert: true,
),
);
final actionResult = await showActionSheet(
context: context,
buttons: buttons,
actionSheetType: ActionSheetType.defaultActionSheet,
body: body,
bodyHighlight: bodyHighlight,
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
}
}
Future<void> showInfoSheet(BuildContext context, File file) async {
final colorScheme = getEnteColorScheme(context);
return showBarModalBottomSheet(
topControl: const SizedBox.shrink(),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
backgroundColor: colorScheme.backgroundElevated,
barrierColor: backdropFaintDark,
context: context,
builder: (BuildContext context) {
return Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: FileInfoWidget(file),
);
},
);
}

View file

@ -597,8 +597,11 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
} }
Future<bool> _restoreFilesToCollection(int toCollectionID) async { Future<bool> _restoreFilesToCollection(int toCollectionID) async {
final dialog = createProgressDialog(context, "Restoring files...", final dialog = createProgressDialog(
isDismissible: true); context,
"Restoring files...",
isDismissible: true,
);
await dialog.show(); await dialog.show();
try { try {
await CollectionsService.instance await CollectionsService.instance

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/db/files_db.dart'; import 'package:photos/db/files_db.dart';
import 'package:photos/models/magic_metadata.dart'; import 'package:photos/models/magic_metadata.dart';
import "package:photos/services/collections_service.dart";
import 'package:photos/ui/viewer/gallery/archive_page.dart'; import 'package:photos/ui/viewer/gallery/archive_page.dart';
import 'package:photos/utils/navigation_util.dart'; import 'package:photos/utils/navigation_util.dart';
@ -15,6 +16,8 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Set<int> hiddenCollectionId =
CollectionsService.instance.getHiddenCollections();
return OutlinedButton( return OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).backgroundColor, backgroundColor: Theme.of(context).backgroundColor,
@ -43,9 +46,10 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
), ),
const Padding(padding: EdgeInsets.all(6)), const Padding(padding: EdgeInsets.all(6)),
FutureBuilder<int>( FutureBuilder<int>(
future: FilesDB.instance.fileCountWithVisibility( future: FilesDB.instance.archivedFilesCount(
visibilityArchive, visibilityArchive,
Configuration.instance.getUserID()!, Configuration.instance.getUserID()!,
hiddenCollectionId,
), ),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data! > 0) { if (snapshot.hasData && snapshot.data! > 0) {

View file

@ -0,0 +1,100 @@
import "dart:async";
import "package:flutter/cupertino.dart";
import "package:logging/logging.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/events/collection_updated_event.dart";
import "package:photos/models/collection_items.dart";
import "package:photos/ui/collections/collection_item_widget.dart";
import "package:photos/ui/common/loading_widget.dart";
import "package:photos/ui/components/divider_widget.dart";
class AlbumHorizontalListWidget extends StatefulWidget {
final Future<List<CollectionWithThumbnail>> Function() collectionsFuture;
const AlbumHorizontalListWidget(
this.collectionsFuture, {
Key? key,
}) : super(key: key);
@override
State<AlbumHorizontalListWidget> createState() =>
_AlbumHorizontalListWidgetState();
}
class _AlbumHorizontalListWidgetState extends State<AlbumHorizontalListWidget> {
late StreamSubscription<CollectionUpdatedEvent>
_collectionUpdatesSubscription;
late Logger _logger;
@override
void initState() {
_collectionUpdatesSubscription =
Bus.instance.on<CollectionUpdatedEvent>().listen((event) {
setState(() {});
});
_logger = Logger((_AlbumHorizontalListWidgetState).toString());
super.initState();
}
@override
void dispose() {
_collectionUpdatesSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint('$runtimeType widget build');
return FutureBuilder<List<CollectionWithThumbnail>>(
future: widget.collectionsFuture(),
builder: (context, snapshot) {
if (snapshot.hasError) {
_logger.severe("failed to fetch albums", snapshot.error);
return const Text("Something went wrong");
} else if (snapshot.hasData) {
if (snapshot.data!.isEmpty) {
return const SizedBox.shrink();
}
final collectionsWithThumbnail =
snapshot.data as List<CollectionWithThumbnail>;
return Align(
alignment: Alignment.centerLeft,
child: SizedBox(
height: 190,
child: Column(
children: [
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: collectionsWithThumbnail.length,
padding: const EdgeInsets.fromLTRB(6, 6, 6, 6),
itemBuilder: (context, index) {
final item = collectionsWithThumbnail[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {},
child: Padding(
padding: const EdgeInsets.all(2.0),
child: CollectionItem(
item,
120,
shouldRender: true,
),
),
);
},
),
),
const DividerWidget(dividerType: DividerType.solid),
],
),
),
);
} else {
return const EnteLoadingWidget();
}
},
);
}
}

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import 'package:photos/theme/colors.dart'; import 'package:photos/theme/colors.dart';
import "package:photos/theme/ente_theme.dart";
import 'package:photos/theme/text_style.dart'; import 'package:photos/theme/text_style.dart';
import 'package:photos/ui/components/icon_button_widget.dart'; import 'package:photos/ui/components/icon_button_widget.dart';
@ -8,6 +9,7 @@ import 'package:photos/ui/components/icon_button_widget.dart';
enum NotificationType { enum NotificationType {
warning, warning,
banner, banner,
goldenBanner,
} }
class NotificationWidget extends StatelessWidget { class NotificationWidget extends StatelessWidget {
@ -30,7 +32,9 @@ class NotificationWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color backgroundColor = Colors.white; final colorScheme = getEnteColorScheme(context);
LinearGradient? backgroundGradient;
Color? backgroundColor;
switch (type) { switch (type) {
case NotificationType.warning: case NotificationType.warning:
backgroundColor = warning500; backgroundColor = warning500;
@ -38,6 +42,13 @@ class NotificationWidget extends StatelessWidget {
case NotificationType.banner: case NotificationType.banner:
backgroundColor = backgroundElevated2Dark; backgroundColor = backgroundElevated2Dark;
break; break;
case NotificationType.goldenBanner:
backgroundGradient = LinearGradient(
colors: [colorScheme.golden700, colorScheme.golden500],
stops: const [0.25, 1],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
);
} }
return Center( return Center(
child: GestureDetector( child: GestureDetector(
@ -49,6 +60,7 @@ class NotificationWidget extends StatelessWidget {
), ),
boxShadow: Theme.of(context).colorScheme.enteTheme.shadowMenu, boxShadow: Theme.of(context).colorScheme.enteTheme.shadowMenu,
color: backgroundColor, color: backgroundColor,
gradient: backgroundGradient,
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),

View file

@ -1,6 +1,8 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:logging/logging.dart"; import "package:logging/logging.dart";
import "package:photos/extensions/input_formatter.dart"; import "package:photos/extensions/input_formatter.dart";
import "package:photos/models/api/storage_bonus/storage_bonus.dart";
import "package:photos/models/user_details.dart";
import "package:photos/services/storage_bonus_service.dart"; import "package:photos/services/storage_bonus_service.dart";
import "package:photos/theme/ente_theme.dart"; import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/button_widget.dart"; import "package:photos/ui/components/button_widget.dart";
@ -8,10 +10,18 @@ import "package:photos/ui/components/icon_button_widget.dart";
import "package:photos/ui/components/models/button_type.dart"; import "package:photos/ui/components/models/button_type.dart";
import "package:photos/ui/components/title_bar_title_widget.dart"; import "package:photos/ui/components/title_bar_title_widget.dart";
import "package:photos/ui/components/title_bar_widget.dart"; import "package:photos/ui/components/title_bar_widget.dart";
import "package:photos/ui/growth/code_success_screen.dart";
import "package:photos/utils/dialog_util.dart"; import "package:photos/utils/dialog_util.dart";
class ApplyCodeScreen extends StatefulWidget { class ApplyCodeScreen extends StatefulWidget {
const ApplyCodeScreen({super.key}); // referrerView and userDetails used to render code_success_screen
final ReferralView referralView;
final UserDetails userDetails;
const ApplyCodeScreen(
this.referralView,
this.userDetails, {
super.key,
});
@override @override
State<ApplyCodeScreen> createState() => _ApplyCodeScreenState(); State<ApplyCodeScreen> createState() => _ApplyCodeScreenState();
@ -112,14 +122,22 @@ class _ApplyCodeScreenState extends State<ApplyCodeScreen> {
await StorageBonusService.instance await StorageBonusService.instance
.getGateway() .getGateway()
.claimReferralCode(code.trim().toUpperCase()); .claimReferralCode(code.trim().toUpperCase());
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => CodeSuccessScreen(
widget.referralView,
widget.userDetails,
),
),
);
} catch (e) { } catch (e) {
Logger('$runtimeType') Logger('$runtimeType')
.severe("failed to apply referral", e); .severe("failed to apply referral", e);
showErrorDialogForException( showErrorDialogForException(
context: context, context: context,
exception: e as Exception, exception: e as Exception,
); apiErrorPrefix: "Failed to apply code");
} }
}, },
) )

View file

@ -0,0 +1,163 @@
import "package:flutter/material.dart";
import "package:flutter_animate/flutter_animate.dart";
import "package:photos/models/api/storage_bonus/storage_bonus.dart";
import "package:photos/models/user_details.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/captioned_text_widget.dart";
import "package:photos/ui/components/icon_button_widget.dart";
import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
import "package:photos/ui/components/title_bar_title_widget.dart";
import "package:photos/ui/components/title_bar_widget.dart";
import "package:photos/ui/growth/referral_code_widget.dart";
import "package:photos/ui/growth/storage_details_screen.dart";
import "package:photos/utils/navigation_util.dart";
import "package:photos/utils/share_util.dart";
class CodeSuccessScreen extends StatelessWidget {
final ReferralView referralView;
final UserDetails userDetails;
const CodeSuccessScreen(this.referralView, this.userDetails, {super.key});
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textStyle = getEnteTextTheme(context);
return Scaffold(
body: CustomScrollView(
primary: false,
slivers: <Widget>[
TitleBarWidget(
flexibleSpaceTitle: const TitleBarTitleWidget(
title: "Code applied",
),
actionIcons: [
IconButtonWidget(
icon: Icons.close_outlined,
iconButtonType: IconButtonType.secondary,
onTap: () {
Navigator.pop(context);
},
),
],
),
SliverList(
delegate: SliverChildBuilderDelegate(
(delegateBuildContext, index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.check,
color: colorScheme.primary500,
size: 96,
)
.animate()
.scaleXY(
begin: 0.5,
end: 1,
duration: 750.ms,
curve: Curves.easeInOutCubic,
delay: 250.ms,
)
.fadeIn(
duration: 500.ms,
curve: Curves.easeInOutCubic,
),
Text(
"${referralView.planInfo.storageInGB} GB",
style: textStyle.h2Bold,
),
Text(
"Claimed",
style: textStyle.body
.copyWith(color: colorScheme.textMuted),
),
const SizedBox(height: 32),
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Details",
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: Icon(
Icons.chevron_right_outlined,
color: colorScheme.strokeBase,
),
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
onTap: () async {
routeToPage(
context,
StorageDetailsScreen(referralView, userDetails),
);
},
),
const SizedBox(height: 32),
InkWell(
onTap: () {
shareText(
"ente referral code: ${referralView.code} \n\nApply it in Settings → General → Referrals to get ${referralView.planInfo.storageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io",
);
},
child: Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(
color: colorScheme.strokeFaint,
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 12,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Claim more!",
style: textStyle.body,
),
const SizedBox(height: 8),
Text(
"${referralView.planInfo.storageInGB} GB each time someone signs up for a paid plan and applies your code",
style: textStyle.small
.copyWith(color: colorScheme.textMuted),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ReferralCodeWidget(referralView.code),
const SizedBox(height: 16),
Text(
"They also get ${referralView.planInfo.storageInGB} GB",
style: textStyle.small
.copyWith(color: colorScheme.textMuted),
textAlign: TextAlign.center,
),
],
),
),
),
)
],
),
),
);
},
childCount: 1,
),
),
],
),
);
}
}

View file

@ -0,0 +1,52 @@
import "package:dotted_border/dotted_border.dart";
import "package:flutter/material.dart";
import "package:photos/theme/ente_theme.dart";
// Figma: https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=11219%3A62974&t=BRCLJhxXP11Q3Wyw-0
class ReferralCodeWidget extends StatelessWidget {
final String codeValue;
const ReferralCodeWidget(this.codeValue, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textStyle = getEnteTextTheme(context);
return Center(
child: Container(
color: colorScheme.backgroundElevated2,
child: DottedBorder(
color: colorScheme.strokeMuted,
strokeWidth: 1,
dashPattern: const [6, 6],
radius: const Radius.circular(8),
child: Padding(
padding: const EdgeInsets.only(
left: 26.0,
top: 14,
right: 12,
bottom: 14,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
codeValue,
style: textStyle.bodyBold.copyWith(
color: colorScheme.primary700,
),
),
const SizedBox(width: 12),
Icon(
Icons.adaptive.share,
size: 22,
color: colorScheme.strokeMuted,
)
],
),
),
),
),
);
}
}

View file

@ -1,4 +1,3 @@
import "package:dotted_border/dotted_border.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:photos/models/api/storage_bonus/storage_bonus.dart"; import "package:photos/models/api/storage_bonus/storage_bonus.dart";
import "package:photos/models/user_details.dart"; import "package:photos/models/user_details.dart";
@ -14,6 +13,7 @@ import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
import "package:photos/ui/components/title_bar_title_widget.dart"; import "package:photos/ui/components/title_bar_title_widget.dart";
import "package:photos/ui/components/title_bar_widget.dart"; import "package:photos/ui/components/title_bar_widget.dart";
import "package:photos/ui/growth/apply_code_screen.dart"; import "package:photos/ui/growth/apply_code_screen.dart";
import "package:photos/ui/growth/referral_code_widget.dart";
import "package:photos/ui/growth/storage_details_screen.dart"; import "package:photos/ui/growth/storage_details_screen.dart";
import "package:photos/utils/data_util.dart"; import "package:photos/utils/data_util.dart";
import "package:photos/utils/navigation_util.dart"; import "package:photos/utils/navigation_util.dart";
@ -129,7 +129,8 @@ class ReferralWidget extends StatelessWidget {
? InkWell( ? InkWell(
onTap: () { onTap: () {
shareText( shareText(
"ente referral code: ${referralView.code} \n\nApply it in Settings → General → Referrals to get 10 GB free after you signup for a paid plan\n\nhttps://ente.io"); "ente referral code: ${referralView.code} \n\nApply it in Settings → General → Referrals to get 10 GB free after you signup for a paid plan\n\nhttps://ente.io",
);
}, },
child: Container( child: Container(
width: double.infinity, width: double.infinity,
@ -153,41 +154,7 @@ class ReferralWidget extends StatelessWidget {
"friends", "friends",
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Center( ReferralCodeWidget(referralView.code),
child: DottedBorder(
color: colorScheme.strokeMuted,
//color of dotted/dash line
strokeWidth: 1,
//thickness of dash/dots
dashPattern: const [6, 6],
radius: const Radius.circular(8),
child: Padding(
padding: const EdgeInsets.only(
left: 26.0,
top: 14,
right: 12,
bottom: 14,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
referralView.code,
style: textStyle.bodyBold.copyWith(
color: colorScheme.primary700,
),
),
const SizedBox(width: 12),
Icon(
Icons.adaptive.share,
size: 22,
color: colorScheme.strokeMuted,
)
],
),
),
),
),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( const Text(
"2. They sign up for a paid plan", "2. They sign up for a paid plan",
@ -213,9 +180,11 @@ class ReferralWidget extends StatelessWidget {
color: colorScheme.strokeMuted, color: colorScheme.strokeMuted,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Text("Referrals are currently paused", Text(
"Referrals are currently paused",
style: textStyle.small style: textStyle.small
.copyWith(color: colorScheme.textFaint)), .copyWith(color: colorScheme.textFaint),
),
], ],
), ),
), ),
@ -247,7 +216,7 @@ class ReferralWidget extends StatelessWidget {
onTap: () async { onTap: () async {
await routeToPage( await routeToPage(
context, context,
const ApplyCodeScreen(), ApplyCodeScreen(referralView, userDetails),
); );
notifyParent(); notifyParent();
}, },
@ -275,7 +244,10 @@ class ReferralWidget extends StatelessWidget {
routeToPage( routeToPage(
context, context,
const WebPage( const WebPage(
"FAQ", "https://ente.io/faq/general/referral-program")); "FAQ",
"https://ente.io/faq/general/referral-program",
),
);
}, },
), ),
const SizedBox(height: 24), const SizedBox(height: 24),

View file

@ -108,19 +108,23 @@ class _StorageDetailsScreenState extends State<StorageDetailsScreen> {
BonusInfoSection( BonusInfoSection(
sectionName: "Free storage claimed", sectionName: "Free storage claimed",
leftValue: convertBytesToAbsoluteGBs( leftValue: convertBytesToAbsoluteGBs(
widget.referralView.claimedStorage), widget.referralView.claimedStorage,
),
leftUnitName: "GB", leftUnitName: "GB",
rightValue: null, rightValue: null,
), ),
BonusInfoSection( BonusInfoSection(
sectionName: "Free storage usable", sectionName: "Free storage usable",
leftValue: convertBytesToAbsoluteGBs(min( leftValue: convertBytesToAbsoluteGBs(
min(
widget.referralView.claimedStorage, widget.referralView.claimedStorage,
widget.userDetails.getTotalStorage(), widget.userDetails.getTotalStorage(),
)), ),
),
leftUnitName: "GB", leftUnitName: "GB",
rightValue: convertBytesToAbsoluteGBs( rightValue: convertBytesToAbsoluteGBs(
widget.userDetails.getTotalStorage()), widget.userDetails.getTotalStorage(),
),
rightUnitName: "GB", rightUnitName: "GB",
), ),
const SizedBox( const SizedBox(

View file

@ -1,8 +1,11 @@
import "dart:io";
import "package:flutter/cupertino.dart";
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart'; import 'package:photos/ente_theme_data.dart';
import 'package:photos/models/memory.dart'; import 'package:photos/models/memory.dart';
import 'package:photos/services/memories_service.dart'; import 'package:photos/services/memories_service.dart';
import 'package:photos/ui/extents_page_view.dart'; import "package:photos/ui/actions/file/file_actions.dart";
import 'package:photos/ui/viewer/file/file_widget.dart'; import 'package:photos/ui/viewer/file/file_widget.dart';
import 'package:photos/ui/viewer/file/thumbnail_widget.dart'; import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
import 'package:photos/utils/date_time_util.dart'; import 'package:photos/utils/date_time_util.dart';
@ -315,6 +318,22 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
); );
} }
void onFileDeleted() {
if (widget.memories.length == 1) {
Navigator.pop(context);
} else {
setState(() {
if (_index != 0) {
_pageController?.jumpToPage(_index - 1);
}
widget.memories.removeAt(_index);
if (_index != 0) {
_index--;
}
});
}
}
Hero _buildInfoText() { Hero _buildInfoText() {
return Hero( return Hero(
tag: widget.title, tag: widget.title,
@ -346,10 +365,38 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
Widget _buildBottomIcons() { Widget _buildBottomIcons() {
final file = widget.memories[_index].file; final file = widget.memories[_index].file;
return Container( return SafeArea(
child: Container(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
padding: const EdgeInsets.fromLTRB(0, 0, 26, 20), padding: const EdgeInsets.fromLTRB(26, 0, 26, 20),
child: IconButton( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Icon(
Platform.isAndroid ? Icons.info_outline : CupertinoIcons.info,
color: Colors.white, //same for both themes
),
onPressed: () {
showInfoSheet(context, file);
},
),
IconButton(
icon: Icon(
Platform.isAndroid
? Icons.delete_outline
: CupertinoIcons.delete,
color: Colors.white, //same for both themes
),
onPressed: () async {
await showSingleFileDeleteSheet(
context,
file,
onFileRemoved: (file) => {onFileDeleted()},
);
},
),
IconButton(
icon: Icon( icon: Icon(
Icons.adaptive.share, Icons.adaptive.share,
color: Colors.white, //same for both themes color: Colors.white, //same for both themes
@ -358,6 +405,9 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
share(context, [file]); share(context, [file]);
}, },
), ),
],
),
),
); );
} }
@ -381,7 +431,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
Widget _buildSwiper() { Widget _buildSwiper() {
_pageController = PageController(initialPage: _index); _pageController = PageController(initialPage: _index);
return ExtentsPageView.extents( return PageView.builder(
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
if (index < widget.memories.length - 1) { if (index < widget.memories.length - 1) {
final nextFile = widget.memories[index + 1].file; final nextFile = widget.memories[index + 1].file;
@ -405,7 +455,6 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
}, },
itemCount: widget.memories.length, itemCount: widget.memories.length,
controller: _pageController, controller: _pageController,
extents: 1,
onPageChanged: (index) async { onPageChanged: (index) async {
await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]); await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]);
if (mounted) { if (mounted) {

View file

@ -265,7 +265,7 @@ class _HomeWidgetState extends State<HomeWidget> {
_logger.info("Building home_Widget with tab $_selectedTabIndex"); _logger.info("Building home_Widget with tab $_selectedTabIndex");
bool isSettingsOpen = false; bool isSettingsOpen = false;
final enableDrawer = LocalSyncService.instance.hasCompletedFirstImport(); final enableDrawer = LocalSyncService.instance.hasCompletedFirstImport();
final action = AppLifecycleService.instance.mediaExtensionAction.action;
return UserDetailsStateWidget( return UserDetailsStateWidget(
child: WillPopScope( child: WillPopScope(
child: Scaffold( child: Scaffold(
@ -298,9 +298,7 @@ class _HomeWidgetState extends State<HomeWidget> {
Navigator.pop(context); Navigator.pop(context);
return false; return false;
} }
if (Platform.isAndroid && if (Platform.isAndroid && action == IntentAction.main) {
AppLifecycleService.instance.intentAction ==
IntentAction.main) {
MoveToBackground.moveTaskToBack(); MoveToBackground.moveTaskToBack();
return false; return false;
} else { } else {

View file

@ -311,7 +311,6 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
bool? _shouldRender; bool? _shouldRender;
int? _currentUserID; int? _currentUserID;
late StreamSubscription<ClearSelectionsEvent> _clearSelectionsEvent; late StreamSubscription<ClearSelectionsEvent> _clearSelectionsEvent;
final _mediaExtensionPlugin = MediaExtension();
@override @override
void initState() { void initState() {
@ -426,16 +425,18 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
if (widget.selectedFiles.files.isNotEmpty) { if (widget.selectedFiles.files.isNotEmpty) {
_selectFile(file); _selectFile(file);
} else { } else {
if (AppLifecycleService.instance.intentAction == IntentAction.pick) { if (AppLifecycleService.instance.mediaExtensionAction.action ==
IntentAction.pick) {
final ioFile = await getFile(file); final ioFile = await getFile(file);
_mediaExtensionPlugin.setResult("file://${ioFile!.path}"); MediaExtension().setResult("file://${ioFile!.path}");
} else { } else {
_routeToDetailPage(file, context); _routeToDetailPage(file, context);
} }
} }
}, },
onLongPress: () { onLongPress: () {
if (AppLifecycleService.instance.intentAction == IntentAction.main) { if (AppLifecycleService.instance.mediaExtensionAction.action ==
IntentAction.main) {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
_selectFile(file); _selectFile(file);
} }

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:flutter_animate/flutter_animate.dart";
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart'; import 'package:photos/core/event_bus.dart';
import 'package:photos/events/opened_settings_event.dart'; import 'package:photos/events/opened_settings_event.dart';
@ -77,19 +78,25 @@ class SettingsPage extends StatelessWidget {
contents.addAll([ contents.addAll([
const StorageCardWidget(), const StorageCardWidget(),
StorageBonusService.instance.shouldShowStorageBonus() StorageBonusService.instance.shouldShowStorageBonus()
? Padding( ? RepaintBoundary(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: NotificationWidget( child: NotificationWidget(
startIcon: Icons.auto_awesome, startIcon: Icons.auto_awesome,
actionIcon: Icons.arrow_forward_outlined, actionIcon: Icons.arrow_forward_outlined,
text: "Double your storage", text: "Double your storage",
subText: "Refer friends and 2x your plan", subText: "Refer friends and 2x your plan",
type: NotificationType.banner, type: NotificationType.goldenBanner,
onTap: () async { onTap: () async {
StorageBonusService.instance.markStorageBonusAsDone(); StorageBonusService.instance.markStorageBonusAsDone();
routeToPage(context, const ReferralScreen()); routeToPage(context, const ReferralScreen());
}, },
), ),
).animate(onPlay: (controller) => controller.repeat()).shimmer(
duration: 1000.ms,
delay: 3200.ms,
size: 0.6,
),
) )
: const SizedBox(height: 12), : const SizedBox(height: 12),
const BackupSectionWidget(), const BackupSectionWidget(),

View file

@ -228,7 +228,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
), ),
leadingIcon: Icons.link, leadingIcon: Icons.link,
menuItemColor: getEnteColorScheme(context).fillFaint, menuItemColor: getEnteColorScheme(context).fillFaint,
isBottomBorderRadiusRemoved: true,
showOnlyLoadingState: true, showOnlyLoadingState: true,
onTap: () async { onTap: () async {
final bool result = final bool result =

View file

@ -101,7 +101,8 @@ class _DeleteEmptyAlbumsState extends State<DeleteEmptyAlbums> {
try { try {
await CollectionsService.instance.trashEmptyCollection( await CollectionsService.instance.trashEmptyCollection(
collections[i].collection, collections[i].collection,
isBulkDelete: true); isBulkDelete: true,
);
} catch (_) { } catch (_) {
failedCount++; failedCount++;
} }

View file

@ -51,7 +51,7 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
late CollectionActions collectionActions; late CollectionActions collectionActions;
late bool isCollectionOwner; late bool isCollectionOwner;
// _cachedCollectionForSharedLink is primarly used to avoid creating duplicate // _cachedCollectionForSharedLink is primarily used to avoid creating duplicate
// links if user keeps on creating Create link button after selecting // links if user keeps on creating Create link button after selecting
// few files. This link is reset on any selection changed; // few files. This link is reset on any selection changed;
Collection? _cachedCollectionForSharedLink; Collection? _cachedCollectionForSharedLink;

View file

@ -0,0 +1,104 @@
import 'dart:convert';
import "package:chewie/chewie.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:logging/logging.dart";
import "package:media_extension/media_extension_action_types.dart";
import "package:photo_view/photo_view.dart";
import "package:photos/services/app_lifecycle_service.dart";
import "package:video_player/video_player.dart";
class FileViewer extends StatefulWidget {
const FileViewer({super.key});
@override
State<StatefulWidget> createState() {
return FileViewerState();
}
}
class FileViewerState extends State<FileViewer> {
final action = AppLifecycleService.instance.mediaExtensionAction;
ChewieController? controller;
VideoPlayerController? videoController;
final Logger _logger = Logger("FileViewer");
@override
void initState() {
super.initState();
if (action.type == MediaType.video) {
initController();
}
}
@override
void dispose() {
videoController?.dispose();
controller?.dispose();
super.dispose();
}
void initController() async {
videoController = VideoPlayerController.contentUri(
Uri.parse(action.data!),
);
controller = ChewieController(
videoPlayerController: videoController!,
autoInitialize: true,
aspectRatio: 16 / 9,
autoPlay: true,
looping: true,
showOptions: false,
materialProgressColors: ChewieProgressColors(
playedColor: const Color.fromRGBO(45, 194, 98, 1.0),
handleColor: Colors.white,
bufferedColor: Colors.white,
),
);
controller!.addListener(() {
if (!controller!.isFullScreen) {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp],
);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
},
icon: const Icon(Icons.arrow_back),
),
),
body: Column(
children: [
Expanded(
child: Center(
child: (() {
if (action.type == MediaType.image) {
return PhotoView(
imageProvider: MemoryImage(base64Decode(action.data!)),
);
} else if (action.type == MediaType.video) {
return controller != null
? Chewie(controller: controller!)
: const CircularProgressIndicator();
} else {
_logger.severe('unsupported file type ${action.type}');
return const Icon(Icons.error);
}
})(),
),
),
],
),
);
}
}

View file

@ -124,6 +124,8 @@ class _DetailPageState extends State<DetailPage> {
_files![_selectedIndex], _files![_selectedIndex],
_onEditFileRequested, _onEditFileRequested,
widget.config.mode == DetailPageMode.minimalistic, widget.config.mode == DetailPageMode.minimalistic,
onFileRemoved: _onFileRemoved,
userID: Configuration.instance.getUserID(),
key: _bottomBarKey, key: _bottomBarKey,
), ),
], ],

View file

@ -14,6 +14,7 @@ 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/file_type.dart'; import 'package:photos/models/file_type.dart';
import 'package:photos/models/ignored_file.dart'; import 'package:photos/models/ignored_file.dart';
import "package:photos/models/magic_metadata.dart";
import 'package:photos/models/selected_files.dart'; import 'package:photos/models/selected_files.dart';
import 'package:photos/models/trash_file.dart'; import 'package:photos/models/trash_file.dart';
import 'package:photos/services/collections_service.dart'; import 'package:photos/services/collections_service.dart';
@ -23,13 +24,10 @@ import 'package:photos/services/ignored_files_service.dart';
import 'package:photos/services/local_sync_service.dart'; import 'package:photos/services/local_sync_service.dart';
import 'package:photos/ui/collection_action_sheet.dart'; import 'package:photos/ui/collection_action_sheet.dart';
import 'package:photos/ui/common/progress_dialog.dart'; import 'package:photos/ui/common/progress_dialog.dart';
import 'package:photos/ui/components/action_sheet_widget.dart';
import 'package:photos/ui/components/button_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
import 'package:photos/ui/viewer/file/custom_app_bar.dart'; import 'package:photos/ui/viewer/file/custom_app_bar.dart';
import 'package:photos/utils/delete_file_util.dart';
import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/file_util.dart'; import 'package:photos/utils/file_util.dart';
import "package:photos/utils/magic_util.dart";
import 'package:photos/utils/toast_util.dart'; import 'package:photos/utils/toast_util.dart';
class FadingAppBar extends StatefulWidget implements PreferredSizeWidget { class FadingAppBar extends StatefulWidget implements PreferredSizeWidget {
@ -148,22 +146,22 @@ class FadingAppBarState extends State<FadingAppBar> {
); );
} }
// options for files owned by the user // options for files owned by the user
if (isOwnedByUser) { if (isOwnedByUser && !isFileHidden) {
final bool isArchived =
widget.file.magicMetadata.visibility == visibilityArchive;
items.add( items.add(
PopupMenuItem( PopupMenuItem(
value: 2, value: 2,
child: Row( child: Row(
children: [ children: [
Icon( Icon(
Platform.isAndroid isArchived ? Icons.unarchive : Icons.archive_outlined,
? Icons.delete_outline
: CupertinoIcons.delete,
color: Theme.of(context).iconTheme.color, color: Theme.of(context).iconTheme.color,
), ),
const Padding( const Padding(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
), ),
const Text("Delete"), Text(isArchived ? "Unarchive" : "Archive"),
], ],
), ),
), ),
@ -235,7 +233,7 @@ class FadingAppBarState extends State<FadingAppBar> {
if (value == 1) { if (value == 1) {
_download(widget.file); _download(widget.file);
} else if (value == 2) { } else if (value == 2) {
await _showSingleFileDeleteSheet(widget.file); await _toggleFileArchiveStatus(widget.file);
} else if (value == 3) { } else if (value == 3) {
_setAs(widget.file); _setAs(widget.file);
} else if (value == 4) { } else if (value == 4) {
@ -337,109 +335,16 @@ class FadingAppBarState extends State<FadingAppBar> {
); );
} }
Future<void> _showSingleFileDeleteSheet(File file) async { Future<void> _toggleFileArchiveStatus(File file) async {
final List<ButtonWidget> buttons = []; final bool isArchived =
final String fileType = file.fileType == FileType.video ? "video" : "photo"; widget.file.magicMetadata.visibility == visibilityArchive;
final bool isBothLocalAndRemote = await changeVisibility(
file.uploadedFileID != null && file.localID != null; context,
final bool isLocalOnly = [widget.file],
file.uploadedFileID == null && file.localID != null; isArchived ? visibilityVisible : visibilityArchive,
final bool isRemoteOnly =
file.uploadedFileID != null && file.localID == null;
const String bodyHighlight = "It will be deleted from all albums.";
String body = "";
if (isBothLocalAndRemote) {
body = "This $fileType is in both ente and your device.";
} else if (isRemoteOnly) {
body = "This $fileType will be deleted from ente.";
} else if (isLocalOnly) {
body = "This $fileType will be deleted from your device.";
} else {
throw AssertionError("Unexpected state");
}
// Add option to delete from ente
if (isBothLocalAndRemote || isRemoteOnly) {
buttons.add(
ButtonWidget(
labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromRemoteOnly(context, [file]);
showShortToast(context, "Moved to trash");
if (isRemoteOnly) {
Navigator.of(context, rootNavigator: true).pop();
widget.onFileRemoved(file);
}
},
),
); );
} if (mounted) {
// Add option to delete from local setState(() {});
if (isBothLocalAndRemote || isLocalOnly) {
buttons.add(
ButtonWidget(
labelText:
isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.second,
shouldSurfaceExecutionStates: false,
isInAlert: true,
onTap: () async {
await deleteFilesOnDeviceOnly(context, [file]);
if (isLocalOnly) {
Navigator.of(context, rootNavigator: true).pop();
widget.onFileRemoved(file);
}
},
),
);
}
if (isBothLocalAndRemote) {
buttons.add(
ButtonWidget(
labelText: "Delete from both",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.third,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
widget.onFileRemoved(file);
},
),
);
}
buttons.add(
const ButtonWidget(
labelText: "Cancel",
buttonType: ButtonType.secondary,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.fourth,
isInAlert: true,
),
);
final actionResult = await showActionSheet(
context: context,
buttons: buttons,
actionSheetType: ActionSheetType.defaultActionSheet,
body: body,
bodyHighlight: bodyHighlight,
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
} }
} }

View file

@ -2,31 +2,30 @@ import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/models/file.dart'; import 'package:photos/models/file.dart';
import 'package:photos/models/file_type.dart'; import 'package:photos/models/file_type.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/selected_files.dart'; import 'package:photos/models/selected_files.dart';
import 'package:photos/models/trash_file.dart'; import 'package:photos/models/trash_file.dart';
import 'package:photos/services/collections_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/file/file_actions.dart";
import 'package:photos/ui/collection_action_sheet.dart'; import 'package:photos/ui/collection_action_sheet.dart';
import 'package:photos/ui/viewer/file/file_info_widget.dart';
import 'package:photos/utils/delete_file_util.dart'; import 'package:photos/utils/delete_file_util.dart';
import 'package:photos/utils/magic_util.dart';
import 'package:photos/utils/share_util.dart'; import 'package:photos/utils/share_util.dart';
class FadingBottomBar extends StatefulWidget { class FadingBottomBar extends StatefulWidget {
final File file; final File file;
final Function(File) onEditRequested; final Function(File) onEditRequested;
final Function(File) onFileRemoved;
final bool showOnlyInfoButton; final bool showOnlyInfoButton;
final int? userID;
const FadingBottomBar( const FadingBottomBar(
this.file, this.file,
this.onEditRequested, this.onEditRequested,
this.showOnlyInfoButton, { this.showOnlyInfoButton, {
required this.onFileRemoved,
this.userID,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -63,6 +62,8 @@ class FadingBottomBarState extends State<FadingBottomBar> {
Widget _getBottomBar() { Widget _getBottomBar() {
final List<Widget> children = []; final List<Widget> children = [];
final bool isOwnedByUser =
widget.file.ownerID == null || widget.file.ownerID == widget.userID;
children.add( children.add(
Tooltip( Tooltip(
message: "Info", message: "Info",
@ -88,15 +89,7 @@ class FadingBottomBarState extends State<FadingBottomBar> {
if (widget.file is TrashFile) { if (widget.file is TrashFile) {
_addTrashOptions(children); _addTrashOptions(children);
} }
final bool isUploadedByUser = widget.file.uploadedFileID != null &&
widget.file.ownerID == Configuration.instance.getUserID();
bool isFileHidden = false;
if (isUploadedByUser) {
isFileHidden = CollectionsService.instance
.getCollectionByID(widget.file.collectionID!)
?.isHidden() ??
false;
}
if (!widget.showOnlyInfoButton && widget.file is! TrashFile) { if (!widget.showOnlyInfoButton && widget.file is! TrashFile) {
if (widget.file.fileType == FileType.image || if (widget.file.fileType == FileType.image ||
widget.file.fileType == FileType.livePhoto) { widget.file.fileType == FileType.livePhoto) {
@ -118,26 +111,21 @@ class FadingBottomBarState extends State<FadingBottomBar> {
), ),
); );
} }
if (isUploadedByUser && !isFileHidden) { if (isOwnedByUser) {
final bool isArchived =
widget.file.magicMetadata.visibility == visibilityArchive;
children.add( children.add(
Tooltip( Tooltip(
message: isArchived ? "Unarchive" : "Archive", message: "Delete",
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 12, bottom: 12), padding: const EdgeInsets.only(top: 12, bottom: 12),
child: IconButton( child: IconButton(
icon: Icon( icon: Icon(
isArchived ? Icons.unarchive : Icons.archive_outlined, Platform.isAndroid
? Icons.delete_outline
: CupertinoIcons.delete,
color: Colors.white, color: Colors.white,
), ),
onPressed: () async { onPressed: () async {
await changeVisibility( await _showSingleFileDeleteSheet(widget.file);
context,
[widget.file],
isArchived ? visibilityVisible : visibilityArchive,
);
safeRefresh();
}, },
), ),
), ),
@ -223,6 +211,14 @@ class FadingBottomBarState extends State<FadingBottomBar> {
); );
} }
Future<void> _showSingleFileDeleteSheet(File file) async {
await showSingleFileDeleteSheet(
context,
file,
onFileRemoved: widget.onFileRemoved,
);
}
void _addTrashOptions(List<Widget> children) { void _addTrashOptions(List<Widget> children) {
children.add( children.add(
Tooltip( Tooltip(
@ -272,20 +268,6 @@ class FadingBottomBarState extends State<FadingBottomBar> {
} }
Future<void> _displayInfo(File file) async { Future<void> _displayInfo(File file) async {
final colorScheme = getEnteColorScheme(context); await showInfoSheet(context, file);
return showBarModalBottomSheet(
topControl: const SizedBox.shrink(),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
backgroundColor: colorScheme.backgroundElevated,
barrierColor: backdropFaintDark,
context: context,
builder: (BuildContext context) {
return Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: FileInfoWidget(file),
);
},
);
} }
} }

View file

@ -1,17 +1,14 @@
import 'package:collection/collection.dart' show IterableExtension; import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import "package:logging/logging.dart";
import 'package:photos/core/configuration.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/files_updated_event.dart'; import 'package:photos/events/files_updated_event.dart';
import "package:photos/models/collection_items.dart";
import 'package:photos/models/gallery_type.dart'; import 'package:photos/models/gallery_type.dart';
import 'package:photos/models/magic_metadata.dart'; import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/selected_files.dart'; import 'package:photos/models/selected_files.dart';
import 'package:photos/services/collections_service.dart'; import 'package:photos/services/collections_service.dart';
import "package:photos/ui/collections/collection_item_widget.dart"; import "package:photos/ui/components/album_horizontal_list_widget.dart";
import "package:photos/ui/common/loading_widget.dart";
import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.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';
@ -22,7 +19,6 @@ class ArchivePage extends StatelessWidget {
final GalleryType appBarType; final GalleryType appBarType;
final GalleryType overlayType; final GalleryType overlayType;
final _selectedFiles = SelectedFiles(); final _selectedFiles = SelectedFiles();
final Logger _logger = Logger("ArchivePage");
ArchivePage({ ArchivePage({
this.tagPrefix = "archived_page", this.tagPrefix = "archived_page",
@ -70,43 +66,8 @@ class ArchivePage extends StatelessWidget {
emptyState: const EmptyState( emptyState: const EmptyState(
text: "You don't have any archived items.", text: "You don't have any archived items.",
), ),
header: FutureBuilder( header: AlbumHorizontalListWidget(
future: CollectionsService.instance.getArchivedCollectionWithThumb(), CollectionsService.instance.getArchivedCollectionWithThumb,
builder: (context, snapshot) {
if (snapshot.hasError) {
_logger.severe("failed to fetch archived albums", snapshot.error);
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final collectionsWithThumbnail =
snapshot.data as List<CollectionWithThumbnail>;
return SizedBox(
height: 200,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: collectionsWithThumbnail.length,
padding: const EdgeInsets.fromLTRB(6, 6, 6, 6),
itemBuilder: (context, index) {
final item = collectionsWithThumbnail[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {},
child: Padding(
padding: const EdgeInsets.all(2.0),
child: CollectionItem(
item,
120,
shouldRender: true,
),
),
);
},
),
);
} else {
return const EnteLoadingWidget();
}
},
), ),
); );
return Scaffold( return Scaffold(

View file

@ -16,13 +16,13 @@ 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';
class CollectionPage extends StatefulWidget { class CollectionPage extends StatelessWidget {
final CollectionWithThumbnail c; final CollectionWithThumbnail c;
final String tagPrefix; final String tagPrefix;
final GalleryType appBarType; final GalleryType appBarType;
final bool hasVerifiedLock; final bool hasVerifiedLock;
const CollectionPage( CollectionPage(
this.c, { this.c, {
this.tagPrefix = "collection", this.tagPrefix = "collection",
this.appBarType = GalleryType.ownedCollection, this.appBarType = GalleryType.ownedCollection,
@ -30,42 +30,24 @@ class CollectionPage extends StatefulWidget {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override
State<CollectionPage> createState() => _CollectionPageState();
}
class _CollectionPageState extends State<CollectionPage> {
final _selectedFiles = SelectedFiles(); final _selectedFiles = SelectedFiles();
final GlobalKey shareButtonKey = GlobalKey(); 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 (widget.hasVerifiedLock == false && widget.c.collection.isHidden()) { if (hasVerifiedLock == false && c.collection.isHidden()) {
return const EmptyState(); return const EmptyState();
} }
final appBarTypeValue = _getGalleryType(widget.c.collection); final appBarTypeValue = _getGalleryType(c.collection);
final List<File>? initialFiles = final List<File>? initialFiles =
widget.c.thumbnail != null ? [widget.c.thumbnail!] : null; c.thumbnail != null ? [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(
widget.c.collection.id, c.collection.id,
creationStartTime, creationStartTime,
creationEndTime, creationEndTime,
limit: limit, limit: limit,
@ -82,25 +64,25 @@ class _CollectionPageState extends State<CollectionPage> {
}, },
reloadEvent: Bus.instance reloadEvent: Bus.instance
.on<CollectionUpdatedEvent>() .on<CollectionUpdatedEvent>()
.where((event) => event.collectionID == widget.c.collection.id), .where((event) => event.collectionID == c.collection.id),
removalEventTypes: const { removalEventTypes: const {
EventType.deletedFromRemote, EventType.deletedFromRemote,
EventType.deletedFromEverywhere, EventType.deletedFromEverywhere,
EventType.hide, EventType.hide,
}, },
tagPrefix: widget.tagPrefix, tagPrefix: tagPrefix,
selectedFiles: _selectedFiles, selectedFiles: _selectedFiles,
initialFiles: initialFiles, initialFiles: initialFiles,
albumName: widget.c.collection.name, albumName: 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(
appBarTypeValue, appBarTypeValue,
widget.c.collection.name, c.collection.name,
_selectedFiles, _selectedFiles,
collection: widget.c.collection, collection: c.collection,
), ),
), ),
body: Stack( body: Stack(
@ -110,7 +92,7 @@ class _CollectionPageState extends State<CollectionPage> {
FileSelectionOverlayBar( FileSelectionOverlayBar(
appBarTypeValue, appBarTypeValue,
_selectedFiles, _selectedFiles,
collection: widget.c.collection, collection: c.collection,
) )
], ],
), ),
@ -129,12 +111,6 @@ class _CollectionPageState extends State<CollectionPage> {
} else if (c.type == CollectionType.favorites) { } else if (c.type == CollectionType.favorites) {
return GalleryType.favorite; return GalleryType.favorite;
} }
return widget.appBarType; return appBarType;
}
_selectedFilesListener() {
_selectedFiles.files.isNotEmpty
? _bottomPosition.value = 0.0
: _bottomPosition.value = -150.0;
} }
} }

View file

@ -14,7 +14,7 @@ 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/utils/delete_file_util.dart'; import 'package:photos/utils/delete_file_util.dart';
class TrashPage extends StatefulWidget { class TrashPage extends StatelessWidget {
final String tagPrefix; final String tagPrefix;
final GalleryType appBarType; final GalleryType appBarType;
final GalleryType overlayType; final GalleryType overlayType;
@ -26,30 +26,9 @@ class TrashPage extends StatefulWidget {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override
State<TrashPage> createState() => _TrashPageState();
}
class _TrashPageState extends State<TrashPage> {
late Function() _selectedFilesListener;
@override
void initState() {
_selectedFilesListener = () {
setState(() {});
};
widget._selectedFiles.addListener(_selectedFilesListener);
super.initState();
}
@override
void dispose() {
widget._selectedFiles.removeListener(_selectedFilesListener);
super.dispose();
}
@override @override
Widget build(Object context) { Widget build(Object context) {
final bool filesAreSelected = widget._selectedFiles.files.isNotEmpty; final bool filesAreSelected = _selectedFiles.files.isNotEmpty;
final gallery = Gallery( final gallery = Gallery(
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) { asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
@ -70,8 +49,8 @@ class _TrashPageState extends State<TrashPage> {
forceReloadEvents: [ forceReloadEvents: [
Bus.instance.on<ForceReloadTrashPageEvent>(), Bus.instance.on<ForceReloadTrashPageEvent>(),
], ],
tagPrefix: widget.tagPrefix, tagPrefix: tagPrefix,
selectedFiles: widget._selectedFiles, selectedFiles: _selectedFiles,
header: _headerWidget(), header: _headerWidget(),
initialFiles: null, initialFiles: null,
); );
@ -80,9 +59,9 @@ class _TrashPageState extends State<TrashPage> {
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(50.0), preferredSize: const Size.fromHeight(50.0),
child: GalleryAppBarWidget( child: GalleryAppBarWidget(
widget.appBarType, appBarType,
"Trash", "Trash",
widget._selectedFiles, _selectedFiles,
), ),
), ),
body: Stack( body: Stack(
@ -109,7 +88,7 @@ class _TrashPageState extends State<TrashPage> {
), ),
), ),
), ),
FileSelectionOverlayBar(GalleryType.trash, widget._selectedFiles) FileSelectionOverlayBar(GalleryType.trash, _selectedFiles)
], ],
), ),
); );

View file

@ -13,6 +13,7 @@ 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';
import 'package:photos/events/files_updated_event.dart'; import 'package:photos/events/files_updated_event.dart';
import "package:photos/events/force_reload_trash_page_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/selected_files.dart';
@ -264,6 +265,9 @@ Future<bool> deleteFromTrash(BuildContext context, List<File> files) async {
source: "deleteFromTrash", source: "deleteFromTrash",
), ),
); );
//the FilesUpdateEvent is not reloading trash on premanently removing
//files, so need to fire ForceReloadTrashPageEvent
Bus.instance.fire(ForceReloadTrashPageEvent());
} catch (e, s) { } catch (e, s) {
_logger.info("failed to delete from trash", e, s); _logger.info("failed to delete from trash", e, s);
rethrow; rethrow;

View file

@ -43,14 +43,15 @@ Future<ButtonResult?> showErrorDialogForException({
required BuildContext context, required BuildContext context,
required Exception exception, required Exception exception,
bool isDismissible = true, bool isDismissible = true,
String apiErrorPrefix = "It looks like something went wrong.",
}) async { }) async {
String errorMessage = String errorMessage =
"It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team."; "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.";
if (exception is DioError && if (exception is DioError &&
exception.response != null && exception.response != null &&
exception.response!.data["code"] != null) { exception.response!.data["code"] != null) {
errorMessage = "It looks like something went wrong. \n\nReason: " + errorMessage =
exception.response!.data["code"]; "$apiErrorPrefix\n\nReason: " + exception.response!.data["code"];
} }
return showDialogWidget( return showDialogWidget(
context: context, context: context,

View file

@ -6,7 +6,7 @@ import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:connectivity/connectivity.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:flutter_sodium/flutter_sodium.dart';
@ -282,9 +282,11 @@ class FileUploader {
return; return;
} }
final connectivityResult = await (Connectivity().checkConnectivity()); final connectivityResult = await (Connectivity().checkConnectivity());
final canUploadUnderCurrentNetworkConditions = bool canUploadUnderCurrentNetworkConditions = true;
(connectivityResult == ConnectivityResult.wifi || if (connectivityResult == ConnectivityResult.mobile) {
Configuration.instance.shouldBackupOverMobileData()); canUploadUnderCurrentNetworkConditions =
Configuration.instance.shouldBackupOverMobileData();
}
if (!canUploadUnderCurrentNetworkConditions) { if (!canUploadUnderCurrentNetworkConditions) {
throw WiFiUnavailableError(); throw WiFiUnavailableError();
} }

View file

@ -0,0 +1,16 @@
import "package:flutter/services.dart";
import "package:media_extension/media_extension.dart";
import "package:media_extension/media_extension_action_types.dart";
Future<MediaExtentionAction> initIntentAction() async {
final mediaExtensionPlugin = MediaExtension();
MediaExtentionAction mediaExtensionAction;
try {
mediaExtensionAction = await mediaExtensionPlugin.getIntentAction();
} on PlatformException {
mediaExtensionAction = MediaExtentionAction(action: IntentAction.main);
} catch (error) {
mediaExtensionAction = MediaExtentionAction(action: IntentAction.main);
}
return mediaExtensionAction;
}

View file

@ -208,38 +208,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "0.6.0"
connectivity: connectivity_plus:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity name: connectivity_plus
sha256: a8e91263cf3e25fb5cc95e19dfde4999e32a648ac3b9e8a558a28165731678f8 sha256: "8875e8ed511a49f030e313656154e4bbbcef18d68dfd32eb853fac10bce48e96"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.6" version: "3.0.3"
connectivity_for_web: connectivity_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: connectivity_for_web name: connectivity_plus_platform_interface
sha256: "01a390c1d5adc2ed1fa1f52d120c07fe9fd01166a93f965a832fd6cfc0ea6482" sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.0+1" version: "1.2.4"
connectivity_macos:
dependency: transitive
description:
name: connectivity_macos
sha256: "51ae08d5162eca9669b9d8951ed83ce19c5355a81149f94e4dee2740beb93628"
url: "https://pub.dev"
source: hosted
version: "0.2.1+2"
connectivity_platform_interface:
dependency: transitive
description:
name: connectivity_platform_interface
sha256: "2d82e942df9d49f29a24bb07fb5ce085d4a53e47818c62364d2b6deb9e0d7a8e"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -493,6 +477,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_animate:
dependency: "direct main"
description:
name: flutter_animate
sha256: c6e38af9fe376fa07fb6995fe6a12d07a08828d2847aa652b7c1bc60f9993374
url: "https://pub.dev"
source: hosted
version: "4.1.0"
flutter_blurhash: flutter_blurhash:
dependency: transitive dependency: transitive
description: description:
@ -1005,6 +997,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
node_preamble: node_preamble:
dependency: transitive dependency: transitive
description: description:

View file

@ -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.7.26+426 version: 0.7.27+427
environment: environment:
sdk: '>=2.17.0 <3.0.0' sdk: '>=2.17.0 <3.0.0'
@ -30,7 +30,7 @@ dependencies:
collection: # dart collection: # dart
computer: ^2.0.0 computer: ^2.0.0
confetti: ^0.6.0 confetti: ^0.6.0
connectivity: ^3.0.3 connectivity_plus: ^3.0.3
cupertino_icons: ^1.0.0 cupertino_icons: ^1.0.0
device_info: ^2.0.2 device_info: ^2.0.2
dio: ^4.0.6 dio: ^4.0.6
@ -49,6 +49,7 @@ dependencies:
fk_user_agent: ^2.0.1 fk_user_agent: ^2.0.1
flutter: flutter:
sdk: flutter sdk: flutter
flutter_animate: ^4.1.0
flutter_cache_manager: ^3.3.0 flutter_cache_manager: ^3.3.0
flutter_datetime_picker: ^1.5.1 flutter_datetime_picker: ^1.5.1
flutter_easyloading: ^3.0.0 flutter_easyloading: ^3.0.0
@ -76,7 +77,7 @@ dependencies:
local_auth: ^1.1.5 local_auth: ^1.1.5
logging: ^1.0.1 logging: ^1.0.1
lottie: ^1.2.2 lottie: ^1.2.2
media_extension: ^1.0.0 media_extension: ^1.0.1
modal_bottom_sheet: ^3.0.0-pre modal_bottom_sheet: ^3.0.0-pre
motionphoto: motionphoto:
git: "https://github.com/ente-io/motionphoto.git" git: "https://github.com/ente-io/motionphoto.git"
@ -147,6 +148,14 @@ flutter_native_splash:
android_fullscreen: true android_fullscreen: true
android_gravity: center android_gravity: center
ios_content_mode: center ios_content_mode: center
android_12:
# The image parameter sets the splash screen icon image. If this parameter is not specified,
# the app's launcher icon will be used instead.
# Please note that the splash screen will be clipped to a circle on the center of the screen.
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
# 768 pixels in diameter.
image: assets/splash-screen-light.png
image_dark: assets/splash-screen-dark.png
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

12
run.sh
View file

@ -2,14 +2,12 @@
FLUTTER_RUN="flutter run --flavor dev " FLUTTER_RUN="flutter run --flavor dev "
if [ ! -z "$1" ] SUPPLIED_ENV_FILE=".env"
then while IFS= read -r line
SUPPLIED_ENV_FILE="$1" do
while IFS= read -r line
do
FLUTTER_RUN="$FLUTTER_RUN --dart-define $line" FLUTTER_RUN="$FLUTTER_RUN --dart-define $line"
done < "$SUPPLIED_ENV_FILE" done < "$SUPPLIED_ENV_FILE"
fi
echo "Running: $FLUTTER_RUN" echo "Running: $FLUTTER_RUN"
$FLUTTER_RUN $FLUTTER_RUN