diff --git a/README.md b/README.md
index 461e60cc94..6db1ee01de 100644
--- a/README.md
+++ b/README.md
@@ -32,8 +32,9 @@ Loading ~4000 images/videos
 ## Screenshots
 
 <p align="left">
-  <img src="design/nsc1.png" width="150" title="Login With Custom URL">
-  <img src="design/nsc2.png" width="150" title="Backup Setting Info">
+  <img src="design/login-screen.png" width="150" title="Login With Custom URL">
+  <img src="design/backup-screen.png" width="150" title="Backup Setting Info">
+  <img src="design/selective-backup-screen.png" width="150" title="Backup Setting Info">
   <img src="design/home-screen.jpeg" width="150" title="Home Screen">
   <img src="design/search-screen.jpeg" width="150" title="Curated Search Info">
   <img src="design/shared-albums.png" width="150" title="Shared Albums">
@@ -50,10 +51,10 @@ This project is under heavy development, there will be continous functions, feat
 # Features
 
 - Upload and view assets (videos/images).
+- Auto Backup.
 - Download asset to local device.
 - Multi-user supported.
 - Quick navigation with drag scroll bar.
-- Auto Backup.
 - Support HEIC/HEIF Backup.
 - Extract and display EXIF info.
 - Real-time render from multi-device upload event.
@@ -65,14 +66,20 @@ This project is under heavy development, there will be continous functions, feat
 - Show curated places on the search page
 - Show curated objects on the search page
 - Shared album with users on the same server
+- Selective backup - albums can be included and excluded during the backup process.
+
 
 # System Requirement
 
-**OS**: Preferred Linux-based operating system (Ubuntu, Debian, MacOS...etc). I haven't tested with `Docker for Windows` as well as `WSL` on Windows
+**OS**: Preferred Linux-based operating system (Ubuntu, Debian, MacOS...etc). 
+
+I haven't tested with `Docker for Windows` as well as `WSL` on Windows
+
+*Raspberry Pi can be used but `microservices` container has to be comment out in `docker-compose` since TensorFlow has not been supported in Dockec image on arm64v7 yet.*
 
 **RAM**: At least 2GB, preffered 4GB.
 
-**Cores**: At least 2 cores, preffered 4 cores.
+**Core**: At least 2 cores, preffered 4 cores.
 
 # Development and Testing out the application
 
diff --git a/design/backup-screen.png b/design/backup-screen.png
new file mode 100644
index 0000000000..d70669a9d6
Binary files /dev/null and b/design/backup-screen.png differ
diff --git a/design/login-screen.png b/design/login-screen.png
new file mode 100644
index 0000000000..a3687fb12a
Binary files /dev/null and b/design/login-screen.png differ
diff --git a/design/nsc1.png b/design/nsc1.png
deleted file mode 100644
index c4eaa711b5..0000000000
Binary files a/design/nsc1.png and /dev/null differ
diff --git a/design/nsc2.png b/design/nsc2.png
deleted file mode 100644
index bdcf015403..0000000000
Binary files a/design/nsc2.png and /dev/null differ
diff --git a/design/selective-backup-screen.png b/design/selective-backup-screen.png
new file mode 100644
index 0000000000..7b3d1ed047
Binary files /dev/null and b/design/selective-backup-screen.png differ
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index 12349fbb7f..08566b0017 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -2,7 +2,7 @@ version: "3.8"
 
 services:
   immich_server:
-    image: immich-server-dev:1.8.0
+    image: immich-server-dev:1.9.0
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -24,7 +24,7 @@ services:
       - immich_network
 
   immich_microservices:
-    image: immich-microservices-dev:1.8.0
+    image: immich-microservices-dev:1.9.0
     build:
       context: ../microservices
       dockerfile: Dockerfile
diff --git a/docker/docker-compose.gpu.yml b/docker/docker-compose.gpu.yml
index 1c289986fd..c2dde0c809 100644
--- a/docker/docker-compose.gpu.yml
+++ b/docker/docker-compose.gpu.yml
@@ -2,7 +2,7 @@ version: "3.8"
 
 services:
   immich_server:
-    image: immich-server-dev:1.8.0
+    image: immich-server-dev:1.9.0
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -22,7 +22,7 @@ services:
       - immich_network
 
   immich_microservices:
-    image: immich-microservices-dev:1.8.0
+    image: immich-microservices-dev:1.9.0
     build:
       context: ../microservices
       dockerfile: Dockerfile
