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:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter> -->
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
@ -42,66 +42,67 @@
<!-- 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">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter android:label="@string/backup">
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter android:label="@string/backup">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="video/*"/>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter android:label="@string/backup">
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="video/*"/>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
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"
android:resource="@string/asset_statements"/>
android:resource="@string/asset_statements" />
<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"
android:value="true"/>
android:value="true" />
</application>
<!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->
<!-- https://developer.android.com/training/package-visibility/use-cases -->
<queries>
<intent>
<action android:name="android.intent.action.SENDTO"/>
<data android:scheme="mailto"/>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MANAGE_MEDIA"/>
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<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
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
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"/>
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.android.vending.BILLING"/>
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.android.vending.BILLING" />
</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">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -4,7 +4,9 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#000000</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
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:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
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">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#ffffff</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
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
- camera_avfoundation (0.0.1):
- Flutter
- connectivity (0.0.1):
- connectivity_plus (0.0.1):
- Flutter
- Reachability
- ReachabilitySwift
- device_info (0.0.1):
- Flutter
- Firebase/CoreOnly (10.3.0):
@ -136,7 +136,7 @@ PODS:
- Flutter
- FlutterMacOS
- PromisesObjC (2.1.1)
- Reachability (3.2)
- ReachabilitySwift (5.0.0)
- receive_sharing_intent (0.0.1):
- Flutter
- SDWebImage (5.15.2):
@ -178,7 +178,7 @@ PODS:
DEPENDENCIES:
- background_fetch (from `.symlinks/plugins/background_fetch/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`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
@ -231,7 +231,7 @@ SPEC REPOS:
- nanopb
- OrderedSet
- PromisesObjC
- Reachability
- ReachabilitySwift
- SDWebImage
- SDWebImageWebPCoder
- Sentry
@ -242,8 +242,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/background_fetch/ios"
camera_avfoundation:
:path: ".symlinks/plugins/camera_avfoundation/ios"
connectivity:
:path: ".symlinks/plugins/connectivity/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info:
:path: ".symlinks/plugins/device_info/ios"
firebase_core:
@ -320,7 +320,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
background_fetch: bd64e544b303ee4cd4cf2fe8cb2187b72aecf9ca
camera_avfoundation: 07c77549ea54ad95d8581be86617c094a46280d9
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
device_info: d7d233b645a32c40dfdc212de5cf646ca482f175
Firebase: f92fc551ead69c94168d36c2b26188263860acd9
firebase_core: f95c8bbe65213d406d592573ad42a12d64849cb8
@ -358,7 +358,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
SDWebImage: 8ab87d4b3e5cc4927bd47f78db6ceb0b94442577
SDWebImageWebPCoder: 4851414d9f8894e328e8b97c93ea4f4f4e4418ae

View file

@ -268,14 +268,14 @@
"${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework",
"${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.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}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework",
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
"${BUILT_PRODUCTS_DIR}/Toast/Toast.framework",
"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.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}/fk_user_agent/fk_user_agent.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}/background_fetch.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}/fk_user_agent.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.framework",

View file

@ -2,8 +2,7 @@
"images" : [
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "1x"
"idiom" : "universal"
},
{
"appearances" : [
@ -13,36 +12,7 @@
}
],
"filename" : "darkbackground.png",
"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"
"idiom" : "universal"
}
],
"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>
</scenes>
<resources>
<image name="LaunchImage" width="1024" height="1024"/>
<image name="LaunchImage" width="1152" height="1152"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>

View file

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

View file

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

View file