diff --git a/mobile/android/fastlane/metadata/android/en-US/changelogs/13.txt b/mobile/android/fastlane/metadata/android/en-US/changelogs/13.txt
new file mode 100644
index 0000000000..d3ac68755c
--- /dev/null
+++ b/mobile/android/fastlane/metadata/android/en-US/changelogs/13.txt
@@ -0,0 +1,2 @@
+* New Feature - Selection backup. User can now select a combination of albums to be included or excluded during the backup process, and only unique photos, and videos that are not overlapping between the two groups will be backup.
+* Bug fix - Show correct count of backup and remainder assets.
\ No newline at end of file
diff --git a/mobile/android/metadata/en-US/images/phoneScreenshots/3_en-US.png b/mobile/android/metadata/en-US/images/phoneScreenshots/3_en-US.png
index d5ac2595a2..7b3d1ed047 100644
Binary files a/mobile/android/metadata/en-US/images/phoneScreenshots/3_en-US.png and b/mobile/android/metadata/en-US/images/phoneScreenshots/3_en-US.png differ
diff --git a/mobile/android/metadata/en-US/images/phoneScreenshots/4_en-US.png b/mobile/android/metadata/en-US/images/phoneScreenshots/4_en-US.png
index b313b8da51..d70669a9d6 100644
Binary files a/mobile/android/metadata/en-US/images/phoneScreenshots/4_en-US.png and b/mobile/android/metadata/en-US/images/phoneScreenshots/4_en-US.png differ
diff --git a/mobile/android/metadata/en-US/images/phoneScreenshots/5_en-US.png b/mobile/android/metadata/en-US/images/phoneScreenshots/5_en-US.png
index 81f620959d..b313b8da51 100644
Binary files a/mobile/android/metadata/en-US/images/phoneScreenshots/5_en-US.png and b/mobile/android/metadata/en-US/images/phoneScreenshots/5_en-US.png differ
diff --git a/mobile/android/metadata/en-US/images/phoneScreenshots/6_en-US.png b/mobile/android/metadata/en-US/images/phoneScreenshots/6_en-US.png
new file mode 100644
index 0000000000..81f620959d
Binary files /dev/null and b/mobile/android/metadata/en-US/images/phoneScreenshots/6_en-US.png differ
diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile
index 72888b6123..bb117c9dff 100644
--- a/mobile/ios/fastlane/Fastfile
+++ b/mobile/ios/fastlane/Fastfile
@@ -19,7 +19,7 @@ platform :ios do
   desc "iOS Beta"
   lane :beta do
     increment_version_number(
-      version_number: "1.8.0"
+      version_number: "1.9.0"
     )
     increment_build_number(
       build_number: latest_testflight_build_number + 1,
diff --git a/mobile/lib/constants/hive_box.dart b/mobile/lib/constants/hive_box.dart
index 4c000524a8..397680dd39 100644
--- a/mobile/lib/constants/hive_box.dart
+++ b/mobile/lib/constants/hive_box.dart
@@ -9,3 +9,7 @@ const String serverEndpointKey = 'immichBoxServerEndpoint';
 // Login Info
 const String hiveLoginInfoBox = "immichLoginInfoBox";
 const String savedLoginInfoKey = "immichSavedLoginInfoKey";
+
+// Backup Info
+const String hiveBackupInfoBox = "immichBackupAlbumInfoBox";
+const String backupInfoKey = "immichBackupAlbumInfoKey";
diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart
index 836571913b..69fdf359a5 100644
--- a/mobile/lib/main.dart
+++ b/mobile/lib/main.dart
@@ -3,12 +3,13 @@ import 'package:flutter/services.dart';
 import 'package:hive_flutter/hive_flutter.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/immich_colors.dart';
+import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
 import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
 import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/routing/tab_navigation_observer.dart';
 import 'package:immich_mobile/shared/providers/app_state.provider.dart';
-import 'package:immich_mobile/shared/providers/backup.provider.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 import 'package:immich_mobile/shared/providers/websocket.provider.dart';
 import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
@@ -16,9 +17,13 @@ import 'constants/hive_box.dart';
 
 void main() async {
   await Hive.initFlutter();
+
   Hive.registerAdapter(HiveSavedLoginInfoAdapter());
+  Hive.registerAdapter(HiveBackupAlbumsAdapter());
+
   await Hive.openBox(userInfoBox);
   await Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox);
+  await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
 
   SystemChrome.setSystemUIOverlayStyle(
     const SystemUiOverlayStyle(
diff --git a/mobile/lib/modules/backup/models/available_album.model.dart b/mobile/lib/modules/backup/models/available_album.model.dart
new file mode 100644
index 0000000000..d202efd19e
--- /dev/null
+++ b/mobile/lib/modules/backup/models/available_album.model.dart
@@ -0,0 +1,35 @@
+import 'dart:typed_data';
+
+import 'package:photo_manager/photo_manager.dart';
+
+class AvailableAlbum {
+  final AssetPathEntity albumEntity;
+  final Uint8List? thumbnailData;
+  AvailableAlbum({
+    required this.albumEntity,
+    this.thumbnailData,
+  });
+
+  AvailableAlbum copyWith({
+    AssetPathEntity? albumEntity,
+    Uint8List? thumbnailData,
+  }) {
+    return AvailableAlbum(
+      albumEntity: albumEntity ?? this.albumEntity,
+      thumbnailData: thumbnailData ?? this.thumbnailData,
+    );
+  }
+
+  @override
+  String toString() => 'AvailableAlbum(albumEntity: $albumEntity, thumbnailData: $thumbnailData)';
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+
+    return other is AvailableAlbum && other.albumEntity == albumEntity && other.thumbnailData == thumbnailData;
+  }
+
+  @override
+  int get hashCode => albumEntity.hashCode ^ thumbnailData.hashCode;
+}
diff --git a/mobile/lib/modules/backup/models/backup_state.model.dart b/mobile/lib/modules/backup/models/backup_state.model.dart
new file mode 100644
index 0000000000..a1bc20c01a
--- /dev/null
+++ b/mobile/lib/modules/backup/models/backup_state.model.dart
@@ -0,0 +1,88 @@
+import 'package:dio/dio.dart';
+import 'package:equatable/equatable.dart';
+import 'package:photo_manager/photo_manager.dart';
+
+import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
+import 'package:immich_mobile/shared/models/server_info.model.dart';
+
+enum BackUpProgressEnum { idle, inProgress, done }
+
+class BackUpState extends Equatable {
+  // enum
+  final BackUpProgressEnum backupProgress;
+  final List<String> allAssetOnDatabase;
+  final double progressInPercentage;
+  final CancelToken cancelToken;
+  final ServerInfo serverInfo;
+
+  /// All available albums on the device
+  final List<AvailableAlbum> availableAlbums;
+  final Set<AssetPathEntity> selectedBackupAlbums;
+  final Set<AssetPathEntity> excludedBackupAlbums;
+
+  /// Assets that are not overlapping in selected backup albums and excluded backup albums
+  final Set<AssetEntity> allUniqueAssets;
+
+  /// All assets from the selected albums that have been backup
+  final Set<String> selectedAlbumsBackupAssetsIds;
+
+  const BackUpState({
+    required this.backupProgress,
+    required this.allAssetOnDatabase,
+    required this.progressInPercentage,
+    required this.cancelToken,
+    required this.serverInfo,
+    required this.availableAlbums,
+    required this.selectedBackupAlbums,
+    required this.excludedBackupAlbums,
+    required this.allUniqueAssets,
+    required this.selectedAlbumsBackupAssetsIds,
+  });
+
+  BackUpState copyWith({
+    BackUpProgressEnum? backupProgress,
+    List<String>? allAssetOnDatabase,
+    double? progressInPercentage,
+    CancelToken? cancelToken,
+    ServerInfo? serverInfo,
+    List<AvailableAlbum>? availableAlbums,
+    Set<AssetPathEntity>? selectedBackupAlbums,
+    Set<AssetPathEntity>? excludedBackupAlbums,
+    Set<AssetEntity>? allUniqueAssets,
+    Set<String>? selectedAlbumsBackupAssetsIds,
+  }) {
+    return BackUpState(
+      backupProgress: backupProgress ?? this.backupProgress,
+      allAssetOnDatabase: allAssetOnDatabase ?? this.allAssetOnDatabase,
+      progressInPercentage: progressInPercentage ?? this.progressInPercentage,
+      cancelToken: cancelToken ?? this.cancelToken,
+      serverInfo: serverInfo ?? this.serverInfo,
+      availableAlbums: availableAlbums ?? this.availableAlbums,
+      selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
+      excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
+      allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
+      selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
+    );
+  }
+
+  @override
+  String toString() {
+    return 'BackUpState(backupProgress: $backupProgress, allAssetOnDatabase: $allAssetOnDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds)';
+  }
+
+  @override
+  List<Object> get props {
+    return [
+      backupProgress,
+      allAssetOnDatabase,
+      progressInPercentage,
+      cancelToken,
+      serverInfo,
+      availableAlbums,
+      selectedBackupAlbums,
+      excludedBackupAlbums,
+      allUniqueAssets,
+      selectedAlbumsBackupAssetsIds,
+    ];
+  }
+}
diff --git a/mobile/lib/modules/backup/models/hive_backup_albums.model.dart b/mobile/lib/modules/backup/models/hive_backup_albums.model.dart
new file mode 100644
index 0000000000..12ca3c1310
--- /dev/null
+++ b/mobile/lib/modules/backup/models/hive_backup_albums.model.dart
@@ -0,0 +1,66 @@
+import 'dart:convert';
+
+import 'package:collection/collection.dart';
+import 'package:hive/hive.dart';
+
+part 'hive_backup_albums.model.g.dart';
+
+@HiveType(typeId: 1)
+class HiveBackupAlbums {
+  @HiveField(0)
+  List<String> selectedAlbumIds;
+
+  @HiveField(1)
+  List<String> excludedAlbumsIds;
+
+  HiveBackupAlbums({
+    required this.selectedAlbumIds,
+    required this.excludedAlbumsIds,
+  });
+
+  @override
+  String toString() => 'HiveBackupAlbums(selectedAlbumIds: $selectedAlbumIds, excludedAlbumsIds: $excludedAlbumsIds)';
+
+  HiveBackupAlbums copyWith({
+    List<String>? selectedAlbumIds,
+    List<String>? excludedAlbumsIds,
+  }) {
+    return HiveBackupAlbums(
+      selectedAlbumIds: selectedAlbumIds ?? this.selectedAlbumIds,
+      excludedAlbumsIds: excludedAlbumsIds ?? this.excludedAlbumsIds,
+    );
+  }
+
+  Map<String, dynamic> toMap() {
+    final result = <String, dynamic>{};
+
+    result.addAll({'selectedAlbumIds': selectedAlbumIds});
+    result.addAll({'excludedAlbumsIds': excludedAlbumsIds});
+
+    return result;
+  }
+
+  factory HiveBackupAlbums.fromMap(Map<String, dynamic> map) {
+    return HiveBackupAlbums(
+      selectedAlbumIds: List<String>.from(map['selectedAlbumIds']),
+      excludedAlbumsIds: List<String>.from(map['excludedAlbumsIds']),
+    );
+  }
+
+  String toJson() => json.encode(toMap());
+
+  factory HiveBackupAlbums.fromJson(String source) => HiveBackupAlbums.fromMap(json.decode(source));
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    final listEquals = const DeepCollectionEquality().equals;
+
+    return other is HiveBackupAlbums &&
+        listEquals(other.selectedAlbumIds, selectedAlbumIds) &&
+        listEquals(other.excludedAlbumsIds, excludedAlbumsIds);
+  }
+
+  @override
+  int get hashCode => selectedAlbumIds.hashCode ^ excludedAlbumsIds.hashCode;
+}
diff --git a/mobile/lib/modules/backup/models/hive_backup_albums.model.g.dart b/mobile/lib/modules/backup/models/hive_backup_albums.model.g.dart
new file mode 100644
index 0000000000..d64ce4e7ba
--- /dev/null
+++ b/mobile/lib/modules/backup/models/hive_backup_albums.model.g.dart
@@ -0,0 +1,42 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'hive_backup_albums.model.dart';
+
+// **************************************************************************
+// TypeAdapterGenerator
+// **************************************************************************
+
+class HiveBackupAlbumsAdapter extends TypeAdapter<HiveBackupAlbums> {
+  @override
+  final int typeId = 1;
+
+  @override
+  HiveBackupAlbums read(BinaryReader reader) {
+    final numOfFields = reader.readByte();
+    final fields = <int, dynamic>{
+      for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
+    };
+    return HiveBackupAlbums(
+      selectedAlbumIds: (fields[0] as List).cast<String>(),
+      excludedAlbumsIds: (fields[1] as List).cast<String>(),
+    );
+  }
+
+  @override
+  void write(BinaryWriter writer, HiveBackupAlbums obj) {
+    writer
+      ..writeByte(2)
+      ..writeByte(0)
+      ..write(obj.selectedAlbumIds)
+      ..writeByte(1)
+      ..write(obj.excludedAlbumsIds);
+  }
+
+  @override
+  int get hashCode => typeId.hashCode;
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is HiveBackupAlbumsAdapter && runtimeType == other.runtimeType && typeId == other.typeId;
+}
diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart
new file mode 100644
index 0000000000..f71e248d73
--- /dev/null
+++ b/mobile/lib/modules/backup/providers/backup.provider.dart
@@ -0,0 +1,347 @@
+import 'package:dio/dio.dart';
+import 'package:flutter/foundation.dart';
+import 'package:hive_flutter/hive_flutter.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/constants/hive_box.dart';
+import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
+import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
+import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
+import 'package:immich_mobile/shared/services/server_info.service.dart';
+import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
+import 'package:immich_mobile/shared/models/server_info.model.dart';
+import 'package:immich_mobile/modules/backup/services/backup.service.dart';
+import 'package:photo_manager/photo_manager.dart';
+
+class BackupNotifier extends StateNotifier<BackUpState> {
+  BackupNotifier({this.ref})
+      : super(
+          BackUpState(
+            backupProgress: BackUpProgressEnum.idle,
+            allAssetOnDatabase: const [],
+            progressInPercentage: 0,
+            cancelToken: CancelToken(),
+            serverInfo: ServerInfo(
+              diskAvailable: "0",
+              diskAvailableRaw: 0,
+              diskSize: "0",
+              diskSizeRaw: 0,
+              diskUsagePercentage: 0.0,
+              diskUse: "0",
+              diskUseRaw: 0,
+            ),
+            availableAlbums: const [],
+            selectedBackupAlbums: const {},
+            excludedBackupAlbums: const {},
+            allUniqueAssets: const {},
+            selectedAlbumsBackupAssetsIds: const {},
+          ),
+        );
+
+  Ref? ref;
+  final BackupService _backupService = BackupService();
+  final ServerInfoService _serverInfoService = ServerInfoService();
+
+  ///
+  /// UI INTERACTION
+  ///
+  /// Album selection
+  /// Due to the overlapping assets across multiple albums on the device
+  /// We have method to include and exclude albums
+  /// The total unique assets will be used for backing mechanism
+  ///
+  void addAlbumForBackup(AssetPathEntity album) {
+    if (state.excludedBackupAlbums.contains(album)) {
+      removeExcludedAlbumForBackup(album);
+    }
+
+    state = state.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album});
+    _updateBackupAssetCount();
+  }
+
+  void addExcludedAlbumForBackup(AssetPathEntity album) {
+    if (state.selectedBackupAlbums.contains(album)) {
+      removeAlbumForBackup(album);
+    }
+    state = state.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album});
+    _updateBackupAssetCount();
+  }
+
+  void removeAlbumForBackup(AssetPathEntity album) {
+    Set<AssetPathEntity> currentSelectedAlbums = state.selectedBackupAlbums;
+
+    currentSelectedAlbums.removeWhere((a) => a == album);
+
+    state = state.copyWith(selectedBackupAlbums: currentSelectedAlbums);
+    _updateBackupAssetCount();
+  }
+
+  void removeExcludedAlbumForBackup(AssetPathEntity album) {
+    Set<AssetPathEntity> currentExcludedAlbums = state.excludedBackupAlbums;
+
+    currentExcludedAlbums.removeWhere((a) => a == album);
+
+    state = state.copyWith(excludedBackupAlbums: currentExcludedAlbums);
+    _updateBackupAssetCount();
+  }
+
+  ///
+  /// Get all album on the device
+  /// Get all selected and excluded album from the user's persistent storage
+  /// If this is the first time performing backup - set the default selected album to be
+  /// the one that has all assets (Recent on Android, Recents on iOS)
+  ///
+  Future<void> getBackupAlbumsInfo() async {
+    // Get all albums on the device
+    List<AvailableAlbum> availableAlbums = [];
+    List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(hasAll: true, type: RequestType.common);
+
+    for (AssetPathEntity album in albums) {
+      AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
+
+      var assetList = await album.getAssetListRange(start: 0, end: album.assetCount);
+
+      if (assetList.isNotEmpty) {
+        var thumbnailAsset = assetList.first;
+        var thumbnailData = await thumbnailAsset.thumbnailDataWithSize(const ThumbnailSize(512, 512));
+        availableAlbum = availableAlbum.copyWith(thumbnailData: thumbnailData);
+      }
+
+      availableAlbums.add(availableAlbum);
+    }
+
+    state = state.copyWith(availableAlbums: availableAlbums);
+
+    // Put persistent storage info into local state of the app
+    // Get local storage on selected backup album
+    Box<HiveBackupAlbums> backupAlbumInfoBox = Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
+    HiveBackupAlbums? backupAlbumInfo = backupAlbumInfoBox.get(
+      backupInfoKey,
+      defaultValue: HiveBackupAlbums(
+        selectedAlbumIds: [],
+        excludedAlbumsIds: [],
+      ),
+    );
+
+    if (backupAlbumInfo == null) {
+      debugPrint("[ERROR] getting Hive backup album infomation");
+      return;
+    }
+
+    // First time backup - set isAll album is the default one for backup.
+    if (backupAlbumInfo.selectedAlbumIds.isEmpty) {
+      debugPrint("First time backup setup recent album as default");
+
+      // Get album that contains all assets
+      var list = await PhotoManager.getAssetPathList(hasAll: true, onlyAll: true, type: RequestType.common);
+      AssetPathEntity albumHasAllAssets = list.first;
+
+      backupAlbumInfoBox.put(
+        backupInfoKey,
+        HiveBackupAlbums(
+          selectedAlbumIds: [albumHasAllAssets.id],
+          excludedAlbumsIds: [],
+        ),
+      );
+
+      backupAlbumInfo = backupAlbumInfoBox.get(backupInfoKey);
+    }
+
+    // Generate AssetPathEntity from id to add to local state
+    try {
+      for (var selectedAlbumId in backupAlbumInfo!.selectedAlbumIds) {
+        var albumAsset = await AssetPathEntity.fromId(selectedAlbumId);
+        state = state.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, albumAsset});
+      }
+
+      for (var excludedAlbumId in backupAlbumInfo.excludedAlbumsIds) {
+        var albumAsset = await AssetPathEntity.fromId(excludedAlbumId);
+        state = state.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, albumAsset});
+      }
+    } catch (e) {
+      debugPrint("[ERROR] Failed to generate album from id $e");
+    }
+  }
+
+  ///
+  /// From all the selected and albums assets
+  /// Find the assets that are not overlapping between the two sets
+  /// Those assets are unique and are used as the total assets
+  ///
+  void _updateBackupAssetCount() async {
+    Set<AssetEntity> assetsFromSelectedAlbums = {};
+    Set<AssetEntity> assetsFromExcludedAlbums = {};
+
+    for (var album in state.selectedBackupAlbums) {
+      var assets = await album.getAssetListRange(start: 0, end: album.assetCount);
+      assetsFromSelectedAlbums.addAll(assets);
+    }
+
+    for (var album in state.excludedBackupAlbums) {
+      var assets = await album.getAssetListRange(start: 0, end: album.assetCount);
+      assetsFromExcludedAlbums.addAll(assets);
+    }
+
+    Set<AssetEntity> allUniqueAssets = assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
+    List<String> allAssetOnDatabase = await _backupService.getDeviceBackupAsset();
+
+    // Find asset that were backup from selected albums
+    Set<String> selectedAlbumsBackupAssets = Set.from(allUniqueAssets.map((e) => e.id));
+    selectedAlbumsBackupAssets.removeWhere((assetId) => !allAssetOnDatabase.contains(assetId));
+
+    if (allUniqueAssets.isEmpty) {
+      debugPrint("No Asset On Device");
+      state = state.copyWith(
+        backupProgress: BackUpProgressEnum.idle,
+        allAssetOnDatabase: allAssetOnDatabase,
+        allUniqueAssets: {},
+        selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
+      );
+      return;
+    } else {
+      state = state.copyWith(
+        allAssetOnDatabase: allAssetOnDatabase,
+        allUniqueAssets: allUniqueAssets,
+        selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
+      );
+    }
+
+    // Save to persistent storage
+    _updatePersistentAlbumsSelection();
+  }
+
+  ///
+  /// Get all necessary information for calculating the available albums,
+  /// which albums are selected or excluded
+  /// and then update the UI according to those information
+  ///
+  void getBackupInfo() async {
+    await getBackupAlbumsInfo();
+    _updateServerInfo();
+    _updateBackupAssetCount();
+  }
+
+  ///
+  /// Save user selection of selected albums and excluded albums to
+  /// Hive database
+  ///
+  void _updatePersistentAlbumsSelection() {
+    Box<HiveBackupAlbums> backupAlbumInfoBox = Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
+    backupAlbumInfoBox.put(
+      backupInfoKey,
+      HiveBackupAlbums(
+        selectedAlbumIds: state.selectedBackupAlbums.map((e) => e.id).toList(),
+        excludedAlbumsIds: state.excludedBackupAlbums.map((e) => e.id).toList(),
+      ),
+    );
+  }
+
+  ///
+  /// Invoke backup process
+  ///
+  void startBackupProcess() async {
+    _updateServerInfo();
+    _updateBackupAssetCount();
+
+    state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
+
+    var authResult = await PhotoManager.requestPermissionExtend();
+    if (authResult.isAuth) {
+      await PhotoManager.clearFileCache();
+
+      if (state.allUniqueAssets.isEmpty) {
+        debugPrint("No Asset On Device - Abort Backup Process");
+        state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
+        return;
+      }
+
+      Set<AssetEntity> assetsWillBeBackup = state.allUniqueAssets;
+
+      // Remove item that has already been backed up
+      for (var assetId in state.allAssetOnDatabase) {
+        assetsWillBeBackup.removeWhere((e) => e.id == assetId);
+      }
+
+      if (assetsWillBeBackup.isEmpty) {
+        state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
+      }
+
+      // Perform Backup
+      state = state.copyWith(cancelToken: CancelToken());
+      _backupService.backupAsset(assetsWillBeBackup, state.cancelToken, _onAssetUploaded, _onUploadProgress);
+    } else {
+      PhotoManager.openSetting();
+    }
+  }
+
+  void cancelBackup() {
+    state.cancelToken.cancel('Cancel Backup');
+    state = state.copyWith(backupProgress: BackUpProgressEnum.idle, progressInPercentage: 0.0);
+  }
+
+  void _onAssetUploaded(String deviceAssetId, String deviceId) {
+    state = state.copyWith(
+        selectedAlbumsBackupAssetsIds: {...state.selectedAlbumsBackupAssetsIds, deviceAssetId},
+        allAssetOnDatabase: [...state.allAssetOnDatabase, deviceAssetId]);
+
+    if (state.allUniqueAssets.length - state.selectedAlbumsBackupAssetsIds.length == 0) {
+      state = state.copyWith(backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0);
+    }
+
+    _updateServerInfo();
+  }
+
+  void _onUploadProgress(int sent, int total) {
+    state = state.copyWith(progressInPercentage: (sent.toDouble() / total.toDouble() * 100));
+  }
+
+  void _updateServerInfo() async {
+    var serverInfo = await _serverInfoService.getServerInfo();
+
+    // Update server info
+    state = state.copyWith(
+      serverInfo: ServerInfo(
+        diskSize: serverInfo.diskSize,
+        diskUse: serverInfo.diskUse,
+        diskAvailable: serverInfo.diskAvailable,
+        diskSizeRaw: serverInfo.diskSizeRaw,
+        diskUseRaw: serverInfo.diskUseRaw,
+        diskAvailableRaw: serverInfo.diskAvailableRaw,
+        diskUsagePercentage: serverInfo.diskUsagePercentage,
+      ),
+    );
+  }
+
+  void resumeBackup() {
+    var authState = ref?.read(authenticationProvider);
+
+    // Check if user is login
+    var accessKey = Hive.box(userInfoBox).get(accessTokenKey);
+
+    // User has been logged out return
+    if (authState != null) {
+      if (accessKey == null || !authState.isAuthenticated) {
+        debugPrint("[resumeBackup] not authenticated - abort");
+        return;
+      }
+
+      // Check if this device is enable backup by the user
+      if ((authState.deviceInfo.deviceId == authState.deviceId) && authState.deviceInfo.isAutoBackup) {
+        // check if backup is alreayd in process - then return
+        if (state.backupProgress == BackUpProgressEnum.inProgress) {
+          debugPrint("[resumeBackup] Backup is already in progress - abort");
+          return;
+        }
+
+        // Run backup
+        debugPrint("[resumeBackup] Start back up");
+        startBackupProcess();
+      }
+
+      return;
+    }
+  }
+}
+
+final backupProvider = StateNotifierProvider<BackupNotifier, BackUpState>((ref) {
+  return BackupNotifier(ref: ref);
+});
diff --git a/mobile/lib/shared/services/backup.service.dart b/mobile/lib/modules/backup/services/backup.service.dart
similarity index 97%
rename from mobile/lib/shared/services/backup.service.dart
rename to mobile/lib/modules/backup/services/backup.service.dart
index 6337a7de01..9f3ace1f22 100644
--- a/mobile/lib/shared/services/backup.service.dart
+++ b/mobile/lib/modules/backup/services/backup.service.dart
@@ -26,7 +26,7 @@ class BackupService {
     return result.cast<String>();
   }
 
-  backupAsset(List<AssetEntity> assetList, CancelToken cancelToken, Function(String, String) singleAssetDoneCb,
+  backupAsset(Set<AssetEntity> assetList, CancelToken cancelToken, Function(String, String) singleAssetDoneCb,
       Function(int, int) uploadProgress) async {
     var dio = Dio();
     dio.interceptors.add(AuthenticatedRequestInterceptor());
diff --git a/mobile/lib/modules/backup/ui/album_info_card.dart b/mobile/lib/modules/backup/ui/album_info_card.dart
new file mode 100644
index 0000000000..f983f43e32
--- /dev/null
+++ b/mobile/lib/modules/backup/ui/album_info_card.dart
@@ -0,0 +1,185 @@
+import 'dart:typed_data';
+
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/shared/ui/immich_toast.dart';
+import 'package:photo_manager/photo_manager.dart';
+
+class AlbumInfoCard extends HookConsumerWidget {
+  final Uint8List? imageData;
+  final AssetPathEntity albumInfo;
+
+  const AlbumInfoCard({Key? key, this.imageData, required this.albumInfo}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final bool isSelected = ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo);
+    final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo);
+
+    ColorFilter selectedFilter = ColorFilter.mode(Theme.of(context).primaryColor.withAlpha(100), BlendMode.darken);
+    ColorFilter excludedFilter = ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
+    ColorFilter unselectedFilter = const ColorFilter.mode(Colors.black, BlendMode.color);
+
+    _buildSelectedTextBox() {
+      if (isSelected) {
+        return Chip(
+          visualDensity: VisualDensity.compact,
+          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+          label: const Text(
+            "INCLUDED",
+            style: TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
+          ),
+          backgroundColor: Theme.of(context).primaryColor,
+        );
+      } else if (isExcluded) {
+        return Chip(
+          visualDensity: VisualDensity.compact,
+          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+          label: const Text(
+            "EXCLUDED",
+            style: TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
+          ),
+          backgroundColor: Colors.red[300],
+        );
+      }
+
+      return Container();
+    }
+
+    _buildImageFilter() {
+      if (isSelected) {
+        return selectedFilter;
+      } else if (isExcluded) {
+        return excludedFilter;
+      } else {
+        return unselectedFilter;
+      }
+    }
+
+    return GestureDetector(
+      onTap: () {
+        HapticFeedback.selectionClick();
+
+        if (isSelected) {
+          if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
+            ImmichToast.show(
+              context: context,
+              msg: "Cannot remove the only album",
+              toastType: ToastType.error,
+              gravity: ToastGravity.BOTTOM,
+            );
+            return;
+          }
+
+          ref.watch(backupProvider.notifier).removeAlbumForBackup(albumInfo);
+        } else {
+          ref.watch(backupProvider.notifier).addAlbumForBackup(albumInfo);
+        }
+      },
+      onDoubleTap: () {
+        HapticFeedback.selectionClick();
+
+        if (isExcluded) {
+          ref.watch(backupProvider.notifier).removeExcludedAlbumForBackup(albumInfo);
+        } else {
+          if (ref.watch(backupProvider).selectedBackupAlbums.length == 1 &&
+              ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo)) {
+            ImmichToast.show(
+              context: context,
+              msg: "Cannot exclude the only album",
+              toastType: ToastType.error,
+              gravity: ToastGravity.BOTTOM,
+            );
+            return;
+          }
+
+          ref.watch(backupProvider.notifier).addExcludedAlbumForBackup(albumInfo);
+        }
+      },
+      child: Card(
+        margin: const EdgeInsets.all(1),
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(12), // if you need this
+          side: const BorderSide(
+            color: Color(0xFFC9C9C9),
+            width: 1,
+          ),
+        ),
+        elevation: 0,
+        borderOnForeground: false,
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Stack(
+              children: [
+                Container(
+                  width: 200,
+                  height: 200,
+                  decoration: BoxDecoration(
+                    borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
+                    image: DecorationImage(
+                      colorFilter: _buildImageFilter(),
+                      image: imageData != null
+                          ? MemoryImage(imageData!)
+                          : const AssetImage('assets/immich-logo-no-outline.png') as ImageProvider,
+                      fit: BoxFit.cover,
+                    ),
+                  ),
+                  child: null,
+                ),
+                Positioned(bottom: 10, left: 25, child: _buildSelectedTextBox())
+              ],
+            ),
+            Padding(
+              padding: const EdgeInsets.only(top: 8.0),
+              child: Row(
+                crossAxisAlignment: CrossAxisAlignment.center,
+                children: [
+                  SizedBox(
+                    width: 140,
+                    child: Padding(
+                      padding: const EdgeInsets.only(left: 25.0),
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: [
+                          Text(
+                            albumInfo.name,
+                            style: TextStyle(
+                                fontSize: 14, color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold),
+                          ),
+                          Padding(
+                            padding: const EdgeInsets.only(top: 2.0),
+                            child: Text(
+                              albumInfo.assetCount.toString() + (albumInfo.isAll ? " (ALL)" : ""),
+                              style: TextStyle(fontSize: 12, color: Colors.grey[600]),
+                            ),
+                          )
+                        ],
+                      ),
+                    ),
+                  ),
+                  IconButton(
+                    onPressed: () {
+                      AutoRouter.of(context).push(AlbumPreviewRoute(album: albumInfo));
+                    },
+                    icon: Icon(
+                      Icons.image_outlined,
+                      color: Theme.of(context).primaryColor,
+                      size: 24,
+                    ),
+                    splashRadius: 25,
+                  ),
+                ],
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/backup/ui/backup_info_card.dart b/mobile/lib/modules/backup/ui/backup_info_card.dart
new file mode 100644
index 0000000000..d6f52fd354
--- /dev/null
+++ b/mobile/lib/modules/backup/ui/backup_info_card.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+
+class BackupInfoCard extends StatelessWidget {
+  final String title;
+  final String subtitle;
+  final String info;
+  const BackupInfoCard({Key? key, required this.title, required this.subtitle, required this.info}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Card(
+      shape: RoundedRectangleBorder(
+        borderRadius: BorderRadius.circular(5), // if you need this
+        side: const BorderSide(
+          color: Colors.black12,
+          width: 1,
+        ),
+      ),
+      elevation: 0,
+      borderOnForeground: false,
+      child: ListTile(
+        minVerticalPadding: 15,
+        isThreeLine: true,
+        title: Text(
+          title,
+          style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
+        ),
+        subtitle: Padding(
+          padding: const EdgeInsets.only(top: 8.0),
+          child: Text(
+            subtitle,
+            style: const TextStyle(color: Color(0xFF808080), fontSize: 12),
+          ),
+        ),
+        trailing: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Text(
+              info,
+              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
+            ),
+            const Text("assets"),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/backup/views/album_preview_page.dart b/mobile/lib/modules/backup/views/album_preview_page.dart
new file mode 100644
index 0000000000..754afdb4de
--- /dev/null
+++ b/mobile/lib/modules/backup/views/album_preview_page.dart
@@ -0,0 +1,84 @@
+import 'dart:typed_data';
+
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+import 'package:photo_manager/photo_manager.dart';
+
+class AlbumPreviewPage extends HookConsumerWidget {
+  final AssetPathEntity album;
+  const AlbumPreviewPage({Key? key, required this.album}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final assets = useState<List<AssetEntity>>([]);
+
+    _getAssetsInAlbum() async {
+      assets.value = await album.getAssetListRange(start: 0, end: album.assetCount);
+    }
+
+    useEffect(() {
+      _getAssetsInAlbum();
+      return null;
+    }, []);
+
+    return Scaffold(
+      appBar: AppBar(
+        elevation: 0,
+        title: Column(
+          children: [
+            Text(
+              "${album.name} (${album.assetCount})",
+              style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+            ),
+            Padding(
+              padding: const EdgeInsets.only(top: 4.0),
+              child: Text(
+                "ID ${album.id}",
+                style: TextStyle(fontSize: 10, color: Colors.grey[600], fontWeight: FontWeight.bold),
+              ),
+            ),
+          ],
+        ),
+        leading: IconButton(
+          onPressed: () => AutoRouter.of(context).pop(),
+          icon: const Icon(Icons.arrow_back_ios_new_rounded),
+        ),
+      ),
+      body: GridView.builder(
+        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+          crossAxisCount: 5,
+          crossAxisSpacing: 2,
+          mainAxisSpacing: 2,
+        ),
+        itemCount: assets.value.length,
+        itemBuilder: (context, index) {
+          Future<Uint8List?> thumbData =
+              assets.value[index].thumbnailDataWithSize(const ThumbnailSize(200, 200), quality: 50);
+
+          return FutureBuilder<Uint8List?>(
+            future: thumbData,
+            builder: ((context, snapshot) {
+              if (snapshot.hasData && snapshot.data != null) {
+                return Image.memory(
+                  snapshot.data!,
+                  width: 100,
+                  height: 100,
+                  fit: BoxFit.cover,
+                );
+              }
+
+              return const SizedBox(
+                width: 100,
+                height: 100,
+                child: ImmichLoadingIndicator(),
+              );
+            }),
+          );
+        },
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/backup/views/backup_album_selection_page.dart b/mobile/lib/modules/backup/views/backup_album_selection_page.dart
new file mode 100644
index 0000000000..100c61057a
--- /dev/null
+++ b/mobile/lib/modules/backup/views/backup_album_selection_page.dart
@@ -0,0 +1,244 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
+import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
+import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+import 'package:immich_mobile/shared/ui/immich_toast.dart';
+
+class BackupAlbumSelectionPage extends HookConsumerWidget {
+  const BackupAlbumSelectionPage({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final availableAlbums = ref.watch(backupProvider).availableAlbums;
+    final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
+    final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
+
+    useEffect(() {
+      ref.read(backupProvider.notifier).getBackupAlbumsInfo();
+      return null;
+    }, []);
+
+    _buildAlbumSelectionList() {
+      if (availableAlbums.isEmpty) {
+        return const Center(
+          child: ImmichLoadingIndicator(),
+        );
+      }
+
+      return SizedBox(
+        height: 265,
+        child: ListView.builder(
+          scrollDirection: Axis.horizontal,
+          itemCount: availableAlbums.length,
+          physics: const BouncingScrollPhysics(),
+          itemBuilder: ((context, index) {
+            var thumbnailData = availableAlbums[index].thumbnailData;
+            return Padding(
+              padding: index == 0 ? const EdgeInsets.only(left: 16.00) : const EdgeInsets.all(0),
+              child: AlbumInfoCard(imageData: thumbnailData, albumInfo: availableAlbums[index].albumEntity),
+            );
+          }),
+        ),
+      );
+    }
+
+    _buildSelectedAlbumNameChip() {
+      return selectedBackupAlbums.map((album) {
+        void removeSelection() {
+          if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
+            ImmichToast.show(
+              context: context,
+              msg: "Cannot remove the only album",
+              toastType: ToastType.error,
+              gravity: ToastGravity.BOTTOM,
+            );
+            return;
+          }
+
+          ref.watch(backupProvider.notifier).removeAlbumForBackup(album);
+        }
+
+        return Padding(
+          padding: const EdgeInsets.only(right: 8.0),
+          child: GestureDetector(
+            onTap: removeSelection,
+            child: Chip(
+              visualDensity: VisualDensity.compact,
+              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+              label: Text(
+                album.name,
+                style: const TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
+              ),
+              backgroundColor: Theme.of(context).primaryColor,
+              deleteIconColor: Colors.white,
+              deleteIcon: const Icon(
+                Icons.cancel_rounded,
+                size: 15,
+              ),
+              onDeleted: removeSelection,
+            ),
+          ),
+        );
+      }).toSet();
+    }
+
+    _buildExcludedAlbumNameChip() {
+      return excludedBackupAlbums.map((album) {
+        void removeSelection() {
+          ref.watch(backupProvider.notifier).removeExcludedAlbumForBackup(album);
+        }
+
+        return GestureDetector(
+          onTap: removeSelection,
+          child: Padding(
+            padding: const EdgeInsets.only(right: 8.0),
+            child: Chip(
+              visualDensity: VisualDensity.compact,
+              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
+              label: Text(
+                album.name,
+                style: const TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
+              ),
+              backgroundColor: Colors.red[300],
+              deleteIconColor: Colors.white,
+              deleteIcon: const Icon(
+                Icons.cancel_rounded,
+                size: 15,
+              ),
+              onDeleted: removeSelection,
+            ),
+          ),
+        );
+      }).toSet();
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        leading: IconButton(
+          onPressed: () => AutoRouter.of(context).pop(),
+          icon: const Icon(Icons.arrow_back_ios_rounded),
+        ),
+        title: const Text(
+          "Select Albums",
+          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
+        ),
+        elevation: 0,
+      ),
+      body: ListView(
+        physics: const ClampingScrollPhysics(),
+        children: [
+          const Padding(
+            padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
+            child: Text(
+              "Selection Info",
+              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
+            ),
+          ),
+          // Selected Album Chips
+
+          Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 16.0),
+            child: Wrap(
+              children: [..._buildSelectedAlbumNameChip(), ..._buildExcludedAlbumNameChip()],
+            ),
+          ),
+
+          Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
+            child: Card(
+              margin: const EdgeInsets.all(0),
+              shape: RoundedRectangleBorder(
+                borderRadius: BorderRadius.circular(5), // if you need this
+                side: const BorderSide(
+                  color: Color.fromARGB(255, 235, 235, 235),
+                  width: 1,
+                ),
+              ),
+              elevation: 0,
+              borderOnForeground: false,
+              child: Column(
+                children: [
+                  ListTile(
+                    visualDensity: VisualDensity.compact,
+                    title: Text(
+                      "Total unique assets",
+                      style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.grey[700]),
+                    ),
+                    trailing: Text(
+                      ref.watch(backupProvider).allUniqueAssets.length.toString(),
+                      style: const TextStyle(fontWeight: FontWeight.bold),
+                    ),
+                  ),
+                ],
+              ),
+            ),
+          ),
+
+          ListTile(
+            title: Text(
+              "Albums on device (${availableAlbums.length.toString()})",
+              style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
+            ),
+            subtitle: Padding(
+              padding: const EdgeInsets.symmetric(vertical: 8.0),
+              child: Text(
+                "Tap to include, double tap to exclude",
+                style: TextStyle(
+                  fontSize: 12,
+                  color: Theme.of(context).primaryColor,
+                  fontWeight: FontWeight.bold,
+                ),
+              ),
+            ),
+            trailing: IconButton(
+              splashRadius: 16,
+              icon: Icon(
+                Icons.info,
+                size: 20,
+                color: Theme.of(context).primaryColor,
+              ),
+              onPressed: () {
+                // show the dialog
+                showDialog(
+                  context: context,
+                  builder: (BuildContext context) {
+                    return AlertDialog(
+                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
+                      elevation: 5,
+                      title: Text(
+                        'Selection Info',
+                        style: TextStyle(
+                          fontSize: 16,
+                          fontWeight: FontWeight.bold,
+                          color: Theme.of(context).primaryColor,
+                        ),
+                      ),
+                      content: SingleChildScrollView(
+                        child: ListBody(
+                          children: [
+                            Text(
+                              'Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.',
+                              style: TextStyle(fontSize: 14, color: Colors.grey[700]),
+                            ),
+                          ],
+                        ),
+                      ),
+                    );
+                  },
+                );
+              },
+            ),
+          ),
+
+          Padding(
+            padding: const EdgeInsets.only(bottom: 16.0),
+            child: _buildAlbumSelectionList(),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/shared/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart
similarity index 56%
rename from mobile/lib/shared/views/backup_controller_page.dart
rename to mobile/lib/modules/backup/views/backup_controller_page.dart
index 5962bab575..d195b81aa5 100644
--- a/mobile/lib/shared/views/backup_controller_page.dart
+++ b/mobile/lib/modules/backup/views/backup_controller_page.dart
@@ -3,10 +3,12 @@ import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
-import 'package:immich_mobile/shared/models/backup_state.model.dart';
+import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
-import 'package:immich_mobile/shared/providers/backup.provider.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/shared/providers/websocket.provider.dart';
+import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
 import 'package:percent_indicator/linear_percent_indicator.dart';
 
 class BackupControllerPage extends HookConsumerWidget {
@@ -14,13 +16,13 @@ class BackupControllerPage extends HookConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    BackUpState _backupState = ref.watch(backupProvider);
+    BackUpState backupState = ref.watch(backupProvider);
     AuthenticationState _authenticationState = ref.watch(authenticationProvider);
-
-    bool shouldBackup = _backupState.totalAssetCount - _backupState.assetOnDatabase == 0 ? false : true;
+    bool shouldBackup =
+        backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length == 0 ? false : true;
 
     useEffect(() {
-      if (_backupState.backupProgress != BackUpProgressEnum.inProgress) {
+      if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
         ref.read(backupProvider.notifier).getBackupInfo();
       }
 
@@ -46,13 +48,13 @@ class BackupControllerPage extends HookConsumerWidget {
               LinearPercentIndicator(
                 padding: const EdgeInsets.only(top: 8.0),
                 lineHeight: 5.0,
-                percent: _backupState.serverInfo.diskUsagePercentage / 100.0,
+                percent: backupState.serverInfo.diskUsagePercentage / 100.0,
                 backgroundColor: Colors.grey,
                 progressColor: Theme.of(context).primaryColor,
               ),
               Padding(
                 padding: const EdgeInsets.only(top: 12.0),
-                child: Text('${_backupState.serverInfo.diskUse} of ${_backupState.serverInfo.diskSize} used'),
+                child: Text('${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'),
               ),
             ],
           ),
@@ -104,18 +106,120 @@ class BackupControllerPage extends HookConsumerWidget {
       );
     }
 
+    Widget _buildSelectedAlbumName() {
+      var text = "Selected: ";
+      var albums = ref.watch(backupProvider).selectedBackupAlbums;
+
+      if (albums.isNotEmpty) {
+        for (var album in albums) {
+          if (album.name == "Recent" || album.name == "Recents") {
+            text += "${album.name} (All), ";
+          } else {
+            text += "${album.name}, ";
+          }
+        }
+
+        return Padding(
+          padding: const EdgeInsets.only(top: 8.0),
+          child: Text(
+            text.trim().substring(0, text.length - 2),
+            style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.bold),
+          ),
+        );
+      } else {
+        return Padding(
+          padding: const EdgeInsets.only(top: 8.0),
+          child: Text(
+            "None selected",
+            style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.bold),
+          ),
+        );
+      }
+    }
+
+    Widget _buildExcludedAlbumName() {
+      var text = "Excluded: ";
+      var albums = ref.watch(backupProvider).excludedBackupAlbums;
+
+      if (albums.isNotEmpty) {
+        for (var album in albums) {
+          text += "${album.name}, ";
+        }
+
+        return Padding(
+          padding: const EdgeInsets.only(top: 8.0),
+          child: Text(
+            text.trim().substring(0, text.length - 2),
+            style: TextStyle(color: Colors.red[300], fontSize: 12, fontWeight: FontWeight.bold),
+          ),
+        );
+      } else {
+        return Container();
+      }
+    }
+
+    _buildFolderSelectionTile() {
+      return Card(
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(5), // if you need this
+          side: const BorderSide(
+            color: Colors.black12,
+            width: 1,
+          ),
+        ),
+        elevation: 0,
+        borderOnForeground: false,
+        child: ListTile(
+          minVerticalPadding: 15,
+          title: const Text("Backup Albums", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
+          subtitle: Padding(
+            padding: const EdgeInsets.only(top: 8.0),
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                const Text(
+                  "Albums to be backup",
+                  style: TextStyle(color: Color(0xFF808080), fontSize: 12),
+                ),
+                _buildSelectedAlbumName(),
+                _buildExcludedAlbumName()
+              ],
+            ),
+          ),
+          trailing: OutlinedButton(
+            onPressed: () {
+              AutoRouter.of(context).push(const BackupAlbumSelectionRoute());
+            },
+            child: const Padding(
+              padding: EdgeInsets.symmetric(
+                vertical: 16.0,
+              ),
+              child: Text(
+                "Select",
+                style: TextStyle(fontWeight: FontWeight.bold),
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
     return Scaffold(
       appBar: AppBar(
+        elevation: 0,
         title: const Text(
           "Backup",
-          style: TextStyle(fontWeight: FontWeight.bold),
+          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
         ),
         leading: IconButton(
             onPressed: () {
               ref.watch(websocketProvider.notifier).listenUploadEvent();
               AutoRouter.of(context).pop(true);
             },
-            icon: const Icon(Icons.arrow_back_ios_rounded)),
+            splashRadius: 24,
+            icon: const Icon(
+              Icons.arrow_back_ios_rounded,
+            )),
       ),
       body: Padding(
         padding: const EdgeInsets.all(16.0),
@@ -129,20 +233,21 @@ class BackupControllerPage extends HookConsumerWidget {
                 style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
               ),
             ),
+            _buildFolderSelectionTile(),
             BackupInfoCard(
               title: "Total",
-              subtitle: "All images and videos on the device",
-              info: "${_backupState.totalAssetCount}",
+              subtitle: "All unique photos and videos from selected albums",
+              info: "${backupState.allUniqueAssets.length}",
             ),
             BackupInfoCard(
               title: "Backup",
-              subtitle: "Images and videos of the device that are backup on server",
-              info: "${_backupState.assetOnDatabase}",
+              subtitle: "Photos and videos from selected albums that are backup",
+              info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
             ),
             BackupInfoCard(
               title: "Remainder",
-              subtitle: "Images and videos that has not been backing up",
-              info: "${_backupState.totalAssetCount - _backupState.assetOnDatabase}",
+              subtitle: "Photos and videos that has not been backing up from selected albums",
+              info: "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
             ),
             const Divider(),
             _buildBackupController(),
@@ -152,14 +257,14 @@ class BackupControllerPage extends HookConsumerWidget {
             Padding(
               padding: const EdgeInsets.all(8.0),
               child: Text(
-                  "Asset that were being backup: ${_backupState.backingUpAssetCount} [${_backupState.progressInPercentage.toStringAsFixed(0)}%]"),
+                  "Asset that were being backup: ${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length} [${backupState.progressInPercentage.toStringAsFixed(0)}%]"),
             ),
             Padding(
               padding: const EdgeInsets.only(left: 8.0),
               child: Row(children: [
                 const Text("Backup Progress:"),
                 const Padding(padding: EdgeInsets.symmetric(horizontal: 2)),
-                _backupState.backupProgress == BackUpProgressEnum.inProgress
+                backupState.backupProgress == BackUpProgressEnum.inProgress
                     ? const CircularProgressIndicator.adaptive()
                     : const Text("Done"),
               ]),
@@ -167,7 +272,7 @@ class BackupControllerPage extends HookConsumerWidget {
             Padding(
               padding: const EdgeInsets.all(8.0),
               child: Container(
-                child: _backupState.backupProgress == BackUpProgressEnum.inProgress
+                child: backupState.backupProgress == BackUpProgressEnum.inProgress
                     ? ElevatedButton(
                         style: ElevatedButton.styleFrom(primary: Colors.red[300]),
                         onPressed: () {
@@ -191,50 +296,3 @@ class BackupControllerPage extends HookConsumerWidget {
     );
   }
 }
-
-class BackupInfoCard extends StatelessWidget {
-  final String title;
-  final String subtitle;
-  final String info;
-  const BackupInfoCard({Key? key, required this.title, required this.subtitle, required this.info}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Card(
-      shape: RoundedRectangleBorder(
-        borderRadius: BorderRadius.circular(5), // if you need this
-        side: const BorderSide(
-          color: Colors.black12,
-          width: 1,
-        ),
-      ),
-      elevation: 0,
-      borderOnForeground: false,
-      child: ListTile(
-        minVerticalPadding: 15,
-        isThreeLine: true,
-        title: Text(
-          title,
-          style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
-        ),
-        subtitle: Padding(
-          padding: const EdgeInsets.only(top: 8.0),
-          child: Text(
-            subtitle,
-            style: const TextStyle(color: Color(0xFF808080), fontSize: 12),
-          ),
-        ),
-        trailing: Column(
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: [
-            Text(
-              info,
-              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
-            ),
-            const Text("assets"),
-          ],
-        ),
-      ),
-    );
-  }
-}
diff --git a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
index ebc9d6d718..09584b2b7d 100644
--- a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
+++ b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
@@ -5,9 +5,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 
 import 'package:immich_mobile/routing/router.dart';
-import 'package:immich_mobile/shared/models/backup_state.model.dart';
+import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
 import 'package:immich_mobile/shared/models/server_info_state.model.dart';
-import 'package:immich_mobile/shared/providers/backup.provider.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 
 class ImmichSliverAppBar extends ConsumerWidget {
@@ -130,7 +130,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
                 ? Positioned(
                     bottom: 5,
                     child: Text(
-                      _backupState.backingUpAssetCount.toString(),
+                      (_backupState.allUniqueAssets.length - _backupState.selectedAlbumsBackupAssetsIds.length)
+                          .toString(),
                       style: const TextStyle(fontSize: 9, fontWeight: FontWeight.bold),
                     ),
                   )
diff --git a/mobile/lib/modules/home/ui/profile_drawer.dart b/mobile/lib/modules/home/ui/profile_drawer.dart
index cafab9e379..73af9d73aa 100644
--- a/mobile/lib/modules/home/ui/profile_drawer.dart
+++ b/mobile/lib/modules/home/ui/profile_drawer.dart
@@ -6,7 +6,7 @@ import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 import 'package:immich_mobile/shared/models/server_info_state.model.dart';
-import 'package:immich_mobile/shared/providers/backup.provider.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 import 'package:immich_mobile/shared/providers/websocket.provider.dart';
 import 'package:package_info_plus/package_info_plus.dart';
diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart
index b7c22cae35..9882a8659c 100644
--- a/mobile/lib/modules/login/providers/authentication.provider.dart
+++ b/mobile/lib/modules/login/providers/authentication.provider.dart
@@ -6,7 +6,7 @@ import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
 import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
 import 'package:immich_mobile/modules/login/models/login_response.model.dart';
-import 'package:immich_mobile/shared/services/backup.service.dart';
+import 'package:immich_mobile/modules/backup/services/backup.service.dart';
 import 'package:immich_mobile/shared/services/device_info.service.dart';
 import 'package:immich_mobile/shared/services/network.service.dart';
 import 'package:immich_mobile/shared/models/device_info.model.dart';
diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart
index bb3dde2b77..851338b38d 100644
--- a/mobile/lib/modules/login/ui/login_form.dart
+++ b/mobile/lib/modules/login/ui/login_form.dart
@@ -7,7 +7,7 @@ import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
 import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
-import 'package:immich_mobile/shared/providers/backup.provider.dart';
+import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 import 'package:immich_mobile/shared/ui/immich_toast.dart';
 
 class LoginForm extends HookConsumerWidget {
diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart
index a25d45c7bd..27a82e43f9 100644
--- a/mobile/lib/routing/router.dart
+++ b/mobile/lib/routing/router.dart
@@ -1,5 +1,7 @@
 import 'package:auto_route/auto_route.dart';
 import 'package:flutter/material.dart';
+import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
+import 'package:immich_mobile/modules/backup/views/backup_album_selection_page.dart';
 import 'package:immich_mobile/modules/login/views/login_page.dart';
 import 'package:immich_mobile/modules/home/views/home_page.dart';
 import 'package:immich_mobile/modules/search/views/search_page.dart';
@@ -14,10 +16,11 @@ import 'package:immich_mobile/modules/sharing/views/select_user_for_sharing_page
 import 'package:immich_mobile/modules/sharing/views/sharing_page.dart';
 import 'package:immich_mobile/routing/auth_guard.dart';
 import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:immich_mobile/shared/views/backup_controller_page.dart';
+import 'package:immich_mobile/modules/backup/views/backup_controller_page.dart';
 import 'package:immich_mobile/modules/asset_viewer/views/image_viewer_page.dart';
 import 'package:immich_mobile/shared/views/tab_controller_page.dart';
 import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
+import 'package:photo_manager/photo_manager.dart';
 
 part 'router.gr.dart';
 
@@ -55,6 +58,8 @@ part 'router.gr.dart';
       guards: [AuthGuard],
       transitionsBuilder: TransitionsBuilders.slideBottom,
     ),
+    AutoRoute(page: BackupAlbumSelectionPage, guards: [AuthGuard]),
+    AutoRoute(page: AlbumPreviewPage, guards: [AuthGuard]),
   ],
 )
 class AppRouter extends _$AppRouter {
diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart
index 0d4f76074d..d59e33470a 100644
--- a/mobile/lib/routing/router.gr.dart
+++ b/mobile/lib/routing/router.gr.dart
@@ -93,6 +93,16 @@ class _$AppRouter extends RootStackRouter {
           opaque: true,
           barrierDismissible: false);
     },
+    BackupAlbumSelectionRoute.name: (routeData) {
+      return MaterialPageX<dynamic>(
+          routeData: routeData, child: const BackupAlbumSelectionPage());
+    },
+    AlbumPreviewRoute.name: (routeData) {
+      final args = routeData.argsAs<AlbumPreviewRouteArgs>();
+      return MaterialPageX<dynamic>(
+          routeData: routeData,
+          child: AlbumPreviewPage(key: args.key, album: args.album));
+    },
     HomeRoute.name: (routeData) {
       return MaterialPageX<dynamic>(
           routeData: routeData, child: const HomePage());
@@ -149,7 +159,11 @@ class _$AppRouter extends RootStackRouter {
             path: '/album-viewer-page', guards: [authGuard]),
         RouteConfig(SelectAdditionalUserForSharingRoute.name,
             path: '/select-additional-user-for-sharing-page',
-            guards: [authGuard])
+            guards: [authGuard]),
+        RouteConfig(BackupAlbumSelectionRoute.name,
+            path: '/backup-album-selection-page', guards: [authGuard]),
+        RouteConfig(AlbumPreviewRoute.name,
+            path: '/album-preview-page', guards: [authGuard])
       ];
 }
 
@@ -358,6 +372,40 @@ class SelectAdditionalUserForSharingRouteArgs {
   }
 }
 
+/// generated route for
+/// [BackupAlbumSelectionPage]
+class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
+  const BackupAlbumSelectionRoute()
+      : super(BackupAlbumSelectionRoute.name,
+            path: '/backup-album-selection-page');
+
+  static const String name = 'BackupAlbumSelectionRoute';
+}
+
+/// generated route for
+/// [AlbumPreviewPage]
+class AlbumPreviewRoute extends PageRouteInfo<AlbumPreviewRouteArgs> {
+  AlbumPreviewRoute({Key? key, required AssetPathEntity album})
+      : super(AlbumPreviewRoute.name,
+            path: '/album-preview-page',
+            args: AlbumPreviewRouteArgs(key: key, album: album));
+
+  static const String name = 'AlbumPreviewRoute';
+}
+
+class AlbumPreviewRouteArgs {
+  const AlbumPreviewRouteArgs({this.key, required this.album});
+
+  final Key? key;
+
+  final AssetPathEntity album;
+
+  @override
+  String toString() {
+    return 'AlbumPreviewRouteArgs{key: $key, album: $album}';
+  }
+}
+
 /// generated route for
 /// [HomePage]
 class HomeRoute extends PageRouteInfo<void> {
diff --git a/mobile/lib/shared/models/backup_state.model.dart b/mobile/lib/shared/models/backup_state.model.dart
deleted file mode 100644
index db78327376..0000000000
--- a/mobile/lib/shared/models/backup_state.model.dart
+++ /dev/null
@@ -1,77 +0,0 @@
-import 'dart:convert';
-
-import 'package:dio/dio.dart';
-
-import 'package:immich_mobile/shared/models/server_info.model.dart';
-
-enum BackUpProgressEnum { idle, inProgress, done }
-
-class BackUpState {
-  final BackUpProgressEnum backupProgress;
-  final int totalAssetCount;
-  final int assetOnDatabase;
-  final int backingUpAssetCount;
-  final double progressInPercentage;
-  final CancelToken cancelToken;
-  final ServerInfo serverInfo;
-
-  BackUpState({
-    required this.backupProgress,
-    required this.totalAssetCount,
-    required this.assetOnDatabase,
-    required this.backingUpAssetCount,
-    required this.progressInPercentage,
-    required this.cancelToken,
-    required this.serverInfo,
-  });
-
-  BackUpState copyWith({
-    BackUpProgressEnum? backupProgress,
-    int? totalAssetCount,
-    int? assetOnDatabase,
-    int? backingUpAssetCount,
-    double? progressInPercentage,
-    CancelToken? cancelToken,
-    ServerInfo? serverInfo,
-  }) {
-    return BackUpState(
-      backupProgress: backupProgress ?? this.backupProgress,
-      totalAssetCount: totalAssetCount ?? this.totalAssetCount,
-      assetOnDatabase: assetOnDatabase ?? this.assetOnDatabase,
-      backingUpAssetCount: backingUpAssetCount ?? this.backingUpAssetCount,
-      progressInPercentage: progressInPercentage ?? this.progressInPercentage,
-      cancelToken: cancelToken ?? this.cancelToken,
-      serverInfo: serverInfo ?? this.serverInfo,
-    );
-  }
-
-  @override
-  String toString() {
-    return 'BackUpState(backupProgress: $backupProgress, totalAssetCount: $totalAssetCount, assetOnDatabase: $assetOnDatabase, backingUpAssetCount: $backingUpAssetCount, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is BackUpState &&
-        other.backupProgress == backupProgress &&
-        other.totalAssetCount == totalAssetCount &&
-        other.assetOnDatabase == assetOnDatabase &&
-        other.backingUpAssetCount == backingUpAssetCount &&
-        other.progressInPercentage == progressInPercentage &&
-        other.cancelToken == cancelToken &&
-        other.serverInfo == serverInfo;
-  }
-
-  @override
-  int get hashCode {
-    return backupProgress.hashCode ^
-        totalAssetCount.hashCode ^
-        assetOnDatabase.hashCode ^
-        backingUpAssetCount.hashCode ^
-        progressInPercentage.hashCode ^
-        cancelToken.hashCode ^
-        serverInfo.hashCode;
-  }
-}
diff --git a/mobile/lib/shared/providers/backup.provider.dart b/mobile/lib/shared/providers/backup.provider.dart
deleted file mode 100644
index d94a00a168..0000000000
--- a/mobile/lib/shared/providers/backup.provider.dart
+++ /dev/null
@@ -1,194 +0,0 @@
-import 'dart:async';
-
-import 'package:dio/dio.dart';
-import 'package:flutter/foundation.dart';
-import 'package:hive_flutter/hive_flutter.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
-import 'package:immich_mobile/shared/services/server_info.service.dart';
-import 'package:immich_mobile/shared/models/backup_state.model.dart';
-import 'package:immich_mobile/shared/models/server_info.model.dart';
-import 'package:immich_mobile/shared/services/backup.service.dart';
-import 'package:photo_manager/photo_manager.dart';
-
-class BackupNotifier extends StateNotifier<BackUpState> {
-  BackupNotifier({this.ref})
-      : super(
-          BackUpState(
-            backupProgress: BackUpProgressEnum.idle,
-            backingUpAssetCount: 0,
-            assetOnDatabase: 0,
-            totalAssetCount: 0,
-            progressInPercentage: 0,
-            cancelToken: CancelToken(),
-            serverInfo: ServerInfo(
-              diskAvailable: "0",
-              diskAvailableRaw: 0,
-              diskSize: "0",
-              diskSizeRaw: 0,
-              diskUsagePercentage: 0.0,
-              diskUse: "0",
-              diskUseRaw: 0,
-            ),
-          ),
-        );
-
-  Ref? ref;
-  final BackupService _backupService = BackupService();
-  final ServerInfoService _serverInfoService = ServerInfoService();
-  final StreamController _onAssetBackupStreamCtrl =
-      StreamController.broadcast();
-
-  void getBackupInfo() async {
-    _updateServerInfo();
-
-    List<AssetPathEntity> list = await PhotoManager.getAssetPathList(
-        onlyAll: true, type: RequestType.common);
-    List<String> didBackupAsset = await _backupService.getDeviceBackupAsset();
-
-    if (list.isEmpty) {
-      debugPrint("No Asset On Device");
-      state = state.copyWith(
-          backupProgress: BackUpProgressEnum.idle,
-          totalAssetCount: 0,
-          assetOnDatabase: didBackupAsset.length);
-      return;
-    }
-
-    int totalAsset = list[0].assetCount;
-
-    state = state.copyWith(
-        totalAssetCount: totalAsset, assetOnDatabase: didBackupAsset.length);
-  }
-
-  void startBackupProcess() async {
-    _updateServerInfo();
-
-    state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
-
-    var authResult = await PhotoManager.requestPermissionExtend();
-    if (authResult.isAuth) {
-      await PhotoManager.clearFileCache();
-      // await PhotoManager.presentLimited();
-      // Gather assets info
-      List<AssetPathEntity> list = await PhotoManager.getAssetPathList(
-          hasAll: true, onlyAll: true, type: RequestType.common);
-
-      // Get device assets info from database
-      // Compare and find different assets that has not been backing up
-      // Backup those assets
-      List<String> backupAsset = await _backupService.getDeviceBackupAsset();
-
-      if (list.isEmpty) {
-        debugPrint("No Asset On Device - Abort Backup Process");
-        state = state.copyWith(
-            backupProgress: BackUpProgressEnum.idle,
-            totalAssetCount: 0,
-            assetOnDatabase: backupAsset.length);
-        return;
-      }
-
-      int totalAsset = list[0].assetCount;
-      List<AssetEntity> currentAssets =
-          await list[0].getAssetListRange(start: 0, end: totalAsset);
-
-      state = state.copyWith(
-          totalAssetCount: totalAsset, assetOnDatabase: backupAsset.length);
-      // Remove item that has already been backed up
-      for (var backupAssetId in backupAsset) {
-        currentAssets.removeWhere((e) => e.id == backupAssetId);
-      }
-
-      if (currentAssets.isEmpty) {
-        state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
-      }
-
-      state = state.copyWith(backingUpAssetCount: currentAssets.length);
-
-      // Perform Backup
-      state = state.copyWith(cancelToken: CancelToken());
-      _backupService.backupAsset(currentAssets, state.cancelToken,
-          _onAssetUploaded, _onUploadProgress);
-    } else {
-      PhotoManager.openSetting();
-    }
-  }
-
-  void cancelBackup() {
-    state.cancelToken.cancel('Cancel Backup');
-    state = state.copyWith(
-        backupProgress: BackUpProgressEnum.idle, progressInPercentage: 0.0);
-  }
-
-  void _onAssetUploaded(String deviceAssetId, String deviceId) {
-    state = state.copyWith(
-        backingUpAssetCount: state.backingUpAssetCount - 1,
-        assetOnDatabase: state.assetOnDatabase + 1);
-
-    if (state.backingUpAssetCount == 0) {
-      state = state.copyWith(
-          backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0);
-    }
-
-    _updateServerInfo();
-  }
-
-  void _onUploadProgress(int sent, int total) {
-    state = state.copyWith(
-        progressInPercentage: (sent.toDouble() / total.toDouble() * 100));
-  }
-
-  void _updateServerInfo() async {
-    var serverInfo = await _serverInfoService.getServerInfo();
-
-    // Update server info
-    state = state.copyWith(
-      serverInfo: ServerInfo(
-        diskSize: serverInfo.diskSize,
-        diskUse: serverInfo.diskUse,
-        diskAvailable: serverInfo.diskAvailable,
-        diskSizeRaw: serverInfo.diskSizeRaw,
-        diskUseRaw: serverInfo.diskUseRaw,
-        diskAvailableRaw: serverInfo.diskAvailableRaw,
-        diskUsagePercentage: serverInfo.diskUsagePercentage,
-      ),
-    );
-  }
-
-  void resumeBackup() {
-    var authState = ref?.read(authenticationProvider);
-
-    // Check if user is login
-    var accessKey = Hive.box(userInfoBox).get(accessTokenKey);
-
-    // User has been logged out return
-    if (authState != null) {
-      if (accessKey == null || !authState.isAuthenticated) {
-        debugPrint("[resumeBackup] not authenticated - abort");
-        return;
-      }
-
-      // Check if this device is enable backup by the user
-      if ((authState.deviceInfo.deviceId == authState.deviceId) &&
-          authState.deviceInfo.isAutoBackup) {
-        // check if backup is alreayd in process - then return
-        if (state.backupProgress == BackUpProgressEnum.inProgress) {
-          debugPrint("[resumeBackup] Backup is already in progress - abort");
-          return;
-        }
-
-        // Run backup
-        debugPrint("[resumeBackup] Start back up");
-        startBackupProcess();
-      }
-
-      return;
-    }
-  }
-}
-
-final backupProvider =
-    StateNotifierProvider<BackupNotifier, BackUpState>((ref) {
-  return BackupNotifier(ref: ref);
-});
diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock
index 44d267cc78..f2c280722b 100644
--- a/mobile/pubspec.lock
+++ b/mobile/pubspec.lock
@@ -239,6 +239,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "4.0.4"
+  equatable:
+    dependency: "direct main"
+    description:
+      name: equatable
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.3"
   exif:
     dependency: "direct main"
     description:
diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml
index cae296b87e..a7c8217125 100644
--- a/mobile/pubspec.yaml
+++ b/mobile/pubspec.yaml
@@ -2,7 +2,7 @@ name: immich_mobile
 description: Immich - selfhosted backup media file on mobile phone
 
 publish_to: "none"
-version: 1.8.0+12
+version: 1.9.0+13
 
 environment:
   sdk: ">=2.15.1 <3.0.0"
@@ -37,6 +37,7 @@ dependencies:
   package_info_plus: ^1.4.0
   flutter_spinkit: ^5.1.0
   flutter_swipe_detector: ^2.0.0
+  equatable: ^2.0.3
 
 dev_dependencies:
   flutter_test:
diff --git a/server/src/constants/server_version.constant.ts b/server/src/constants/server_version.constant.ts
index 40326a71d1..775b97ccf5 100644
--- a/server/src/constants/server_version.constant.ts
+++ b/server/src/constants/server_version.constant.ts
@@ -3,7 +3,7 @@
 
 export const serverVersion = {
   major: 1,
-  minor: 8,
+  minor: 9,
   patch: 0,
-  build: 12,
+  build: 13,
 };