@ -1091,13 +1091,17 @@ class FilesDB {
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 count = Sqflite.firstIntValue(
await db.rawQuery(
'SELECT COUNT(distinct($columnUploadedFileID)) FROM $filesTable where '
'$columnMMdVisibility'
' = $visibility AND $columnOwnerID = $ownerID',
' = $visibility AND $columnOwnerID = $ownerID AND $columnCollectionID NOT IN (${hiddenCollections.join(', ')})',
),
);
return count ?? 0;

View file

@ -3,7 +3,9 @@ import "package:flutter/services.dart";
class UpperCaseTextFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
TextEditingValue oldValue,
TextEditingValue newValue,
) {
return TextEditingValue(
text: newValue.text.toUpperCase(),
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:flutter/foundation.dart';
import 'package:flutter/material.dart';
import "package:flutter/rendering.dart";
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photos/app.dart';
@ -54,6 +55,7 @@ const kFGTaskDeathTimeoutInMicroseconds = 5000000;
const kBackgroundLockLatency = Duration(seconds: 3);
void main() async {
debugRepaintRainbowEnabled = false;
WidgetsFlutterBinding.ensureInitialized();
await _runInForeground();
BackgroundFetch.registerHeadlessTask(_headlessTaskHandler);

View file

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

View file

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

View file

@ -1,8 +1,10 @@
import 'package:flutter/foundation.dart';
import 'package:logging/logging.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/memories_db.dart';
import "package:photos/events/files_updated_event.dart";
import 'package:photos/models/filters/important_items_filter.dart';
import 'package:photos/models/memory.dart';
import 'package:photos/services/collections_service.dart';
@ -34,6 +36,17 @@ class MemoriesService extends ChangeNotifier {
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() {

View file

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

View file

@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:connectivity/connectivity.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart';

View file

@ -48,6 +48,10 @@ class EnteColorScheme {
final Color warning800;
final Color caution500;
//golden colors
final Color golden700;
final Color golden500;
//other colors
final Color tabIcon;
final List<Color> avatarColors;
@ -86,6 +90,8 @@ class EnteColorScheme {
this.warning500 = _warning500,
this.warning400 = _warning400,
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 _golden700 = Color(0xFFFDB816);
const Color _golden500 = Color(0xFFFFC336);
const List<Color> avatarLight = [
Color.fromRGBO(118, 84, 154, 1),
Color.fromRGBO(223, 120, 97, 1),

View file

@ -86,6 +86,26 @@ class EnteTextTheme {
final TextStyle brandSmall;
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({
required this.h1,
required this.h1Bold,
@ -105,13 +125,42 @@ class EnteTextTheme {
required this.tinyBold,
required this.brandSmall,
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 darkTextTheme = _buildEnteTextStyle(textBaseDark);
EnteTextTheme lightTextTheme = _buildEnteTextStyle(
textBaseLight,
textMutedLight,
textFaintLight,
);
EnteTextTheme _buildEnteTextStyle(Color color) {
EnteTextTheme darkTextTheme = _buildEnteTextStyle(
textBaseDark,
textMutedDark,
textFaintDark,
);
EnteTextTheme _buildEnteTextStyle(
Color color,
Color textMuted,
Color textFaint,
) {
return EnteTextTheme(
h1: h1.copyWith(color: color),
h1Bold: h1.copyWith(color: color, fontWeight: _boldWeight),
@ -131,5 +180,21 @@ EnteTextTheme _buildEnteTextStyle(Color color) {
tinyBold: tiny.copyWith(color: color, fontWeight: _boldWeight),
brandSmall: brandStyleSmall.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 {
final dialog = createProgressDialog(context, "Restoring files...",
isDismissible: true);
final dialog = createProgressDialog(
context,
"Restoring files...",
isDismissible: true,
);
await dialog.show();
try {
await CollectionsService.instance

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/db/files_db.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/utils/navigation_util.dart';
@ -15,6 +16,8 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Set<int> hiddenCollectionId =
CollectionsService.instance.getHiddenCollections();
return OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).backgroundColor,
@ -43,9 +46,10 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
),
const Padding(padding: EdgeInsets.all(6)),
FutureBuilder<int>(
future: FilesDB.instance.fileCountWithVisibility(
future: FilesDB.instance.archivedFilesCount(
visibilityArchive,
Configuration.instance.getUserID()!,
hiddenCollectionId,
),
builder: (context, snapshot) {
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:photos/ente_theme_data.dart';
import 'package:photos/theme/colors.dart';
import "package:photos/theme/ente_theme.dart";
import 'package:photos/theme/text_style.dart';
import 'package:photos/ui/components/icon_button_widget.dart';
@ -8,6 +9,7 @@ import 'package:photos/ui/components/icon_button_widget.dart';
enum NotificationType {
warning,
banner,
goldenBanner,
}
class NotificationWidget extends StatelessWidget {
@ -30,7 +32,9 @@ class NotificationWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
Color backgroundColor = Colors.white;
final colorScheme = getEnteColorScheme(context);
LinearGradient? backgroundGradient;
Color? backgroundColor;
switch (type) {
case NotificationType.warning:
backgroundColor = warning500;
@ -38,6 +42,13 @@ class NotificationWidget extends StatelessWidget {
case NotificationType.banner:
backgroundColor = backgroundElevated2Dark;
break;
case NotificationType.goldenBanner:
backgroundGradient = LinearGradient(
colors: [colorScheme.golden700, colorScheme.golden500],
stops: const [0.25, 1],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
);
}
return Center(
child: GestureDetector(
@ -49,6 +60,7 @@ class NotificationWidget extends StatelessWidget {
),
boxShadow: Theme.of(context).colorScheme.enteTheme.shadowMenu,
color: backgroundColor,
gradient: backgroundGradient,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),

View file

@ -1,6 +1,8 @@
import "package:flutter/material.dart";
import "package:logging/logging.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/theme/ente_theme.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/title_bar_title_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";
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
State<ApplyCodeScreen> createState() => _ApplyCodeScreenState();
@ -112,14 +122,22 @@ class _ApplyCodeScreenState extends State<ApplyCodeScreen> {
await StorageBonusService.instance
.getGateway()
.claimReferralCode(code.trim().toUpperCase());
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => CodeSuccessScreen(
widget.referralView,
widget.userDetails,
),
),
);
} catch (e) {
Logger('$runtimeType')
.severe("failed to apply referral", e);
showErrorDialogForException(
context: context,
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:photos/models/api/storage_bonus/storage_bonus.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_widget.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/utils/data_util.dart";
import "package:photos/utils/navigation_util.dart";
@ -129,7 +129,8 @@ class ReferralWidget extends StatelessWidget {
? InkWell(
onTap: () {
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(
width: double.infinity,
@ -153,41 +154,7 @@ class ReferralWidget extends StatelessWidget {
"friends",
),
const SizedBox(height: 12),
Center(
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,
)
],
),
),
),
),
ReferralCodeWidget(referralView.code),
const SizedBox(height: 12),
const Text(
"2. They sign up for a paid plan",
@ -213,9 +180,11 @@ class ReferralWidget extends StatelessWidget {
color: colorScheme.strokeMuted,
),
const SizedBox(height: 12),
Text("Referrals are currently paused",
Text(
"Referrals are currently paused",
style: textStyle.small
.copyWith(color: colorScheme.textFaint)),
.copyWith(color: colorScheme.textFaint),
),
],
),
),
@ -247,7 +216,7 @@ class ReferralWidget extends StatelessWidget {
onTap: () async {
await routeToPage(
context,
const ApplyCodeScreen(),
ApplyCodeScreen(referralView, userDetails),
);
notifyParent();
},
@ -275,7 +244,10 @@ class ReferralWidget extends StatelessWidget {
routeToPage(
context,
const WebPage(
"FAQ", "https://ente.io/faq/general/referral-program"));
"FAQ",
"https://ente.io/faq/general/referral-program",
),
);
},
),
const SizedBox(height: 24),

View file

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

View file

@ -1,8 +1,11 @@
import "dart:io";
import "package:flutter/cupertino.dart";
import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/models/memory.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/thumbnail_widget.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() {
return Hero(
tag: widget.title,
@ -346,10 +365,38 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
Widget _buildBottomIcons() {
final file = widget.memories[_index].file;
return Container(
return SafeArea(
child: Container(
alignment: Alignment.bottomRight,
padding: const EdgeInsets.fromLTRB(0, 0, 26, 20),
child: IconButton(
padding: const EdgeInsets.fromLTRB(26, 0, 26, 20),
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(
Icons.adaptive.share,
color: Colors.white, //same for both themes
@ -358,6 +405,9 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
share(context, [file]);
},
),
],
),
),
);
}
@ -381,7 +431,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
Widget _buildSwiper() {
_pageController = PageController(initialPage: _index);
return ExtentsPageView.extents(
return PageView.builder(
itemBuilder: (BuildContext context, int index) {
if (index < widget.memories.length - 1) {
final nextFile = widget.memories[index + 1].file;
@ -405,7 +455,6 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
},
itemCount: widget.memories.length,
controller: _pageController,
extents: 1,
onPageChanged: (index) async {
await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]);
if (mounted) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -51,7 +51,7 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
late CollectionActions collectionActions;
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
// few files. This link is reset on any selection changed;
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],
_onEditFileRequested,
widget.config.mode == DetailPageMode.minimalistic,
onFileRemoved: _onFileRemoved,
userID: Configuration.instance.getUserID(),
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_type.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/trash_file.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/ui/collection_action_sheet.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/utils/delete_file_util.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/file_util.dart';
import "package:photos/utils/magic_util.dart";
import 'package:photos/utils/toast_util.dart';
class FadingAppBar extends StatefulWidget implements PreferredSizeWidget {
@ -148,22 +146,22 @@ class FadingAppBarState extends State<FadingAppBar> {
);
}
// options for files owned by the user
if (isOwnedByUser) {
if (isOwnedByUser && !isFileHidden) {
final bool isArchived =
widget.file.magicMetadata.visibility == visibilityArchive;
items.add(
PopupMenuItem(
value: 2,
child: Row(
children: [
Icon(
Platform.isAndroid
? Icons.delete_outline
: CupertinoIcons.delete,
isArchived ? Icons.unarchive : Icons.archive_outlined,
color: Theme.of(context).iconTheme.color,
),
const Padding(
padding: EdgeInsets.all(8),
),
const Text("Delete"),
Text(isArchived ? "Unarchive" : "Archive"),
],
),
),
@ -235,7 +233,7 @@ class FadingAppBarState extends State<FadingAppBar> {
if (value == 1) {
_download(widget.file);
} else if (value == 2) {
await _showSingleFileDeleteSheet(widget.file);
await _toggleFileArchiveStatus(widget.file);
} else if (value == 3) {
_setAs(widget.file);
} else if (value == 4) {
@ -337,109 +335,16 @@ class FadingAppBarState extends State<FadingAppBar> {
);
}
Future<void> _showSingleFileDeleteSheet(File file) 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();
widget.onFileRemoved(file);
}
},
),
Future<void> _toggleFileArchiveStatus(File file) async {
final bool isArchived =
widget.file.magicMetadata.visibility == visibilityArchive;
await changeVisibility(
context,
[widget.file],
isArchived ? visibilityVisible : visibilityArchive,
);
}
// 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();
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);
if (mounted) {
setState(() {});
}
}

View file

@ -2,31 +2,30 @@ import 'dart:io';
import 'package:flutter/cupertino.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_type.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/selected_files.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/ente_theme.dart';
import "package:photos/ui/actions/file/file_actions.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/magic_util.dart';
import 'package:photos/utils/share_util.dart';
class FadingBottomBar extends StatefulWidget {
final File file;
final Function(File) onEditRequested;
final Function(File) onFileRemoved;
final bool showOnlyInfoButton;
final int? userID;
const FadingBottomBar(
this.file,
this.onEditRequested,
this.showOnlyInfoButton, {
required this.onFileRemoved,
this.userID,
Key? key,
}) : super(key: key);
@ -63,6 +62,8 @@ class FadingBottomBarState extends State<FadingBottomBar> {
Widget _getBottomBar() {
final List<Widget> children = [];
final bool isOwnedByUser =
widget.file.ownerID == null || widget.file.ownerID == widget.userID;
children.add(
Tooltip(
message: "Info",
@ -88,15 +89,7 @@ class FadingBottomBarState extends State<FadingBottomBar> {
if (widget.file is TrashFile) {
_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.file.fileType == FileType.image ||
widget.file.fileType == FileType.livePhoto) {
@ -118,26 +111,21 @@ class FadingBottomBarState extends State<FadingBottomBar> {
),
);
}
if (isUploadedByUser && !isFileHidden) {
final bool isArchived =
widget.file.magicMetadata.visibility == visibilityArchive;
if (isOwnedByUser) {
children.add(
Tooltip(
message: isArchived ? "Unarchive" : "Archive",
message: "Delete",
child: Padding(
padding: const EdgeInsets.only(top: 12, bottom: 12),
child: IconButton(
icon: Icon(
isArchived ? Icons.unarchive : Icons.archive_outlined,
Platform.isAndroid
? Icons.delete_outline
: CupertinoIcons.delete,
color: Colors.white,
),
onPressed: () async {
await changeVisibility(
context,
[widget.file],
isArchived ? visibilityVisible : visibilityArchive,
);
safeRefresh();
await _showSingleFileDeleteSheet(widget.file);
},
),
),
@ -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) {
children.add(
Tooltip(
@ -272,20 +268,6 @@ class FadingBottomBarState extends State<FadingBottomBar> {
}
Future<void> _displayInfo(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),
);
},
);
await showInfoSheet(context, file);
}
}

View file

@ -1,17 +1,14 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/events/files_updated_event.dart';
import "package:photos/models/collection_items.dart";
import 'package:photos/models/gallery_type.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/services/collections_service.dart';
import "package:photos/ui/collections/collection_item_widget.dart";
import "package:photos/ui/common/loading_widget.dart";
import "package:photos/ui/components/album_horizontal_list_widget.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/gallery.dart';
@ -22,7 +19,6 @@ class ArchivePage extends StatelessWidget {
final GalleryType appBarType;
final GalleryType overlayType;
final _selectedFiles = SelectedFiles();
final Logger _logger = Logger("ArchivePage");
ArchivePage({
this.tagPrefix = "archived_page",
@ -70,43 +66,8 @@ class ArchivePage extends StatelessWidget {
emptyState: const EmptyState(
text: "You don't have any archived items.",
),
header: FutureBuilder(
future: 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();
}
},
header: AlbumHorizontalListWidget(
CollectionsService.instance.getArchivedCollectionWithThumb,
),
);
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_app_bar_widget.dart';
class CollectionPage extends StatefulWidget {
class CollectionPage extends StatelessWidget {
final CollectionWithThumbnail c;
final String tagPrefix;
final GalleryType appBarType;
final bool hasVerifiedLock;
const CollectionPage(
CollectionPage(
this.c, {
this.tagPrefix = "collection",
this.appBarType = GalleryType.ownedCollection,
@ -30,42 +30,24 @@ class CollectionPage extends StatefulWidget {
Key? key,
}) : super(key: key);
@override
State<CollectionPage> createState() => _CollectionPageState();
}
class _CollectionPageState extends State<CollectionPage> {
final _selectedFiles = SelectedFiles();
final GlobalKey shareButtonKey = GlobalKey();
final ValueNotifier<double> _bottomPosition = ValueNotifier(-150.0);
@override
void initState() {
_selectedFiles.addListener(_selectedFilesListener);
super.initState();
}
@override
void dispose() {
_selectedFiles.removeListener(_selectedFilesListener);
super.dispose();
}
@override
Widget build(Object context) {
if (widget.hasVerifiedLock == false && widget.c.collection.isHidden()) {
if (hasVerifiedLock == false && c.collection.isHidden()) {
return const EmptyState();
}
final appBarTypeValue = _getGalleryType(widget.c.collection);
final appBarTypeValue = _getGalleryType(c.collection);
final List<File>? initialFiles =
widget.c.thumbnail != null ? [widget.c.thumbnail!] : null;
c.thumbnail != null ? [c.thumbnail!] : null;
final gallery = Gallery(
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
final FileLoadResult result =
await FilesDB.instance.getFilesInCollection(
widget.c.collection.id,
c.collection.id,
creationStartTime,
creationEndTime,
limit: limit,
@ -82,25 +64,25 @@ class _CollectionPageState extends State<CollectionPage> {
},
reloadEvent: Bus.instance
.on<CollectionUpdatedEvent>()
.where((event) => event.collectionID == widget.c.collection.id),
.where((event) => event.collectionID == c.collection.id),
removalEventTypes: const {
EventType.deletedFromRemote,
EventType.deletedFromEverywhere,
EventType.hide,
},
tagPrefix: widget.tagPrefix,
tagPrefix: tagPrefix,
selectedFiles: _selectedFiles,
initialFiles: initialFiles,
albumName: widget.c.collection.name,
albumName: c.collection.name,
);
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(50.0),
child: GalleryAppBarWidget(
appBarTypeValue,
widget.c.collection.name,
c.collection.name,
_selectedFiles,
collection: widget.c.collection,
collection: c.collection,
),
),
body: Stack(
@ -110,7 +92,7 @@ class _CollectionPageState extends State<CollectionPage> {
FileSelectionOverlayBar(
appBarTypeValue,
_selectedFiles,
collection: widget.c.collection,
collection: c.collection,
)
],
),
@ -129,12 +111,6 @@ class _CollectionPageState extends State<CollectionPage> {
} else if (c.type == CollectionType.favorites) {
return GalleryType.favorite;
}
return widget.appBarType;
}
_selectedFilesListener() {
_selectedFiles.files.isNotEmpty
? _bottomPosition.value = 0.0
: _bottomPosition.value = -150.0;
return appBarType;
}
}

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/utils/delete_file_util.dart';
class TrashPage extends StatefulWidget {
class TrashPage extends StatelessWidget {
final String tagPrefix;
final GalleryType appBarType;
final GalleryType overlayType;
@ -26,30 +26,9 @@ class TrashPage extends StatefulWidget {
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
Widget build(Object context) {
final bool filesAreSelected = widget._selectedFiles.files.isNotEmpty;
final bool filesAreSelected = _selectedFiles.files.isNotEmpty;
final gallery = Gallery(
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
@ -70,8 +49,8 @@ class _TrashPageState extends State<TrashPage> {
forceReloadEvents: [
Bus.instance.on<ForceReloadTrashPageEvent>(),
],
tagPrefix: widget.tagPrefix,
selectedFiles: widget._selectedFiles,
tagPrefix: tagPrefix,
selectedFiles: _selectedFiles,
header: _headerWidget(),
initialFiles: null,
);
@ -80,9 +59,9 @@ class _TrashPageState extends State<TrashPage> {
appBar: PreferredSize(
preferredSize: const Size.fromHeight(50.0),
child: GalleryAppBarWidget(
widget.appBarType,
appBarType,
"Trash",
widget._selectedFiles,
_selectedFiles,
),
),
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/events/collection_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/models/file.dart';
import 'package:photos/models/selected_files.dart';
@ -264,6 +265,9 @@ Future<bool> deleteFromTrash(BuildContext context, List<File> files) async {
source: "deleteFromTrash",
),
);
//the FilesUpdateEvent is not reloading trash on premanently removing
//files, so need to fire ForceReloadTrashPageEvent
Bus.instance.fire(ForceReloadTrashPageEvent());
} catch (e, s) {
_logger.info("failed to delete from trash", e, s);
rethrow;

View file

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

View file

@ -6,7 +6,7 @@ import 'dart:math';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:connectivity/connectivity.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_sodium/flutter_sodium.dart';
@ -282,9 +282,11 @@ class FileUploader {
return;
}
final connectivityResult = await (Connectivity().checkConnectivity());
final canUploadUnderCurrentNetworkConditions =
(connectivityResult == ConnectivityResult.wifi ||
Configuration.instance.shouldBackupOverMobileData());
bool canUploadUnderCurrentNetworkConditions = true;
if (connectivityResult == ConnectivityResult.mobile) {
canUploadUnderCurrentNetworkConditions =
Configuration.instance.shouldBackupOverMobileData();
}
if (!canUploadUnderCurrentNetworkConditions) {
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"
source: hosted
version: "0.6.0"
connectivity:
connectivity_plus:
dependency: "direct main"
description:
name: connectivity
sha256: a8e91263cf3e25fb5cc95e19dfde4999e32a648ac3b9e8a558a28165731678f8
name: connectivity_plus
sha256: "8875e8ed511a49f030e313656154e4bbbcef18d68dfd32eb853fac10bce48e96"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
connectivity_for_web:
version: "3.0.3"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_for_web
sha256: "01a390c1d5adc2ed1fa1f52d120c07fe9fd01166a93f965a832fd6cfc0ea6482"
name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
url: "https://pub.dev"
source: hosted
version: "0.4.0+1"
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"
version: "1.2.4"
convert:
dependency: transitive
description:
@ -493,6 +477,14 @@ packages:
description: flutter
source: sdk
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:
dependency: transitive
description:
@ -1005,6 +997,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
node_preamble:
dependency: transitive
description:

View file

@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.7.26+426
version: 0.7.27+427
environment:
sdk: '>=2.17.0 <3.0.0'
@ -30,7 +30,7 @@ dependencies:
collection: # dart
computer: ^2.0.0
confetti: ^0.6.0
connectivity: ^3.0.3
connectivity_plus: ^3.0.3
cupertino_icons: ^1.0.0
device_info: ^2.0.2
dio: ^4.0.6
@ -49,6 +49,7 @@ dependencies:
fk_user_agent: ^2.0.1
flutter:
sdk: flutter
flutter_animate: ^4.1.0
flutter_cache_manager: ^3.3.0
flutter_datetime_picker: ^1.5.1
flutter_easyloading: ^3.0.0
@ -76,7 +77,7 @@ dependencies:
local_auth: ^1.1.5
logging: ^1.0.1
lottie: ^1.2.2
media_extension: ^1.0.0
media_extension: ^1.0.1
modal_bottom_sheet: ^3.0.0-pre
motionphoto:
git: "https://github.com/ente-io/motionphoto.git"
@ -147,6 +148,14 @@ flutter_native_splash:
android_fullscreen: true
android_gravity: 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
# following page: https://dart.dev/tools/pub/pubspec

12
run.sh
View file

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