diff --git a/README.md b/README.md
index cd5c644a31..6d7957629a 100644
--- a/README.md
+++ b/README.md
@@ -34,8 +34,9 @@ Loading ~4000 images/videos
 <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/nsc3.png" width="150" title="Multiple select">
-  <img src="design/nsc4.jpeg" width="150" title="Curated Search 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">
   <img src="design/nsc6.png" width="150" title="EXIF Info">
 
 </p>
@@ -63,6 +64,7 @@ This project is under heavy development, there will be continous functions, feat
 - Show asset's location information on map (OpenStreetMap).
 - Show curated places on the search page
 - Show curated objects on the search page
+- Shared album with users on the same server
 
 # Development
 
@@ -111,6 +113,14 @@ curl --location --request POST 'http://your-server-ip:2283/auth/signUp' \
 
 ## Run mobile app
 
+## F-Droid
+You can get the app on F-droid by cliking the image below.
+
+[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
+    alt="Get it on F-Droid"
+    height="80">](https://f-droid.org/packages/app.alextran.immich)
+
+
 ## Android
 
 #### Download latest `apk` in release tab and run on your phone. You can follow this guide on how to do that
diff --git a/design/home-screen.jpeg b/design/home-screen.jpeg
new file mode 100644
index 0000000000..7871ed6f9a
Binary files /dev/null and b/design/home-screen.jpeg differ
diff --git a/design/search-screen.jpeg b/design/search-screen.jpeg
new file mode 100644
index 0000000000..e43fb897ea
Binary files /dev/null and b/design/search-screen.jpeg differ
diff --git a/design/shared-albums.png b/design/shared-albums.png
new file mode 100644
index 0000000000..a0b139b843
Binary files /dev/null and b/design/shared-albums.png differ
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index 16938140f3..a70078f311 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.6.0
+    image: immich-server-dev:1.7.0
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -24,7 +24,7 @@ services:
       - immich_network
 
   immich_microservices:
-    image: immich-microservices-dev:1.6.0
+    image: immich-microservices-dev:1.7.0
     build:
       context: ../microservices
       dockerfile: Dockerfile
diff --git a/docker/docker-compose.gpu.yml b/docker/docker-compose.gpu.yml
index bbdaf497c4..e37c6f8a05 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.6.0
+    image: immich-server-dev:1.7.0
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -22,7 +22,7 @@ services:
       - immich_network
 
   immich_microservices:
-    image: immich-microservices-dev:1.6.0
+    image: immich-microservices-dev:1.7.0
     build:
       context: ../microservices
       dockerfile: Dockerfile
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 74fc4fd283..2e292ed8f6 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -2,7 +2,7 @@ version: "3.8"
 
 services:
   immich_server:
-    image: immich-server:1.6.0
+    image: immich-server:1.7.0
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -23,7 +23,7 @@ services:
     restart: unless-stopped
 
   immich_microservices:
-    image: immich-microservices:1.6.0
+    image: immich-microservices:1.7.0
     build:
       context: ../microservices
       dockerfile: Dockerfile
diff --git a/docker/settings/nginx-conf/nginx.conf b/docker/settings/nginx-conf/nginx.conf
index 8d53113a0d..73fdd77ec8 100644
--- a/docker/settings/nginx-conf/nginx.conf
+++ b/docker/settings/nginx-conf/nginx.conf
@@ -10,11 +10,22 @@ map $http_upgrade $connection_upgrade {
 
 server {
 
+  gzip on;
+  gzip_min_length 1000;
+  gunzip on;
+
   client_max_body_size 50000M;
 
   listen 80;
   access_log off;
+
   location / {
+
+    # Compression
+    gzip_static         on;
+    gzip_min_length     1000;
+    gzip_comp_level     2;
+
     proxy_buffering off;
     proxy_buffer_size 16k;
     proxy_busy_buffers_size 24k;
diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle
index 2f7ca021ff..2a79dedc75 100644
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -81,4 +81,5 @@ flutter {
 
 dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    implementation 'com.android.support:multidex:1.0.3'
 }
diff --git a/mobile/android/fastlane/metadata/android/en-US/changelogs/11.txt b/mobile/android/fastlane/metadata/android/en-US/changelogs/11.txt
new file mode 100644
index 0000000000..f398e9b56e
--- /dev/null
+++ b/mobile/android/fastlane/metadata/android/en-US/changelogs/11.txt
@@ -0,0 +1,7 @@
+* New features 
+  - Share album. Users can now create albums to share with existing people on the network.
+  - Owner can delete the album.
+  - Owner can invite the additional users to the album.
+  - Shared users and the owner can add additional assets to the album.
+* In the asset viewer, the user can swipe up to see detailed information and swip down to dismiss.
+* Several UI enhancements.
\ No newline at end of file
diff --git a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png
index 83350376ca..36f586014f 100644
Binary files a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png and b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png differ
diff --git a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png
index f5a9e490bb..0e2151cc13 100644
Binary files a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png and b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png differ
diff --git a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png
index dabf1f16cd..4111a86c99 100644
Binary files a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png and b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png differ
diff --git a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png
new file mode 100644
index 0000000000..f5a9e490bb
Binary files /dev/null and b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png differ
diff --git a/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.png b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.png
new file mode 100644
index 0000000000..dabf1f16cd
Binary files /dev/null and b/mobile/android/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.png differ
diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile
index 232a109ebd..98b94617c3 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.6.0"
+      version_number: "1.7.0"
     )
     increment_build_number(
       build_number: latest_testflight_build_number + 1,
diff --git a/mobile/lib/constants/immich_colors.dart b/mobile/lib/constants/immich_colors.dart
new file mode 100644
index 0000000000..f93ae3736a
--- /dev/null
+++ b/mobile/lib/constants/immich_colors.dart
@@ -0,0 +1,3 @@
+import 'package:flutter/material.dart';
+
+const immichBackgroundColor = Color(0xFFf6f8fe);
diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart
index 9a6254f49b..3d2d71e584 100644
--- a/mobile/lib/main.dart
+++ b/mobile/lib/main.dart
@@ -2,20 +2,20 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:hive_flutter/hive_flutter.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
+import 'package:immich_mobile/constants/immich_colors.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/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';
 import 'constants/hive_box.dart';
 
 void main() async {
   await Hive.initFlutter();
   await Hive.openBox(userInfoBox);
-  // Hive.registerAdapter(ImmichBackUpAssetAdapter());
-  // Hive.deleteBoxFromDisk(hiveImmichBox);
 
   SystemChrome.setSystemUIOverlayStyle(
     const SystemUiOverlayStyle(
@@ -87,28 +87,33 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
 
   @override
   Widget build(BuildContext context) {
-    return MaterialApp.router(
-      title: 'Immich',
+    return MaterialApp(
       debugShowCheckedModeBanner: false,
-      theme: ThemeData(
-        brightness: Brightness.light,
-        primarySwatch: Colors.indigo,
-        // textTheme: GoogleFonts.workSansTextTheme(
-        //   Theme.of(context).textTheme.apply(fontSizeFactor: 1.0),
-        // ),
-        fontFamily: 'WorkSans',
-        snackBarTheme: const SnackBarThemeData(contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
-        scaffoldBackgroundColor: const Color(0xFFf6f8fe),
-        appBarTheme: const AppBarTheme(
-          backgroundColor: Colors.white,
-          foregroundColor: Colors.indigo,
-          elevation: 1,
-          centerTitle: true,
-          systemOverlayStyle: SystemUiOverlayStyle.dark,
-        ),
+      home: Stack(
+        children: [
+          MaterialApp.router(
+            title: 'Immich',
+            debugShowCheckedModeBanner: false,
+            theme: ThemeData(
+              brightness: Brightness.light,
+              primarySwatch: Colors.indigo,
+              fontFamily: 'WorkSans',
+              snackBarTheme: const SnackBarThemeData(contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
+              scaffoldBackgroundColor: immichBackgroundColor,
+              appBarTheme: const AppBarTheme(
+                backgroundColor: immichBackgroundColor,
+                foregroundColor: Colors.indigo,
+                elevation: 1,
+                centerTitle: true,
+                systemOverlayStyle: SystemUiOverlayStyle.dark,
+              ),
+            ),
+            routeInformationParser: _immichRouter.defaultRouteParser(),
+            routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
+          ),
+          const ImmichLoadingOverlay(),
+        ],
       ),
-      routeInformationParser: _immichRouter.defaultRouteParser(),
-      routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
     );
   }
 }
diff --git a/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart b/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart
index 856e4c3764..6dd7fd1457 100644
--- a/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart
+++ b/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart
@@ -1,6 +1,8 @@
+import 'package:auto_route/auto_route.dart';
 import 'package:cached_network_image/cached_network_image.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:flutter_swipe_detector/flutter_swipe_detector.dart';
 import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
@@ -35,6 +37,18 @@ class ImageViewerPage extends HookConsumerWidget {
       assetDetail = await _assetService.getAssetById(asset.id);
     }
 
+    showInfo() {
+      showModalBottomSheet(
+        backgroundColor: Colors.black,
+        barrierColor: Colors.transparent,
+        isScrollControlled: false,
+        context: context,
+        builder: (context) {
+          return ExifBottomSheet(assetDetail: assetDetail!);
+        },
+      );
+    }
+
     useEffect(() {
       getAssetExif();
       return null;
@@ -44,79 +58,77 @@ class ImageViewerPage extends HookConsumerWidget {
       backgroundColor: Colors.black,
       appBar: TopControlAppBar(
         asset: asset,
-        onMoreInfoPressed: () {
-          showModalBottomSheet(
-            backgroundColor: Colors.black,
-            barrierColor: Colors.transparent,
-            isScrollControlled: false,
-            context: context,
-            builder: (context) {
-              return ExifBottomSheet(assetDetail: assetDetail!);
-            },
-          );
-        },
+        onMoreInfoPressed: showInfo,
         onDownloadPressed: () {
           ref.watch(imageViewerStateProvider.notifier).downloadAsset(asset, context);
         },
       ),
-      body: SafeArea(
-        child: Stack(
-          children: [
-            Center(
-              child: Hero(
-                tag: heroTag,
-                child: CachedNetworkImage(
-                  fit: BoxFit.cover,
-                  imageUrl: imageUrl,
-                  httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
-                  fadeInDuration: const Duration(milliseconds: 250),
-                  errorWidget: (context, url, error) => ConstrainedBox(
-                    constraints: const BoxConstraints(maxWidth: 300),
-                    child: Wrap(
-                      spacing: 32,
-                      runSpacing: 32,
-                      alignment: WrapAlignment.center,
-                      children: [
-                        const Text(
-                          "Failed To Render Image - Possibly Corrupted Data",
-                          textAlign: TextAlign.center,
-                          style: TextStyle(fontSize: 16, color: Colors.white),
-                        ),
-                        SingleChildScrollView(
-                          child: Text(
-                            error.toString(),
+      body: SwipeDetector(
+        onSwipeDown: (_) {
+          AutoRouter.of(context).pop();
+        },
+        onSwipeUp: (_) {
+          showInfo();
+        },
+        child: SafeArea(
+          child: Stack(
+            children: [
+              Center(
+                child: Hero(
+                  tag: heroTag,
+                  child: CachedNetworkImage(
+                    fit: BoxFit.cover,
+                    imageUrl: imageUrl,
+                    httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
+                    fadeInDuration: const Duration(milliseconds: 250),
+                    errorWidget: (context, url, error) => ConstrainedBox(
+                      constraints: const BoxConstraints(maxWidth: 300),
+                      child: Wrap(
+                        spacing: 32,
+                        runSpacing: 32,
+                        alignment: WrapAlignment.center,
+                        children: [
+                          const Text(
+                            "Failed To Render Image - Possibly Corrupted Data",
                             textAlign: TextAlign.center,
-                            style: TextStyle(fontSize: 12, color: Colors.grey[400]),
+                            style: TextStyle(fontSize: 16, color: Colors.white),
                           ),
-                        ),
-                      ],
+                          SingleChildScrollView(
+                            child: Text(
+                              error.toString(),
+                              textAlign: TextAlign.center,
+                              style: TextStyle(fontSize: 12, color: Colors.grey[400]),
+                            ),
+                          ),
+                        ],
+                      ),
                     ),
+                    placeholder: (context, url) {
+                      return CachedNetworkImage(
+                        cacheKey: thumbnailUrl,
+                        fit: BoxFit.cover,
+                        imageUrl: thumbnailUrl,
+                        httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
+                        placeholderFadeInDuration: const Duration(milliseconds: 0),
+                        progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
+                          scale: 0.2,
+                          child: CircularProgressIndicator(value: downloadProgress.progress),
+                        ),
+                        errorWidget: (context, url, error) => Icon(
+                          Icons.error,
+                          color: Colors.grey[300],
+                        ),
+                      );
+                    },
                   ),
-                  placeholder: (context, url) {
-                    return CachedNetworkImage(
-                      cacheKey: thumbnailUrl,
-                      fit: BoxFit.cover,
-                      imageUrl: thumbnailUrl,
-                      httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
-                      placeholderFadeInDuration: const Duration(milliseconds: 0),
-                      progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
-                        scale: 0.2,
-                        child: CircularProgressIndicator(value: downloadProgress.progress),
-                      ),
-                      errorWidget: (context, url, error) => Icon(
-                        Icons.error,
-                        color: Colors.grey[300],
-                      ),
-                    );
-                  },
                 ),
               ),
-            ),
-            if (downloadAssetStatus == DownloadAssetStatus.loading)
-              const Center(
-                child: DownloadLoadingIndicator(),
-              ),
-          ],
+              if (downloadAssetStatus == DownloadAssetStatus.loading)
+                const Center(
+                  child: DownloadLoadingIndicator(),
+                ),
+            ],
+          ),
         ),
       ),
     );
diff --git a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart
index 891128313f..e93e83d22e 100644
--- a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart
+++ b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart
@@ -1,5 +1,7 @@
+import 'package:auto_route/auto_route.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:flutter_swipe_detector/flutter_swipe_detector.dart';
 import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
@@ -29,6 +31,18 @@ class VideoViewerPage extends HookConsumerWidget {
 
     String jwtToken = Hive.box(userInfoBox).get(accessTokenKey);
 
+    void showInfo() {
+      showModalBottomSheet(
+        backgroundColor: Colors.black,
+        barrierColor: Colors.transparent,
+        isScrollControlled: false,
+        context: context,
+        builder: (context) {
+          return ExifBottomSheet(assetDetail: assetDetail!);
+        },
+      );
+    }
+
     getAssetExif() async {
       assetDetail = await _assetService.getAssetById(asset.id);
     }
@@ -43,32 +57,32 @@ class VideoViewerPage extends HookConsumerWidget {
       appBar: TopControlAppBar(
         asset: asset,
         onMoreInfoPressed: () {
-          showModalBottomSheet(
-            backgroundColor: Colors.black,
-            barrierColor: Colors.transparent,
-            isScrollControlled: false,
-            context: context,
-            builder: (context) {
-              return ExifBottomSheet(assetDetail: assetDetail!);
-            },
-          );
+          showInfo();
         },
         onDownloadPressed: () {
           ref.watch(imageViewerStateProvider.notifier).downloadAsset(asset, context);
         },
       ),
-      body: SafeArea(
-        child: Stack(
-          children: [
-            VideoThumbnailPlayer(
-              url: videoUrl,
-              jwtToken: jwtToken,
-            ),
-            if (downloadAssetStatus == DownloadAssetStatus.loading)
-              const Center(
-                child: DownloadLoadingIndicator(),
+      body: SwipeDetector(
+        onSwipeDown: (_) {
+          AutoRouter.of(context).pop();
+        },
+        onSwipeUp: (_) {
+          showInfo();
+        },
+        child: SafeArea(
+          child: Stack(
+            children: [
+              VideoThumbnailPlayer(
+                url: videoUrl,
+                jwtToken: jwtToken,
               ),
-          ],
+              if (downloadAssetStatus == DownloadAssetStatus.loading)
+                const Center(
+                  child: DownloadLoadingIndicator(),
+                ),
+            ],
+          ),
         ),
       ),
     );
diff --git a/mobile/lib/modules/home/ui/delete_diaglog.dart b/mobile/lib/modules/home/ui/delete_diaglog.dart
index 5294c7e629..af6040da83 100644
--- a/mobile/lib/modules/home/ui/delete_diaglog.dart
+++ b/mobile/lib/modules/home/ui/delete_diaglog.dart
@@ -1,6 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
+import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
 
 class DeleteDialog extends ConsumerWidget {
diff --git a/mobile/lib/modules/home/ui/image_grid.dart b/mobile/lib/modules/home/ui/image_grid.dart
index 32233a7944..1ac6d61ba7 100644
--- a/mobile/lib/modules/home/ui/image_grid.dart
+++ b/mobile/lib/modules/home/ui/image_grid.dart
@@ -11,40 +11,44 @@ class ImageGrid extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     return SliverGrid(
-      gridDelegate:
-          const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 5.0, mainAxisSpacing: 5),
+      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+        crossAxisCount: 3,
+        crossAxisSpacing: 5.0,
+        mainAxisSpacing: 5,
+      ),
       delegate: SliverChildBuilderDelegate(
         (BuildContext context, int index) {
           var assetType = assetGroup[index].type;
 
           return GestureDetector(
-              onTap: () {},
-              child: Stack(
-                children: [
-                  ThumbnailImage(asset: assetGroup[index]),
-                  assetType == 'IMAGE'
-                      ? Container()
-                      : Positioned(
-                          top: 5,
-                          right: 5,
-                          child: Row(
-                            children: [
-                              Text(
-                                assetGroup[index].duration.toString().substring(0, 7),
-                                style: const TextStyle(
-                                  color: Colors.white,
-                                  fontSize: 10,
-                                ),
-                              ),
-                              const Icon(
-                                Icons.play_circle_outline_rounded,
+            onTap: () {},
+            child: Stack(
+              children: [
+                ThumbnailImage(asset: assetGroup[index]),
+                assetType == 'IMAGE'
+                    ? Container()
+                    : Positioned(
+                        top: 5,
+                        right: 5,
+                        child: Row(
+                          children: [
+                            Text(
+                              assetGroup[index].duration.toString().substring(0, 7),
+                              style: const TextStyle(
                                 color: Colors.white,
+                                fontSize: 10,
                               ),
-                            ],
-                          ),
-                        )
-                ],
-              ));
+                            ),
+                            const Icon(
+                              Icons.play_circle_outline_rounded,
+                              color: Colors.white,
+                            ),
+                          ],
+                        ),
+                      )
+              ],
+            ),
+          );
         },
         childCount: assetGroup.length,
       ),
diff --git a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
index 18fee2450a..4094257316 100644
--- a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
+++ b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
@@ -29,7 +29,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
       floating: true,
       pinned: false,
       snap: false,
-      backgroundColor: Colors.grey[200],
+      // backgroundColor: Colors.grey[200],
       shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))),
       leading: Builder(
         builder: (BuildContext context) {
@@ -40,7 +40,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
                 child: IconButton(
                   splashRadius: 25,
                   icon: const Icon(
-                    Icons.account_circle_rounded,
+                    Icons.face_outlined,
                     size: 30,
                   ),
                   onPressed: () {
diff --git a/mobile/lib/modules/home/ui/profile_drawer.dart b/mobile/lib/modules/home/ui/profile_drawer.dart
index c9a586fc17..cafab9e379 100644
--- a/mobile/lib/modules/home/ui/profile_drawer.dart
+++ b/mobile/lib/modules/home/ui/profile_drawer.dart
@@ -2,7 +2,7 @@ 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/modules/home/providers/asset.provider.dart';
+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';
@@ -79,7 +79,7 @@ class ProfileDrawer extends HookConsumerWidget {
                 ),
                 title: const Text(
                   "Sign Out",
-                  style: TextStyle(color: Colors.black54, fontSize: 14),
+                  style: TextStyle(color: Colors.black54, fontSize: 14, fontWeight: FontWeight.bold),
                 ),
                 onTap: () async {
                   bool res = await ref.read(authenticationProvider.notifier).logout();
diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart
index c96c578c9a..9152c06a7c 100644
--- a/mobile/lib/modules/home/views/home_page.dart
+++ b/mobile/lib/modules/home/views/home_page.dart
@@ -10,7 +10,7 @@ import 'package:immich_mobile/modules/home/ui/image_grid.dart';
 import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
 import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart';
 import 'package:immich_mobile/modules/home/ui/profile_drawer.dart';
-import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
+import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 import 'package:immich_mobile/shared/providers/websocket.provider.dart';
 import 'package:sliver_tools/sliver_tools.dart';
diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart
index 213ca92684..9ea9adfbc7 100644
--- a/mobile/lib/modules/login/ui/login_form.dart
+++ b/mobile/lib/modules/login/ui/login_form.dart
@@ -2,7 +2,7 @@ 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/modules/home/providers/asset.provider.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/shared/ui/immich_toast.dart';
@@ -14,7 +14,7 @@ class LoginForm extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     final usernameController = useTextEditingController(text: 'testuser@email.com');
     final passwordController = useTextEditingController(text: 'password');
-    final serverEndpointController = useTextEditingController(text: 'http://192.168.1.103:2283');
+    final serverEndpointController = useTextEditingController(text: 'http://192.168.1.216:2283');
 
     return Center(
       child: ConstrainedBox(
@@ -119,11 +119,11 @@ class LoginButton extends ConsumerWidget {
           // This will remove current cache asset state of previous user login.
           ref.watch(assetProvider.notifier).clearAllAsset();
 
-          var isAuthenicated = await ref
+          var isAuthenticated = await ref
               .read(authenticationProvider.notifier)
               .login(emailController.text, passwordController.text, serverEndpointController.text);
 
-          if (isAuthenicated) {
+          if (isAuthenticated) {
             // Resume backup (if enable) then navigate
             ref.watch(backupProvider.notifier).resumeBackup();
             // AutoRouter.of(context).pushNamed("/home-page");
diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart
index 9e8b3cd78e..59866b6056 100644
--- a/mobile/lib/modules/search/views/search_page.dart
+++ b/mobile/lib/modules/search/views/search_page.dart
@@ -1,6 +1,7 @@
 import 'package:auto_route/auto_route.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:flutter_spinkit/flutter_spinkit.dart';
 import 'package:hive_flutter/hive_flutter.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
@@ -11,6 +12,7 @@ import 'package:immich_mobile/modules/search/ui/search_bar.dart';
 import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
 import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
 import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
 import 'package:immich_mobile/utils/capitalize_first_letter.dart';
 
 // ignore: must_be_immutable
@@ -23,8 +25,10 @@ class SearchPage extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     var box = Hive.box(userInfoBox);
     final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
-    AsyncValue<List<CuratedLocation>> curatedLocation = ref.watch(getCuratedLocationProvider);
-    AsyncValue<List<CuratedObject>> curatedObjects = ref.watch(getCuratedObjectProvider);
+    AsyncValue<List<CuratedLocation>> curatedLocation =
+        ref.watch(getCuratedLocationProvider);
+    AsyncValue<List<CuratedObject>> curatedObjects =
+        ref.watch(getCuratedObjectProvider);
 
     useEffect(() {
       searchFocusNode = FocusNode();
@@ -40,7 +44,10 @@ class SearchPage extends HookConsumerWidget {
 
     _buildPlaces() {
       return curatedLocation.when(
-        loading: () => const SizedBox(width: 60, height: 60, child: CircularProgressIndicator.adaptive()),
+        loading: () => const SizedBox(
+          height: 200,
+          child: Center(child: ImmichLoadingIndicator()),
+        ),
         error: (err, stack) => Text('Error: $err'),
         data: (curatedLocations) {
           return curatedLocations.isNotEmpty
@@ -59,7 +66,8 @@ class SearchPage extends HookConsumerWidget {
                         imageUrl: thumbnailRequestUrl,
                         textInfo: locationInfo.city,
                         onTap: () {
-                          AutoRouter.of(context).push(SearchResultRoute(searchTerm: locationInfo.city));
+                          AutoRouter.of(context).push(
+                              SearchResultRoute(searchTerm: locationInfo.city));
                         },
                       );
                     }),
@@ -87,7 +95,10 @@ class SearchPage extends HookConsumerWidget {
 
     _buildThings() {
       return curatedObjects.when(
-        loading: () => const SizedBox(width: 60, height: 60, child: CircularProgressIndicator.adaptive()),
+        loading: () => const SizedBox(
+          height: 200,
+          child: Center(child: ImmichLoadingIndicator()),
+        ),
         error: (err, stack) => Text('Error: $err'),
         data: (objects) {
           return objects.isNotEmpty
@@ -106,8 +117,9 @@ class SearchPage extends HookConsumerWidget {
                         imageUrl: thumbnailRequestUrl,
                         textInfo: curatedObjectInfo.object,
                         onTap: () {
-                          AutoRouter.of(context)
-                              .push(SearchResultRoute(searchTerm: curatedObjectInfo.object.capitalizeFirstLetter()));
+                          AutoRouter.of(context).push(SearchResultRoute(
+                              searchTerm: curatedObjectInfo.object
+                                  .capitalizeFirstLetter()));
                         },
                       );
                     }),
@@ -165,7 +177,9 @@ class SearchPage extends HookConsumerWidget {
                 _buildThings()
               ],
             ),
-            isSearchEnabled ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) : Container(),
+            isSearchEnabled
+                ? SearchSuggestionList(onSubmitted: _onSearchSubmitted)
+                : Container(),
           ],
         ),
       ),
diff --git a/mobile/lib/modules/sharing/models/asset_selection_page_result.model.dart b/mobile/lib/modules/sharing/models/asset_selection_page_result.model.dart
new file mode 100644
index 0000000000..e4c53e37fd
--- /dev/null
+++ b/mobile/lib/modules/sharing/models/asset_selection_page_result.model.dart
@@ -0,0 +1,70 @@
+import 'dart:convert';
+
+import 'package:collection/collection.dart';
+
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+
+class AssetSelectionPageResult {
+  final Set<ImmichAsset> selectedNewAsset;
+  final Set<ImmichAsset> selectedAdditionalAsset;
+  final bool isAlbumExist;
+
+  AssetSelectionPageResult({
+    required this.selectedNewAsset,
+    required this.selectedAdditionalAsset,
+    required this.isAlbumExist,
+  });
+
+  AssetSelectionPageResult copyWith({
+    Set<ImmichAsset>? selectedNewAsset,
+    Set<ImmichAsset>? selectedAdditionalAsset,
+    bool? isAlbumExist,
+  }) {
+    return AssetSelectionPageResult(
+      selectedNewAsset: selectedNewAsset ?? this.selectedNewAsset,
+      selectedAdditionalAsset: selectedAdditionalAsset ?? this.selectedAdditionalAsset,
+      isAlbumExist: isAlbumExist ?? this.isAlbumExist,
+    );
+  }
+
+  Map<String, dynamic> toMap() {
+    final result = <String, dynamic>{};
+
+    result.addAll({'selectedNewAsset': selectedNewAsset.map((x) => x.toMap()).toList()});
+    result.addAll({'selectedAdditionalAsset': selectedAdditionalAsset.map((x) => x.toMap()).toList()});
+    result.addAll({'isAlbumExist': isAlbumExist});
+
+    return result;
+  }
+
+  factory AssetSelectionPageResult.fromMap(Map<String, dynamic> map) {
+    return AssetSelectionPageResult(
+      selectedNewAsset: Set<ImmichAsset>.from(map['selectedNewAsset']?.map((x) => ImmichAsset.fromMap(x))),
+      selectedAdditionalAsset:
+          Set<ImmichAsset>.from(map['selectedAdditionalAsset']?.map((x) => ImmichAsset.fromMap(x))),
+      isAlbumExist: map['isAlbumExist'] ?? false,
+    );
+  }
+
+  String toJson() => json.encode(toMap());
+
+  factory AssetSelectionPageResult.fromJson(String source) => AssetSelectionPageResult.fromMap(json.decode(source));
+
+  @override
+  String toString() =>
+      'AssetSelectionPageResult(selectedNewAsset: $selectedNewAsset, selectedAdditionalAsset: $selectedAdditionalAsset, isAlbumExist: $isAlbumExist)';
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    final setEquals = const DeepCollectionEquality().equals;
+
+    return other is AssetSelectionPageResult &&
+        setEquals(other.selectedNewAsset, selectedNewAsset) &&
+        setEquals(other.selectedAdditionalAsset, selectedAdditionalAsset) &&
+        other.isAlbumExist == isAlbumExist;
+  }
+
+  @override
+  int get hashCode => selectedNewAsset.hashCode ^ selectedAdditionalAsset.hashCode ^ isAlbumExist.hashCode;
+}
diff --git a/mobile/lib/modules/sharing/models/asset_selection_state.model.dart b/mobile/lib/modules/sharing/models/asset_selection_state.model.dart
new file mode 100644
index 0000000000..33e312362e
--- /dev/null
+++ b/mobile/lib/modules/sharing/models/asset_selection_state.model.dart
@@ -0,0 +1,103 @@
+import 'dart:convert';
+
+import 'package:collection/collection.dart';
+
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+
+class AssetSelectionState {
+  final Set<String> selectedMonths;
+  final Set<ImmichAsset> selectedNewAssetsForAlbum;
+  final Set<ImmichAsset> selectedAdditionalAssetsForAlbum;
+  final Set<ImmichAsset> selectedAssetsInAlbumViewer;
+  final bool isMultiselectEnable;
+
+  /// Indicate the asset selection page is navigated from existing album
+  final bool isAlbumExist;
+  AssetSelectionState({
+    required this.selectedMonths,
+    required this.selectedNewAssetsForAlbum,
+    required this.selectedAdditionalAssetsForAlbum,
+    required this.selectedAssetsInAlbumViewer,
+    required this.isMultiselectEnable,
+    required this.isAlbumExist,
+  });
+
+  AssetSelectionState copyWith({
+    Set<String>? selectedMonths,
+    Set<ImmichAsset>? selectedNewAssetsForAlbum,
+    Set<ImmichAsset>? selectedAdditionalAssetsForAlbum,
+    Set<ImmichAsset>? selectedAssetsInAlbumViewer,
+    bool? isMultiselectEnable,
+    bool? isAlbumExist,
+  }) {
+    return AssetSelectionState(
+      selectedMonths: selectedMonths ?? this.selectedMonths,
+      selectedNewAssetsForAlbum: selectedNewAssetsForAlbum ?? this.selectedNewAssetsForAlbum,
+      selectedAdditionalAssetsForAlbum: selectedAdditionalAssetsForAlbum ?? this.selectedAdditionalAssetsForAlbum,
+      selectedAssetsInAlbumViewer: selectedAssetsInAlbumViewer ?? this.selectedAssetsInAlbumViewer,
+      isMultiselectEnable: isMultiselectEnable ?? this.isMultiselectEnable,
+      isAlbumExist: isAlbumExist ?? this.isAlbumExist,
+    );
+  }
+
+  Map<String, dynamic> toMap() {
+    final result = <String, dynamic>{};
+
+    result.addAll({'selectedMonths': selectedMonths.toList()});
+    result.addAll({'selectedNewAssetsForAlbum': selectedNewAssetsForAlbum.map((x) => x.toMap()).toList()});
+    result
+        .addAll({'selectedAdditionalAssetsForAlbum': selectedAdditionalAssetsForAlbum.map((x) => x.toMap()).toList()});
+    result.addAll({'selectedAssetsInAlbumViewer': selectedAssetsInAlbumViewer.map((x) => x.toMap()).toList()});
+    result.addAll({'isMultiselectEnable': isMultiselectEnable});
+    result.addAll({'isAlbumExist': isAlbumExist});
+
+    return result;
+  }
+
+  factory AssetSelectionState.fromMap(Map<String, dynamic> map) {
+    return AssetSelectionState(
+      selectedMonths: Set<String>.from(map['selectedMonths']),
+      selectedNewAssetsForAlbum:
+          Set<ImmichAsset>.from(map['selectedNewAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
+      selectedAdditionalAssetsForAlbum:
+          Set<ImmichAsset>.from(map['selectedAdditionalAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
+      selectedAssetsInAlbumViewer:
+          Set<ImmichAsset>.from(map['selectedAssetsInAlbumViewer']?.map((x) => ImmichAsset.fromMap(x))),
+      isMultiselectEnable: map['isMultiselectEnable'] ?? false,
+      isAlbumExist: map['isAlbumExist'] ?? false,
+    );
+  }
+
+  String toJson() => json.encode(toMap());
+
+  factory AssetSelectionState.fromJson(String source) => AssetSelectionState.fromMap(json.decode(source));
+
+  @override
+  String toString() {
+    return 'AssetSelectionState(selectedMonths: $selectedMonths, selectedNewAssetsForAlbum: $selectedNewAssetsForAlbum, selectedAdditionalAssetsForAlbum: $selectedAdditionalAssetsForAlbum, selectedAssetsInAlbumViewer: $selectedAssetsInAlbumViewer, isMultiselectEnable: $isMultiselectEnable, isAlbumExist: $isAlbumExist)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    final setEquals = const DeepCollectionEquality().equals;
+
+    return other is AssetSelectionState &&
+        setEquals(other.selectedMonths, selectedMonths) &&
+        setEquals(other.selectedNewAssetsForAlbum, selectedNewAssetsForAlbum) &&
+        setEquals(other.selectedAdditionalAssetsForAlbum, selectedAdditionalAssetsForAlbum) &&
+        setEquals(other.selectedAssetsInAlbumViewer, selectedAssetsInAlbumViewer) &&
+        other.isMultiselectEnable == isMultiselectEnable &&
+        other.isAlbumExist == isAlbumExist;
+  }
+
+  @override
+  int get hashCode {
+    return selectedMonths.hashCode ^
+        selectedNewAssetsForAlbum.hashCode ^
+        selectedAdditionalAssetsForAlbum.hashCode ^
+        selectedAssetsInAlbumViewer.hashCode ^
+        isMultiselectEnable.hashCode ^
+        isAlbumExist.hashCode;
+  }
+}
diff --git a/mobile/lib/modules/sharing/models/shared_album.model.dart b/mobile/lib/modules/sharing/models/shared_album.model.dart
new file mode 100644
index 0000000000..e1323dbcb2
--- /dev/null
+++ b/mobile/lib/modules/sharing/models/shared_album.model.dart
@@ -0,0 +1,113 @@
+import 'dart:convert';
+
+import 'package:collection/collection.dart';
+
+import 'package:immich_mobile/modules/sharing/models/shared_asset.model.dart';
+import 'package:immich_mobile/modules/sharing/models/shared_user.model.dart';
+
+class SharedAlbum {
+  final String id;
+  final String ownerId;
+  final String albumName;
+  final String createdAt;
+  final String? albumThumbnailAssetId;
+  final List<SharedUsers> sharedUsers;
+  final List<SharedAssets>? sharedAssets;
+
+  SharedAlbum({
+    required this.id,
+    required this.ownerId,
+    required this.albumName,
+    required this.createdAt,
+    required this.albumThumbnailAssetId,
+    required this.sharedUsers,
+    this.sharedAssets,
+  });
+
+  SharedAlbum copyWith({
+    String? id,
+    String? ownerId,
+    String? albumName,
+    String? createdAt,
+    String? albumThumbnailAssetId,
+    List<SharedUsers>? sharedUsers,
+    List<SharedAssets>? sharedAssets,
+  }) {
+    return SharedAlbum(
+      id: id ?? this.id,
+      ownerId: ownerId ?? this.ownerId,
+      albumName: albumName ?? this.albumName,
+      createdAt: createdAt ?? this.createdAt,
+      albumThumbnailAssetId: albumThumbnailAssetId ?? this.albumThumbnailAssetId,
+      sharedUsers: sharedUsers ?? this.sharedUsers,
+      sharedAssets: sharedAssets ?? this.sharedAssets,
+    );
+  }
+
+  Map<String, dynamic> toMap() {
+    final result = <String, dynamic>{};
+
+    result.addAll({'id': id});
+    result.addAll({'ownerId': ownerId});
+    result.addAll({'albumName': albumName});
+    result.addAll({'createdAt': createdAt});
+    if (albumThumbnailAssetId != null) {
+      result.addAll({'albumThumbnailAssetId': albumThumbnailAssetId});
+    }
+    result.addAll({'sharedUsers': sharedUsers.map((x) => x.toMap()).toList()});
+    if (sharedAssets != null) {
+      result.addAll({'sharedAssets': sharedAssets!.map((x) => x.toMap()).toList()});
+    }
+
+    return result;
+  }
+
+  factory SharedAlbum.fromMap(Map<String, dynamic> map) {
+    return SharedAlbum(
+      id: map['id'] ?? '',
+      ownerId: map['ownerId'] ?? '',
+      albumName: map['albumName'] ?? '',
+      createdAt: map['createdAt'] ?? '',
+      albumThumbnailAssetId: map['albumThumbnailAssetId'],
+      sharedUsers: List<SharedUsers>.from(map['sharedUsers']?.map((x) => SharedUsers.fromMap(x))),
+      sharedAssets: map['sharedAssets'] != null
+          ? List<SharedAssets>.from(map['sharedAssets']?.map((x) => SharedAssets.fromMap(x)))
+          : null,
+    );
+  }
+
+  String toJson() => json.encode(toMap());
+
+  factory SharedAlbum.fromJson(String source) => SharedAlbum.fromMap(json.decode(source));
+
+  @override
+  String toString() {
+    return 'SharedAlbum(id: $id, ownerId: $ownerId, albumName: $albumName, createdAt: $createdAt, albumThumbnailAssetId: $albumThumbnailAssetId, sharedUsers: $sharedUsers, sharedAssets: $sharedAssets)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    final listEquals = const DeepCollectionEquality().equals;
+
+    return other is SharedAlbum &&
+        other.id == id &&
+        other.ownerId == ownerId &&
+        other.albumName == albumName &&
+        other.createdAt == createdAt &&
+        other.albumThumbnailAssetId == albumThumbnailAssetId &&
+        listEquals(other.sharedUsers, sharedUsers) &&
+        listEquals(other.sharedAssets, sharedAssets);
+  }
+
+  @override
+  int get hashCode {
+    return id.hashCode ^
+        ownerId.hashCode ^
+        albumName.hashCode ^
+        createdAt.hashCode ^
+        albumThumbnailAssetId.hashCode ^
+        sharedUsers.hashCode ^
+        sharedAssets.hashCode;
+  }
+}
diff --git a/mobile/lib/modules/sharing/models/shared_asset.model.dart b/mobile/lib/modules/sharing/models/shared_asset.model.dart
new file mode 100644
index 0000000000..e74584ce7c
--- /dev/null
+++ b/mobile/lib/modules/sharing/models/shared_asset.model.dart
@@ -0,0 +1,50 @@
+import 'dart:convert';
+
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+
+class SharedAssets {
+  final ImmichAsset assetInfo;
+
+  SharedAssets({
+    required this.assetInfo,
+  });
+
+  SharedAssets copyWith({
+    ImmichAsset? assetInfo,
+  }) {
+    return SharedAssets(
+      assetInfo: assetInfo ?? this.assetInfo,
+    );
+  }
+
+  Map<String, dynamic> toMap() {
+    final result = <String, dynamic>{};
+
+    result.addAll({'assetInfo': assetInfo.toMap()});
+
+    return result;
+  }
+
+  factory SharedAssets.fromMap(Map<String, dynamic> map) {
+    return SharedAssets(
+      assetInfo: ImmichAsset.fromMap(map['assetInfo']),
+    );
+  }
+
+  String toJson() => json.encode(toMap());
+
+  factory SharedAssets.fromJson(String source) => SharedAssets.fromMap(json.decode(source));
+
+  @override
+  String toString() => 'SharedAssets(assetInfo: $assetInfo)';
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+
+    return other is SharedAssets && other.assetInfo == assetInfo;
+  }
+
+  @override
+  int get hashCode => assetInfo.hashCode;
+}
diff --git a/mobile/lib/modules/sharing/models/shared_user.model.dart b/mobile/lib/modules/sharing/models/shared_user.model.dart
new file mode 100644
index 0000000000..78e0398f75
--- /dev/null
+++ b/mobile/lib/modules/sharing/models/shared_user.model.dart
@@ -0,0 +1,76 @@
+import 'dart:convert';
+
+import 'package:immich_mobile/shared/models/user_info.model.dart';
+
+class SharedUsers {
+  final int id;
+  final String albumId;
+  final String sharedUserId;
+  final UserInfo userInfo;
+
+  SharedUsers({
+    required this.id,
+    required this.albumId,
+    required this.sharedUserId,
+    required this.userInfo,
+  });
+
+  SharedUsers copyWith({
+    int? id,
+    String? albumId,
+    String? sharedUserId,
+    UserInfo? userInfo,
+  }) {
+    return SharedUsers(
+      id: id ?? this.id,
+      albumId: albumId ?? this.albumId,
+      sharedUserId: sharedUserId ?? this.sharedUserId,
+      userInfo: userInfo ?? this.userInfo,
+    );
+  }
+
+  Map<String, dynamic> toMap() {
+    final result = <String, dynamic>{};
+
+    result.addAll({'id': id});
+    result.addAll({'albumId': albumId});
+    result.addAll({'sharedUserId': sharedUserId});
+    result.addAll({'userInfo': userInfo.toMap()});
+
+    return result;
+  }
+
+  factory SharedUsers.fromMap(Map<String, dynamic> map) {
+    return SharedUsers(
+      id: map['id']?.toInt() ?? 0,
+      albumId: map['albumId'] ?? '',
+      sharedUserId: map['sharedUserId'] ?? '',
+      userInfo: UserInfo.fromMap(map['userInfo']),
+    );
+  }
+
+  String toJson() => json.encode(toMap());
+
+  factory SharedUsers.fromJson(String source) => SharedUsers.fromMap(json.decode(source));
+
+  @override
+  String toString() {
+    return 'SharedUsers(id: $id, albumId: $albumId, sharedUserId: $sharedUserId, userInfo: $userInfo)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+
+    return other is SharedUsers &&
+        other.id == id &&
+        other.albumId == albumId &&
+        other.sharedUserId == sharedUserId &&
+        other.userInfo == userInfo;
+  }
+
+  @override
+  int get hashCode {
+    return id.hashCode ^ albumId.hashCode ^ sharedUserId.hashCode ^ userInfo.hashCode;
+  }
+}
diff --git a/mobile/lib/modules/sharing/providers/album_title.provider.dart b/mobile/lib/modules/sharing/providers/album_title.provider.dart
new file mode 100644
index 0000000000..bf812a01d8
--- /dev/null
+++ b/mobile/lib/modules/sharing/providers/album_title.provider.dart
@@ -0,0 +1,15 @@
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+class AlbumTitleNotifier extends StateNotifier<String> {
+  AlbumTitleNotifier() : super("");
+
+  setAlbumTitle(String title) {
+    state = title;
+  }
+
+  clearAlbumTitle() {
+    state = "";
+  }
+}
+
+final albumTitleProvider = StateNotifierProvider<AlbumTitleNotifier, String>((ref) => AlbumTitleNotifier());
diff --git a/mobile/lib/modules/sharing/providers/asset_selection.provider.dart b/mobile/lib/modules/sharing/providers/asset_selection.provider.dart
new file mode 100644
index 0000000000..bd8dcc4a85
--- /dev/null
+++ b/mobile/lib/modules/sharing/providers/asset_selection.provider.dart
@@ -0,0 +1,113 @@
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/sharing/models/asset_selection_state.model.dart';
+
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+
+class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
+  AssetSelectionNotifier()
+      : super(AssetSelectionState(
+          selectedNewAssetsForAlbum: {},
+          selectedMonths: {},
+          selectedAdditionalAssetsForAlbum: {},
+          selectedAssetsInAlbumViewer: {},
+          isAlbumExist: false,
+          isMultiselectEnable: false,
+        ));
+
+  void setIsAlbumExist(bool isAlbumExist) {
+    state = state.copyWith(isAlbumExist: isAlbumExist);
+  }
+
+  void removeAssetsInMonth(String removedMonth, List<ImmichAsset> assetsInMonth) {
+    Set<ImmichAsset> currentAssetList = state.selectedNewAssetsForAlbum;
+    Set<String> currentMonthList = state.selectedMonths;
+
+    currentMonthList.removeWhere((selectedMonth) => selectedMonth == removedMonth);
+
+    for (ImmichAsset asset in assetsInMonth) {
+      currentAssetList.removeWhere((e) => e.id == asset.id);
+    }
+
+    state = state.copyWith(selectedNewAssetsForAlbum: currentAssetList, selectedMonths: currentMonthList);
+  }
+
+  void addAdditionalAssets(List<ImmichAsset> assets) {
+    state = state.copyWith(
+      selectedAdditionalAssetsForAlbum: {...state.selectedAdditionalAssetsForAlbum, ...assets},
+    );
+  }
+
+  void addAllAssetsInMonth(String month, List<ImmichAsset> assetsInMonth) {
+    state = state.copyWith(
+      selectedMonths: {...state.selectedMonths, month},
+      selectedNewAssetsForAlbum: {...state.selectedNewAssetsForAlbum, ...assetsInMonth},
+    );
+  }
+
+  void addNewAssets(List<ImmichAsset> assets) {
+    state = state.copyWith(
+      selectedNewAssetsForAlbum: {...state.selectedNewAssetsForAlbum, ...assets},
+    );
+  }
+
+  void removeSelectedNewAssets(List<ImmichAsset> assets) {
+    Set<ImmichAsset> currentList = state.selectedNewAssetsForAlbum;
+
+    for (ImmichAsset asset in assets) {
+      currentList.removeWhere((e) => e.id == asset.id);
+    }
+
+    state = state.copyWith(selectedNewAssetsForAlbum: currentList);
+  }
+
+  void removeSelectedAdditionalAssets(List<ImmichAsset> assets) {
+    Set<ImmichAsset> currentList = state.selectedAdditionalAssetsForAlbum;
+
+    for (ImmichAsset asset in assets) {
+      currentList.removeWhere((e) => e.id == asset.id);
+    }
+
+    state = state.copyWith(selectedAdditionalAssetsForAlbum: currentList);
+  }
+
+  void removeAll() {
+    state = state.copyWith(
+      selectedNewAssetsForAlbum: {},
+      selectedMonths: {},
+      selectedAdditionalAssetsForAlbum: {},
+      selectedAssetsInAlbumViewer: {},
+      isAlbumExist: false,
+    );
+  }
+
+  void enableMultiselection() {
+    state = state.copyWith(isMultiselectEnable: true);
+  }
+
+  void disableMultiselection() {
+    state = state.copyWith(
+      isMultiselectEnable: false,
+      selectedAssetsInAlbumViewer: {},
+    );
+  }
+
+  void addAssetsInAlbumViewer(List<ImmichAsset> assets) {
+    state = state.copyWith(
+      selectedAssetsInAlbumViewer: {...state.selectedAssetsInAlbumViewer, ...assets},
+    );
+  }
+
+  void removeAssetsInAlbumViewer(List<ImmichAsset> assets) {
+    Set<ImmichAsset> currentList = state.selectedAssetsInAlbumViewer;
+
+    for (ImmichAsset asset in assets) {
+      currentList.removeWhere((e) => e.id == asset.id);
+    }
+
+    state = state.copyWith(selectedAssetsInAlbumViewer: currentList);
+  }
+}
+
+final assetSelectionProvider = StateNotifierProvider<AssetSelectionNotifier, AssetSelectionState>((ref) {
+  return AssetSelectionNotifier();
+});
diff --git a/mobile/lib/modules/sharing/providers/shared_album.provider.dart b/mobile/lib/modules/sharing/providers/shared_album.provider.dart
new file mode 100644
index 0000000000..be9f47581c
--- /dev/null
+++ b/mobile/lib/modules/sharing/providers/shared_album.provider.dart
@@ -0,0 +1,57 @@
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
+import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
+
+class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> {
+  SharedAlbumNotifier() : super([]);
+
+  final SharedAlbumService _sharedAlbumService = SharedAlbumService();
+
+  getAllSharedAlbums() async {
+    List<SharedAlbum> sharedAlbums = await _sharedAlbumService.getAllSharedAlbum();
+
+    state = sharedAlbums;
+  }
+
+  Future<bool> deleteAlbum(String albumId) async {
+    var res = await _sharedAlbumService.deleteAlbum(albumId);
+
+    if (res) {
+      state = state.where((album) => album.id != albumId).toList();
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  Future<bool> leaveAlbum(String albumId) async {
+    var res = await _sharedAlbumService.leaveAlbum(albumId);
+
+    if (res) {
+      state = state.where((album) => album.id != albumId).toList();
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  Future<bool> removeAssetFromAlbum(String albumId, List<String> assetIds) async {
+    var res = await _sharedAlbumService.removeAssetFromAlbum(albumId, assetIds);
+
+    if (res) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+}
+
+final sharedAlbumProvider = StateNotifierProvider<SharedAlbumNotifier, List<SharedAlbum>>((ref) {
+  return SharedAlbumNotifier();
+});
+
+final sharedAlbumDetailProvider = FutureProvider.autoDispose.family<SharedAlbum, String>((ref, albumId) async {
+  final SharedAlbumService _sharedAlbumService = SharedAlbumService();
+
+  return await _sharedAlbumService.getAlbumDetail(albumId);
+});
diff --git a/mobile/lib/modules/sharing/providers/suggested_shared_users.provider.dart b/mobile/lib/modules/sharing/providers/suggested_shared_users.provider.dart
new file mode 100644
index 0000000000..64a82dfe78
--- /dev/null
+++ b/mobile/lib/modules/sharing/providers/suggested_shared_users.provider.dart
@@ -0,0 +1,9 @@
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/shared/models/user_info.model.dart';
+import 'package:immich_mobile/shared/services/user.service.dart';
+
+final suggestedSharedUsersProvider = FutureProvider.autoDispose<List<UserInfo>>((ref) async {
+  UserService userService = UserService();
+
+  return await userService.getAllUsersInfo();
+});
diff --git a/mobile/lib/modules/sharing/services/shared_album.service.dart b/mobile/lib/modules/sharing/services/shared_album.service.dart
new file mode 100644
index 0000000000..88e4398a58
--- /dev/null
+++ b/mobile/lib/modules/sharing/services/shared_album.service.dart
@@ -0,0 +1,141 @@
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:dio/dio.dart';
+import 'package:flutter/foundation.dart';
+import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:immich_mobile/shared/services/network.service.dart';
+
+class SharedAlbumService {
+  final NetworkService _networkService = NetworkService();
+
+  Future<List<SharedAlbum>> getAllSharedAlbum() async {
+    try {
+      var res = await _networkService.getRequest(url: 'shared/allSharedAlbums');
+      List<dynamic> decodedData = jsonDecode(res.toString());
+      List<SharedAlbum> result = List.from(decodedData.map((e) => SharedAlbum.fromMap(e)));
+
+      return result;
+    } catch (e) {
+      debugPrint("Error getAllSharedAlbum  ${e.toString()}");
+    }
+
+    return [];
+  }
+
+  Future<bool> createSharedAlbum(String albumName, Set<ImmichAsset> assets, List<String> sharedUserIds) async {
+    try {
+      var res = await _networkService.postRequest(url: 'shared/createAlbum', data: {
+        "albumName": albumName,
+        "sharedWithUserIds": sharedUserIds,
+        "assetIds": assets.map((asset) => asset.id).toList(),
+      });
+
+      if (res == null) {
+        return false;
+      }
+
+      return true;
+    } catch (e) {
+      debugPrint("Error createSharedAlbum  ${e.toString()}");
+      return false;
+    }
+  }
+
+  Future<SharedAlbum> getAlbumDetail(String albumId) async {
+    try {
+      var res = await _networkService.getRequest(url: 'shared/$albumId');
+      dynamic decodedData = jsonDecode(res.toString());
+      SharedAlbum result = SharedAlbum.fromMap(decodedData);
+
+      return result;
+    } catch (e) {
+      throw Exception('Error getAllSharedAlbum  ${e.toString()}');
+    }
+  }
+
+  Future<bool> addAdditionalAssetToAlbum(Set<ImmichAsset> assets, String albumId) async {
+    try {
+      var res = await _networkService.postRequest(url: 'shared/addAssets', data: {
+        "albumId": albumId,
+        "assetIds": assets.map((asset) => asset.id).toList(),
+      });
+
+      if (res == null) {
+        return false;
+      }
+
+      return true;
+    } catch (e) {
+      debugPrint("Error addAdditionalAssetToAlbum  ${e.toString()}");
+      return false;
+    }
+  }
+
+  Future<bool> addAdditionalUserToAlbum(List<String> sharedUserIds, String albumId) async {
+    try {
+      var res = await _networkService.postRequest(url: 'shared/addUsers', data: {
+        "albumId": albumId,
+        "sharedUserIds": sharedUserIds,
+      });
+
+      if (res == null) {
+        return false;
+      }
+
+      return true;
+    } catch (e) {
+      debugPrint("Error addAdditionalUserToAlbum  ${e.toString()}");
+      return false;
+    }
+  }
+
+  Future<bool> deleteAlbum(String albumId) async {
+    try {
+      Response res = await _networkService.deleteRequest(url: 'shared/$albumId');
+
+      if (res.statusCode != 200) {
+        return false;
+      }
+
+      return true;
+    } catch (e) {
+      debugPrint("Error deleteAlbum  ${e.toString()}");
+      return false;
+    }
+  }
+
+  Future<bool> leaveAlbum(String albumId) async {
+    try {
+      Response res = await _networkService.deleteRequest(url: 'shared/leaveAlbum/$albumId');
+
+      if (res.statusCode != 200) {
+        return false;
+      }
+
+      return true;
+    } catch (e) {
+      debugPrint("Error deleteAlbum  ${e.toString()}");
+      return false;
+    }
+  }
+
+  Future<bool> removeAssetFromAlbum(String albumId, List<String> assetIds) async {
+    try {
+      Response res = await _networkService.deleteRequest(url: 'shared/removeAssets/', data: {
+        "albumId": albumId,
+        "assetIds": assetIds,
+      });
+
+      if (res.statusCode != 200) {
+        return false;
+      }
+
+      return true;
+    } catch (e) {
+      debugPrint("Error deleteAlbum  ${e.toString()}");
+      return false;
+    }
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/album_action_outlined_button.dart b/mobile/lib/modules/sharing/ui/album_action_outlined_button.dart
new file mode 100644
index 0000000000..62be8e8552
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/album_action_outlined_button.dart
@@ -0,0 +1,36 @@
+import 'package:flutter/material.dart';
+
+class AlbumActionOutlinedButton extends StatelessWidget {
+  final VoidCallback? onPressed;
+  final String labelText;
+  final IconData iconData;
+
+  const AlbumActionOutlinedButton({Key? key, this.onPressed, required this.labelText, required this.iconData})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.only(right: 8.0),
+      child: OutlinedButton.icon(
+        style: ButtonStyle(
+          padding: MaterialStateProperty.all<EdgeInsets>(const EdgeInsets.symmetric(vertical: 0, horizontal: 10)),
+          shape: MaterialStateProperty.resolveWith<OutlinedBorder>(
+            (_) => RoundedRectangleBorder(
+              borderRadius: BorderRadius.circular(25),
+            ),
+          ),
+          side: MaterialStateProperty.resolveWith<BorderSide>(
+            (_) => const BorderSide(width: 1, color: Color.fromARGB(255, 158, 158, 158)),
+          ),
+        ),
+        icon: Icon(iconData, size: 15),
+        label: Text(
+          labelText,
+          style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87),
+        ),
+        onPressed: onPressed,
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/album_title_text_field.dart b/mobile/lib/modules/sharing/ui/album_title_text_field.dart
new file mode 100644
index 0000000000..f777e2863f
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/album_title_text_field.dart
@@ -0,0 +1,68 @@
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
+
+class AlbumTitleTextField extends ConsumerWidget {
+  const AlbumTitleTextField({
+    Key? key,
+    required this.isAlbumTitleEmpty,
+    required this.albumTitleTextFieldFocusNode,
+    required this.albumTitleController,
+    required this.isAlbumTitleTextFieldFocus,
+  }) : super(key: key);
+
+  final ValueNotifier<bool> isAlbumTitleEmpty;
+  final FocusNode albumTitleTextFieldFocusNode;
+  final TextEditingController albumTitleController;
+  final ValueNotifier<bool> isAlbumTitleTextFieldFocus;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return TextField(
+      onChanged: (v) {
+        if (v.isEmpty) {
+          isAlbumTitleEmpty.value = true;
+        } else {
+          isAlbumTitleEmpty.value = false;
+        }
+
+        ref.watch(albumTitleProvider.notifier).setAlbumTitle(v);
+      },
+      focusNode: albumTitleTextFieldFocusNode,
+      style: TextStyle(fontSize: 28, color: Colors.grey[700], fontWeight: FontWeight.bold),
+      controller: albumTitleController,
+      onTap: () {
+        isAlbumTitleTextFieldFocus.value = true;
+
+        if (albumTitleController.text == 'Untitled') {
+          albumTitleController.clear();
+        }
+      },
+      decoration: InputDecoration(
+        contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
+        suffixIcon: !isAlbumTitleEmpty.value && isAlbumTitleTextFieldFocus.value
+            ? IconButton(
+                onPressed: () {
+                  albumTitleController.clear();
+                  isAlbumTitleEmpty.value = true;
+                },
+                icon: const Icon(Icons.cancel_rounded),
+                splashRadius: 10,
+              )
+            : null,
+        enabledBorder: OutlineInputBorder(
+          borderSide: const BorderSide(color: Colors.transparent),
+          borderRadius: BorderRadius.circular(10),
+        ),
+        focusedBorder: OutlineInputBorder(
+          borderSide: const BorderSide(color: Colors.transparent),
+          borderRadius: BorderRadius.circular(10),
+        ),
+        hintText: 'Add a title',
+        focusColor: Colors.grey[300],
+        fillColor: Colors.grey[200],
+        filled: isAlbumTitleTextFieldFocus.value,
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/album_viewer_appbar.dart b/mobile/lib/modules/sharing/ui/album_viewer_appbar.dart
new file mode 100644
index 0000000000..83a627542d
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/album_viewer_appbar.dart
@@ -0,0 +1,181 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/constants/immich_colors.dart';
+import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
+import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/shared/ui/immich_toast.dart';
+import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
+
+class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
+  const AlbumViewerAppbar({
+    Key? key,
+    required AsyncValue<SharedAlbum> albumInfo,
+    required this.userId,
+    required this.albumId,
+  })  : _albumInfo = albumInfo,
+        super(key: key);
+
+  final AsyncValue<SharedAlbum> _albumInfo;
+  final String userId;
+  final String albumId;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final isMultiSelectionEnable = ref.watch(assetSelectionProvider).isMultiselectEnable;
+    final selectedAssetsInAlbum = ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
+
+    void _onDeleteAlbumPressed(String albumId) async {
+      ImmichLoadingOverlayController.appLoader.show();
+
+      bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId);
+
+      if (isSuccess) {
+        AutoRouter.of(context).navigate(const TabControllerRoute(children: [SharingRoute()]));
+      } else {
+        ImmichToast.show(
+          context: context,
+          msg: "Failed to delete album",
+          toastType: ToastType.error,
+          gravity: ToastGravity.BOTTOM,
+        );
+      }
+
+      ImmichLoadingOverlayController.appLoader.hide();
+    }
+
+    void _onLeaveAlbumPressed(String albumId) async {
+      ImmichLoadingOverlayController.appLoader.show();
+
+      bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId);
+
+      if (isSuccess) {
+        AutoRouter.of(context).navigate(const TabControllerRoute(children: [SharingRoute()]));
+      } else {
+        Navigator.pop(context);
+        ImmichToast.show(
+          context: context,
+          msg: "Failed to leave album",
+          toastType: ToastType.error,
+          gravity: ToastGravity.BOTTOM,
+        );
+      }
+
+      ImmichLoadingOverlayController.appLoader.hide();
+    }
+
+    void _onRemoveFromAlbumPressed(String albumId) async {
+      ImmichLoadingOverlayController.appLoader.show();
+
+      bool isSuccess = await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum(
+            albumId,
+            selectedAssetsInAlbum.map((a) => a.id).toList(),
+          );
+
+      if (isSuccess) {
+        Navigator.pop(context);
+        ref.watch(assetSelectionProvider.notifier).disableMultiselection();
+        ref.refresh(sharedAlbumDetailProvider(albumId));
+      } else {
+        Navigator.pop(context);
+        ImmichToast.show(
+          context: context,
+          msg: "There are problems in removing assets from album",
+          toastType: ToastType.error,
+          gravity: ToastGravity.BOTTOM,
+        );
+      }
+
+      ImmichLoadingOverlayController.appLoader.hide();
+    }
+
+    _buildBottomSheetActionButton() {
+      if (isMultiSelectionEnable) {
+        if (_albumInfo.asData?.value.ownerId == userId) {
+          return ListTile(
+            leading: const Icon(Icons.delete_sweep_rounded),
+            title: const Text(
+              'Remove from album',
+              style: TextStyle(fontWeight: FontWeight.bold),
+            ),
+            onTap: () => _onRemoveFromAlbumPressed(albumId),
+          );
+        } else {
+          return Container();
+        }
+      } else {
+        if (_albumInfo.asData?.value.ownerId == userId) {
+          return ListTile(
+            leading: const Icon(Icons.delete_forever_rounded),
+            title: const Text(
+              'Delete album',
+              style: TextStyle(fontWeight: FontWeight.bold),
+            ),
+            onTap: () => _onDeleteAlbumPressed(albumId),
+          );
+        } else {
+          return ListTile(
+            leading: const Icon(Icons.person_remove_rounded),
+            title: const Text(
+              'Leave album',
+              style: TextStyle(fontWeight: FontWeight.bold),
+            ),
+            onTap: () => _onLeaveAlbumPressed(albumId),
+          );
+        }
+      }
+    }
+
+    void _buildBottomSheet() {
+      showModalBottomSheet(
+        backgroundColor: immichBackgroundColor,
+        isScrollControlled: false,
+        context: context,
+        builder: (context) {
+          return Column(
+            mainAxisSize: MainAxisSize.min,
+            children: [
+              _buildBottomSheetActionButton(),
+            ],
+          );
+        },
+      );
+    }
+
+    _buildLeadingButton() {
+      if (isMultiSelectionEnable) {
+        return IconButton(
+          onPressed: () => ref.watch(assetSelectionProvider.notifier).disableMultiselection(),
+          icon: const Icon(Icons.close_rounded),
+          splashRadius: 25,
+        );
+      } else {
+        return IconButton(
+          onPressed: () async => await AutoRouter.of(context).pop(),
+          icon: const Icon(Icons.arrow_back_ios_rounded),
+          splashRadius: 25,
+        );
+      }
+    }
+
+    return AppBar(
+      elevation: 0,
+      leading: _buildLeadingButton(),
+      title: isMultiSelectionEnable ? Text(selectedAssetsInAlbum.length.toString()) : Container(),
+      centerTitle: false,
+      actions: [
+        IconButton(
+          splashRadius: 25,
+          onPressed: _buildBottomSheet,
+          icon: const Icon(Icons.more_horiz_rounded),
+        ),
+      ],
+    );
+  }
+
+  @override
+  Size get preferredSize => const Size.fromHeight(kToolbarHeight);
+}
diff --git a/mobile/lib/modules/sharing/ui/album_viewer_thumbnail.dart b/mobile/lib/modules/sharing/ui/album_viewer_thumbnail.dart
new file mode 100644
index 0000000000..cf7e99b71e
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/album_viewer_thumbnail.dart
@@ -0,0 +1,181 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.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/modules/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:immich_mobile/routing/router.dart';
+
+class AlbumViewerThumbnail extends HookConsumerWidget {
+  final ImmichAsset asset;
+
+  const AlbumViewerThumbnail({Key? key, required this.asset}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final cacheKey = useState(1);
+    var box = Hive.box(userInfoBox);
+    var thumbnailRequestUrl =
+        '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
+    var deviceId = ref.watch(authenticationProvider).deviceId;
+    final selectedAssetsInAlbumViewer = ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
+    final isMultiSelectionEnable = ref.watch(assetSelectionProvider).isMultiselectEnable;
+
+    _viewAsset() {
+      if (asset.type == 'IMAGE') {
+        AutoRouter.of(context).push(
+          ImageViewerRoute(
+            imageUrl:
+                '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false',
+            heroTag: asset.id,
+            thumbnailUrl: thumbnailRequestUrl,
+            asset: asset,
+          ),
+        );
+      } else {
+        AutoRouter.of(context).push(
+          VideoViewerRoute(
+              videoUrl: '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
+              asset: asset),
+        );
+      }
+    }
+
+    BoxBorder drawBorderColor() {
+      if (selectedAssetsInAlbumViewer.contains(asset)) {
+        return Border.all(
+          color: Theme.of(context).primaryColorLight,
+          width: 10,
+        );
+      } else {
+        return const Border();
+      }
+    }
+
+    _enableMultiSelection() {
+      ref.watch(assetSelectionProvider.notifier).enableMultiselection();
+      ref.watch(assetSelectionProvider.notifier).addAssetsInAlbumViewer([asset]);
+    }
+
+    _disableMultiSelection() {
+      ref.watch(assetSelectionProvider.notifier).disableMultiselection();
+    }
+
+    _buildVideoLabel() {
+      if (asset.type == 'IMAGE') {
+        return Container();
+      } else {
+        return Positioned(
+          top: 5,
+          right: 5,
+          child: Row(
+            children: [
+              Text(
+                asset.duration.toString().substring(0, 7),
+                style: const TextStyle(
+                  color: Colors.white,
+                  fontSize: 10,
+                ),
+              ),
+              const Icon(
+                Icons.play_circle_outline_rounded,
+                color: Colors.white,
+              ),
+            ],
+          ),
+        );
+      }
+    }
+
+    _buildAssetStoreLocationIcon() {
+      return Positioned(
+        right: 10,
+        bottom: 5,
+        child: Icon(
+          (deviceId != asset.deviceId) ? Icons.cloud_done_outlined : Icons.photo_library_rounded,
+          color: Colors.white,
+          size: 18,
+        ),
+      );
+    }
+
+    _buildAssetSelectionIcon() {
+      bool isSelected = selectedAssetsInAlbumViewer.contains(asset);
+      if (isMultiSelectionEnable) {
+        return Positioned(
+          left: 10,
+          top: 5,
+          child: isSelected
+              ? Icon(
+                  Icons.check_circle_rounded,
+                  color: Theme.of(context).primaryColor,
+                )
+              : const Icon(
+                  Icons.check_circle_outline_rounded,
+                  color: Colors.white,
+                ),
+        );
+      } else {
+        return Container();
+      }
+    }
+
+    _buildThumbnailImage() {
+      return Container(
+        decoration: BoxDecoration(border: drawBorderColor()),
+        child: CachedNetworkImage(
+          cacheKey: "${asset.id}-${cacheKey.value}",
+          width: 300,
+          height: 300,
+          memCacheHeight: 200,
+          fit: BoxFit.cover,
+          imageUrl: thumbnailRequestUrl,
+          httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
+          fadeInDuration: const Duration(milliseconds: 250),
+          progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
+            scale: 0.2,
+            child: CircularProgressIndicator(value: downloadProgress.progress),
+          ),
+          errorWidget: (context, url, error) {
+            return Icon(
+              Icons.image_not_supported_outlined,
+              color: Theme.of(context).primaryColor,
+            );
+          },
+        ),
+      );
+    }
+
+    _handleSelectionGesture() {
+      if (selectedAssetsInAlbumViewer.contains(asset)) {
+        ref.watch(assetSelectionProvider.notifier).removeAssetsInAlbumViewer([asset]);
+
+        if (selectedAssetsInAlbumViewer.isEmpty) {
+          _disableMultiSelection();
+        }
+      } else {
+        ref.watch(assetSelectionProvider.notifier).addAssetsInAlbumViewer([asset]);
+      }
+    }
+
+    return GestureDetector(
+      onTap: isMultiSelectionEnable ? _handleSelectionGesture : _viewAsset,
+      onLongPress: _enableMultiSelection,
+      child: Hero(
+        tag: asset.id,
+        child: Stack(
+          children: [
+            _buildThumbnailImage(),
+            _buildAssetStoreLocationIcon(),
+            _buildVideoLabel(),
+            _buildAssetSelectionIcon(),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/asset_grid_by_month.dart b/mobile/lib/modules/sharing/ui/asset_grid_by_month.dart
new file mode 100644
index 0000000000..acc4749d78
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/asset_grid_by_month.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/sharing/ui/selection_thumbnail_image.dart';
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+
+class AssetGridByMonth extends HookConsumerWidget {
+  final List<ImmichAsset> assetGroup;
+  const AssetGridByMonth({Key? key, required this.assetGroup}) : super(key: key);
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return SliverGrid(
+      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+        crossAxisCount: 4,
+        crossAxisSpacing: 5.0,
+        mainAxisSpacing: 5,
+      ),
+      delegate: SliverChildBuilderDelegate(
+        (BuildContext context, int index) {
+          return SelectionThumbnailImage(asset: assetGroup[index]);
+        },
+        childCount: assetGroup.length,
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/month_group_title.dart b/mobile/lib/modules/sharing/ui/month_group_title.dart
new file mode 100644
index 0000000000..297127aa51
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/month_group_title.dart
@@ -0,0 +1,92 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+
+class MonthGroupTitle extends HookConsumerWidget {
+  final String month;
+  final List<ImmichAsset> assetGroup;
+
+  const MonthGroupTitle({Key? key, required this.month, required this.assetGroup}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final selectedDateGroup = ref.watch(assetSelectionProvider).selectedMonths;
+    final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
+    final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
+
+    _handleTitleIconClick() {
+      HapticFeedback.heavyImpact();
+
+      if (isAlbumExist) {
+        if (selectedDateGroup.contains(month)) {
+          ref.watch(assetSelectionProvider.notifier).removeAssetsInMonth(month, []);
+          ref.watch(assetSelectionProvider.notifier).removeSelectedAdditionalAssets(assetGroup);
+        } else {
+          ref.watch(assetSelectionProvider.notifier).addAllAssetsInMonth(month, []);
+
+          // Deep clone assetGroup
+          var assetGroupWithNewItems = [...assetGroup];
+
+          for (var selectedAsset in selectedAssets) {
+            assetGroupWithNewItems.removeWhere((a) => a.id == selectedAsset.id);
+          }
+
+          ref.watch(assetSelectionProvider.notifier).addAdditionalAssets(assetGroupWithNewItems);
+        }
+      } else {
+        if (selectedDateGroup.contains(month)) {
+          ref.watch(assetSelectionProvider.notifier).removeAssetsInMonth(month, assetGroup);
+        } else {
+          ref.watch(assetSelectionProvider.notifier).addAllAssetsInMonth(month, assetGroup);
+        }
+      }
+    }
+
+    _getSimplifiedMonth() {
+      var monthAndYear = month.split(',');
+      var yearText = monthAndYear[1].trim();
+      var monthText = monthAndYear[0].trim();
+      var currentYear = DateTime.now().year.toString();
+
+      if (yearText == currentYear) {
+        return monthText;
+      } else {
+        return month;
+      }
+    }
+
+    return SliverToBoxAdapter(
+      child: Padding(
+        padding: const EdgeInsets.only(top: 29.0, bottom: 29.0, left: 14.0, right: 8.0),
+        child: Row(
+          children: [
+            GestureDetector(
+              onTap: _handleTitleIconClick,
+              child: selectedDateGroup.contains(month)
+                  ? Icon(
+                      Icons.check_circle_rounded,
+                      color: Theme.of(context).primaryColor,
+                    )
+                  : const Icon(
+                      Icons.circle_outlined,
+                      color: Colors.grey,
+                    ),
+            ),
+            Padding(
+              padding: const EdgeInsets.only(left: 8.0),
+              child: Text(
+                _getSimplifiedMonth(),
+                style: TextStyle(
+                  fontSize: 24,
+                  color: Theme.of(context).primaryColor,
+                ),
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/selection_thumbnail_image.dart b/mobile/lib/modules/sharing/ui/selection_thumbnail_image.dart
new file mode 100644
index 0000000000..33831a6587
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/selection_thumbnail_image.dart
@@ -0,0 +1,149 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.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/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+
+class SelectionThumbnailImage extends HookConsumerWidget {
+  final ImmichAsset asset;
+
+  const SelectionThumbnailImage({Key? key, required this.asset}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final cacheKey = useState(1);
+    var box = Hive.box(userInfoBox);
+    var thumbnailRequestUrl =
+        '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
+    var selectedAsset = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
+    var newAssetsForAlbum = ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
+    var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
+
+    Widget _buildSelectionIcon(ImmichAsset asset) {
+      if (selectedAsset.contains(asset) && !isAlbumExist) {
+        return Icon(
+          Icons.check_circle,
+          color: Theme.of(context).primaryColor,
+        );
+      } else if (selectedAsset.contains(asset) && isAlbumExist) {
+        return const Icon(
+          Icons.check_circle,
+          color: Color.fromARGB(255, 233, 233, 233),
+        );
+      } else if (newAssetsForAlbum.contains(asset) && isAlbumExist) {
+        return Icon(
+          Icons.check_circle,
+          color: Theme.of(context).primaryColor,
+        );
+      } else {
+        return const Icon(
+          Icons.circle_outlined,
+          color: Colors.white,
+        );
+      }
+    }
+
+    BoxBorder drawBorderColor() {
+      if (selectedAsset.contains(asset) && !isAlbumExist) {
+        return Border.all(
+          color: Theme.of(context).primaryColorLight,
+          width: 10,
+        );
+      } else if (selectedAsset.contains(asset) && isAlbumExist) {
+        return Border.all(
+          color: const Color.fromARGB(255, 190, 190, 190),
+          width: 10,
+        );
+      } else if (newAssetsForAlbum.contains(asset) && isAlbumExist) {
+        return Border.all(
+          color: Theme.of(context).primaryColorLight,
+          width: 10,
+        );
+      }
+      return const Border();
+    }
+
+    return GestureDetector(
+      onTap: () {
+        if (isAlbumExist) {
+          // Operation for existing album
+          if (!selectedAsset.contains(asset)) {
+            if (newAssetsForAlbum.contains(asset)) {
+              ref.watch(assetSelectionProvider.notifier).removeSelectedAdditionalAssets([asset]);
+            } else {
+              ref.watch(assetSelectionProvider.notifier).addAdditionalAssets([asset]);
+            }
+          }
+        } else {
+          // Operation for new album
+          if (selectedAsset.contains(asset)) {
+            ref.watch(assetSelectionProvider.notifier).removeSelectedNewAssets([asset]);
+          } else {
+            ref.watch(assetSelectionProvider.notifier).addNewAssets([asset]);
+          }
+        }
+      },
+      child: Hero(
+        tag: asset.id,
+        child: Stack(
+          children: [
+            Container(
+              decoration: BoxDecoration(border: drawBorderColor()),
+              child: CachedNetworkImage(
+                cacheKey: "${asset.id}-${cacheKey.value}",
+                width: 150,
+                height: 150,
+                memCacheHeight: asset.type == 'IMAGE' ? 150 : 150,
+                fit: BoxFit.cover,
+                imageUrl: thumbnailRequestUrl,
+                httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
+                fadeInDuration: const Duration(milliseconds: 250),
+                progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
+                  scale: 0.2,
+                  child: CircularProgressIndicator(value: downloadProgress.progress),
+                ),
+                errorWidget: (context, url, error) {
+                  return Icon(
+                    Icons.image_not_supported_outlined,
+                    color: Theme.of(context).primaryColor,
+                  );
+                },
+              ),
+            ),
+            Padding(
+              padding: const EdgeInsets.all(3.0),
+              child: Align(
+                alignment: Alignment.topLeft,
+                child: _buildSelectionIcon(asset),
+              ),
+            ),
+            asset.type == 'IMAGE'
+                ? Container()
+                : Positioned(
+                    bottom: 5,
+                    right: 5,
+                    child: Row(
+                      children: [
+                        Text(
+                          asset.duration.toString().substring(0, 7),
+                          style: const TextStyle(
+                            color: Colors.white,
+                            fontSize: 10,
+                          ),
+                        ),
+                        const Icon(
+                          Icons.play_circle_outline_rounded,
+                          color: Colors.white,
+                        ),
+                      ],
+                    ),
+                  )
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/shared_album_thumbnail_image.dart b/mobile/lib/modules/sharing/ui/shared_album_thumbnail_image.dart
new file mode 100644
index 0000000000..01971a6494
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/shared_album_thumbnail_image.dart
@@ -0,0 +1,55 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.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/shared/models/immich_asset.model.dart';
+
+class SharedAlbumThumbnailImage extends HookConsumerWidget {
+  final ImmichAsset asset;
+
+  const SharedAlbumThumbnailImage({Key? key, required this.asset}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final cacheKey = useState(1);
+
+    var box = Hive.box(userInfoBox);
+    var thumbnailRequestUrl =
+        '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
+
+    return GestureDetector(
+      onTap: () {
+        // debugPrint("View ${asset.id}");
+      },
+      child: Hero(
+        tag: asset.id,
+        child: Stack(
+          children: [
+            CachedNetworkImage(
+              cacheKey: "${asset.id}-${cacheKey.value}",
+              width: 500,
+              height: 500,
+              memCacheHeight: asset.type == 'IMAGE' ? 500 : 500,
+              fit: BoxFit.cover,
+              imageUrl: thumbnailRequestUrl,
+              httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
+              fadeInDuration: const Duration(milliseconds: 250),
+              progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
+                scale: 0.2,
+                child: CircularProgressIndicator(value: downloadProgress.progress),
+              ),
+              errorWidget: (context, url, error) {
+                return Icon(
+                  Icons.image_not_supported_outlined,
+                  color: Theme.of(context).primaryColor,
+                );
+              },
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/ui/sharing_sliver_appbar.dart b/mobile/lib/modules/sharing/ui/sharing_sliver_appbar.dart
new file mode 100644
index 0000000000..baf0029273
--- /dev/null
+++ b/mobile/lib/modules/sharing/ui/sharing_sliver_appbar.dart
@@ -0,0 +1,83 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:immich_mobile/routing/router.dart';
+
+class SharingSliverAppBar extends StatelessWidget {
+  const SharingSliverAppBar({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SliverAppBar(
+      centerTitle: true,
+      floating: false,
+      pinned: true,
+      snap: false,
+      leading: Container(),
+      // elevation: 0,
+      title: Text(
+        'IMMICH',
+        style: TextStyle(
+          fontFamily: 'SnowburstOne',
+          fontWeight: FontWeight.bold,
+          fontSize: 22,
+          color: Theme.of(context).primaryColor,
+        ),
+      ),
+      bottom: PreferredSize(
+        preferredSize: const Size.fromHeight(50.0),
+        child: Padding(
+          padding: const EdgeInsets.symmetric(horizontal: 12.0),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              Expanded(
+                child: Padding(
+                  padding: const EdgeInsets.only(right: 4.0),
+                  child: TextButton.icon(
+                    style: ButtonStyle(
+                      backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor.withAlpha(20)),
+                      // foregroundColor: MaterialStateProperty.all(Colors.white),
+                    ),
+                    onPressed: () {
+                      AutoRouter.of(context).push(const CreateSharedAlbumRoute());
+                    },
+                    icon: const Icon(
+                      Icons.photo_album_outlined,
+                      size: 20,
+                    ),
+                    label: const Text(
+                      "Create shared album",
+                      style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
+                    ),
+                  ),
+                ),
+              ),
+              Expanded(
+                child: Padding(
+                  padding: const EdgeInsets.only(left: 4.0),
+                  child: TextButton.icon(
+                    style: ButtonStyle(
+                      backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor.withAlpha(20)),
+                      // foregroundColor: MaterialStateProperty.all(Colors.white),
+                    ),
+                    onPressed: null,
+                    icon: const Icon(
+                      Icons.swap_horizontal_circle_outlined,
+                      size: 20,
+                    ),
+                    label: const Text(
+                      "Share with partner",
+                      style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
+                    ),
+                  ),
+                ),
+              )
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/views/album_viewer_page.dart b/mobile/lib/modules/sharing/views/album_viewer_page.dart
new file mode 100644
index 0000000000..3077eb89e5
--- /dev/null
+++ b/mobile/lib/modules/sharing/views/album_viewer_page.dart
@@ -0,0 +1,245 @@
+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/constants/immich_colors.dart';
+import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
+import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
+import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
+import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
+import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
+import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
+import 'package:immich_mobile/modules/sharing/ui/album_action_outlined_button.dart';
+import 'package:immich_mobile/modules/sharing/ui/album_viewer_appbar.dart';
+import 'package:immich_mobile/modules/sharing/ui/album_viewer_thumbnail.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
+import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
+import 'package:intl/intl.dart';
+
+class AlbumViewerPage extends HookConsumerWidget {
+  final String albumId;
+
+  const AlbumViewerPage({Key? key, required this.albumId}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    ScrollController _scrollController = useScrollController();
+    AsyncValue<SharedAlbum> _albumInfo = ref.watch(sharedAlbumDetailProvider(albumId));
+
+    final userId = ref.watch(authenticationProvider).userId;
+
+    /// Find out if the assets in album exist on the device
+    /// If they exist, add to selected asset state to show they are already selected.
+    void _onAddPhotosPressed(SharedAlbum albumInfo) async {
+      if (albumInfo.sharedAssets != null && albumInfo.sharedAssets!.isNotEmpty) {
+        ref
+            .watch(assetSelectionProvider.notifier)
+            .addNewAssets(albumInfo.sharedAssets!.map((e) => e.assetInfo).toList());
+      }
+
+      ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(true);
+
+      AssetSelectionPageResult? returnPayload =
+          await AutoRouter.of(context).push<AssetSelectionPageResult?>(const AssetSelectionRoute());
+
+      if (returnPayload != null) {
+        // Check if there is new assets add
+        if (returnPayload.selectedAdditionalAsset.isNotEmpty) {
+          ImmichLoadingOverlayController.appLoader.show();
+
+          var isSuccess =
+              await SharedAlbumService().addAdditionalAssetToAlbum(returnPayload.selectedAdditionalAsset, albumId);
+
+          if (isSuccess) {
+            ref.refresh(sharedAlbumDetailProvider(albumId));
+          }
+
+          ImmichLoadingOverlayController.appLoader.hide();
+        }
+
+        ref.watch(assetSelectionProvider.notifier).removeAll();
+      } else {
+        ref.watch(assetSelectionProvider.notifier).removeAll();
+      }
+    }
+
+    void _onAddUsersPressed(SharedAlbum albumInfo) async {
+      List<String>? sharedUserIds =
+          await AutoRouter.of(context).push<List<String>?>(SelectAdditionalUserForSharingRoute(albumInfo: albumInfo));
+
+      if (sharedUserIds != null) {
+        ImmichLoadingOverlayController.appLoader.show();
+
+        var isSuccess = await SharedAlbumService().addAdditionalUserToAlbum(sharedUserIds, albumId);
+
+        if (isSuccess) {
+          ref.refresh(sharedAlbumDetailProvider(albumId));
+        }
+
+        ImmichLoadingOverlayController.appLoader.hide();
+      }
+    }
+
+    Widget _buildTitle(String title) {
+      return Padding(
+        padding: const EdgeInsets.only(left: 16.0, top: 16),
+        child: Text(
+          title,
+          style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
+        ),
+      );
+    }
+
+    Widget _buildAlbumDateRange(SharedAlbum albumInfo) {
+      if (albumInfo.sharedAssets != null && albumInfo.sharedAssets!.isNotEmpty) {
+        String startDate = "";
+        DateTime parsedStartDate = DateTime.parse(albumInfo.sharedAssets!.first.assetInfo.createdAt);
+        DateTime parsedEndDate = DateTime.parse(albumInfo.sharedAssets!.last.assetInfo.createdAt);
+
+        if (parsedStartDate.year == parsedEndDate.year) {
+          startDate = DateFormat('LLL d').format(parsedStartDate);
+        } else {
+          startDate = DateFormat('LLL d, y').format(parsedStartDate);
+        }
+
+        String endDate = DateFormat('LLL d, y').format(parsedEndDate);
+
+        return Padding(
+          padding: const EdgeInsets.only(left: 16.0, top: 8),
+          child: Text(
+            "$startDate-$endDate",
+            style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey),
+          ),
+        );
+      } else {
+        return Container();
+      }
+    }
+
+    Widget _buildHeader(SharedAlbum albumInfo) {
+      return SliverToBoxAdapter(
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            _buildTitle(albumInfo.albumName),
+            _buildAlbumDateRange(albumInfo),
+            SizedBox(
+              height: 60,
+              child: ListView.builder(
+                padding: const EdgeInsets.only(left: 16),
+                scrollDirection: Axis.horizontal,
+                itemBuilder: ((context, index) {
+                  return Padding(
+                    padding: const EdgeInsets.only(right: 8.0),
+                    child: CircleAvatar(
+                      backgroundColor: Colors.grey[300],
+                      radius: 18,
+                      child: Padding(
+                        padding: const EdgeInsets.all(2.0),
+                        child: ClipRRect(
+                          child: Image.asset('assets/immich-logo-no-outline.png'),
+                          borderRadius: BorderRadius.circular(50.0),
+                        ),
+                      ),
+                    ),
+                  );
+                }),
+                itemCount: albumInfo.sharedUsers.length,
+              ),
+            )
+          ],
+        ),
+      );
+    }
+
+    Widget _buildImageGrid(SharedAlbum albumInfo) {
+      if (albumInfo.sharedAssets != null && albumInfo.sharedAssets!.isNotEmpty) {
+        return SliverPadding(
+          padding: const EdgeInsets.only(top: 10.0),
+          sliver: SliverGrid(
+            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+              crossAxisCount: 3,
+              crossAxisSpacing: 5.0,
+              mainAxisSpacing: 5,
+            ),
+            delegate: SliverChildBuilderDelegate(
+              (BuildContext context, int index) {
+                return AlbumViewerThumbnail(asset: albumInfo.sharedAssets![index].assetInfo);
+              },
+              childCount: albumInfo.sharedAssets?.length,
+            ),
+          ),
+        );
+      }
+      return const SliverToBoxAdapter();
+    }
+
+    Widget _buildControlButton(SharedAlbum albumInfo) {
+      return Padding(
+        padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
+        child: SizedBox(
+          height: 40,
+          child: ListView(
+            scrollDirection: Axis.horizontal,
+            children: [
+              AlbumActionOutlinedButton(
+                iconData: Icons.add_photo_alternate_outlined,
+                onPressed: () => _onAddPhotosPressed(albumInfo),
+                labelText: "Add photos",
+              ),
+              userId == albumInfo.ownerId
+                  ? AlbumActionOutlinedButton(
+                      iconData: Icons.person_add_alt_rounded,
+                      onPressed: () => _onAddUsersPressed(albumInfo),
+                      labelText: "Add users",
+                    )
+                  : Container(),
+            ],
+          ),
+        ),
+      );
+    }
+
+    Widget _buildBody(SharedAlbum albumInfo) {
+      return Stack(children: [
+        DraggableScrollbar.semicircle(
+          backgroundColor: Theme.of(context).primaryColor,
+          controller: _scrollController,
+          heightScrollThumb: 48.0,
+          child: CustomScrollView(
+            controller: _scrollController,
+            slivers: [
+              _buildHeader(albumInfo),
+              SliverPersistentHeader(
+                pinned: true,
+                delegate: ImmichSliverPersistentAppBarDelegate(
+                  minHeight: 50,
+                  maxHeight: 50,
+                  child: Container(
+                    color: immichBackgroundColor,
+                    child: _buildControlButton(albumInfo),
+                  ),
+                ),
+              ),
+              _buildImageGrid(albumInfo)
+            ],
+          ),
+        ),
+      ]);
+    }
+
+    return Scaffold(
+      appBar: AlbumViewerAppbar(albumInfo: _albumInfo, userId: userId, albumId: albumId),
+      body: _albumInfo.when(
+        data: (albumInfo) => _buildBody(albumInfo),
+        error: (e, _) => Center(child: Text("Error loading album info $e")),
+        loading: () => const Center(
+          child: ImmichLoadingIndicator(),
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/views/asset_selection_page.dart b/mobile/lib/modules/sharing/views/asset_selection_page.dart
new file mode 100644
index 0000000000..5147e71f1f
--- /dev/null
+++ b/mobile/lib/modules/sharing/views/asset_selection_page.dart
@@ -0,0 +1,95 @@
+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/modules/sharing/models/asset_selection_page_result.model.dart';
+import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/modules/sharing/ui/asset_grid_by_month.dart';
+import 'package:immich_mobile/modules/sharing/ui/month_group_title.dart';
+import 'package:immich_mobile/shared/providers/asset.provider.dart';
+import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
+
+class AssetSelectionPage extends HookConsumerWidget {
+  const AssetSelectionPage({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    ScrollController _scrollController = useScrollController();
+    var assetGroupMonthYear = ref.watch(assetGroupByMonthYearProvider);
+    final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
+    final newAssetsForAlbum = ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
+    final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
+
+    List<Widget> _imageGridGroup = [];
+
+    String _buildAssetCountText() {
+      if (isAlbumExist) {
+        return (selectedAssets.length + newAssetsForAlbum.length).toString();
+      } else {
+        return selectedAssets.length.toString();
+      }
+    }
+
+    Widget _buildBody() {
+      assetGroupMonthYear.forEach((monthYear, assetGroup) {
+        _imageGridGroup.add(MonthGroupTitle(month: monthYear, assetGroup: assetGroup));
+        _imageGridGroup.add(AssetGridByMonth(assetGroup: assetGroup));
+      });
+
+      return Stack(
+        children: [
+          DraggableScrollbar.semicircle(
+            backgroundColor: Theme.of(context).primaryColor,
+            controller: _scrollController,
+            heightScrollThumb: 48.0,
+            child: CustomScrollView(
+              controller: _scrollController,
+              slivers: [..._imageGridGroup],
+            ),
+          ),
+        ],
+      );
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        elevation: 0,
+        leading: IconButton(
+          icon: const Icon(Icons.close_rounded),
+          onPressed: () {
+            ref.watch(assetSelectionProvider.notifier).removeAll();
+            AutoRouter.of(context).pop(null);
+          },
+        ),
+        title: selectedAssets.isEmpty
+            ? const Text(
+                'Add photos',
+                style: TextStyle(fontSize: 18),
+              )
+            : Text(
+                _buildAssetCountText(),
+                style: const TextStyle(fontSize: 18),
+              ),
+        centerTitle: false,
+        actions: [
+          (!isAlbumExist && selectedAssets.isNotEmpty) || (isAlbumExist && newAssetsForAlbum.isNotEmpty)
+              ? TextButton(
+                  onPressed: () {
+                    var payload = AssetSelectionPageResult(
+                      isAlbumExist: isAlbumExist,
+                      selectedAdditionalAsset: newAssetsForAlbum,
+                      selectedNewAsset: selectedAssets,
+                    );
+                    AutoRouter.of(context).pop(payload);
+                  },
+                  child: const Text(
+                    "Add",
+                    style: TextStyle(fontWeight: FontWeight.bold),
+                  ),
+                )
+              : Container()
+        ],
+      ),
+      body: _buildBody(),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/views/create_shared_album_page.dart b/mobile/lib/modules/sharing/views/create_shared_album_page.dart
new file mode 100644
index 0000000000..747ee8e9b4
--- /dev/null
+++ b/mobile/lib/modules/sharing/views/create_shared_album_page.dart
@@ -0,0 +1,208 @@
+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/modules/sharing/models/asset_selection_page_result.model.dart';
+import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
+import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/modules/sharing/ui/album_action_outlined_button.dart';
+import 'package:immich_mobile/modules/sharing/ui/album_title_text_field.dart';
+import 'package:immich_mobile/modules/sharing/ui/shared_album_thumbnail_image.dart';
+import 'package:immich_mobile/routing/router.dart';
+
+class CreateSharedAlbumPage extends HookConsumerWidget {
+  const CreateSharedAlbumPage({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final albumTitleController = useTextEditingController.fromValue(TextEditingValue.empty);
+    final albumTitleTextFieldFocusNode = useFocusNode();
+    final isAlbumTitleTextFieldFocus = useState(false);
+    final isAlbumTitleEmpty = useState(true);
+    final selectedAssets = ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
+
+    _showSelectUserPage() {
+      AutoRouter.of(context).push(const SelectUserForSharingRoute());
+    }
+
+    void _onBackgroundTapped() {
+      albumTitleTextFieldFocusNode.unfocus();
+      isAlbumTitleTextFieldFocus.value = false;
+
+      if (albumTitleController.text.isEmpty) {
+        albumTitleController.text = 'Untitled';
+        ref.watch(albumTitleProvider.notifier).setAlbumTitle('Untitled');
+      }
+    }
+
+    _onSelectPhotosButtonPressed() async {
+      ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(false);
+
+      AssetSelectionPageResult? selectedAsset =
+          await AutoRouter.of(context).push<AssetSelectionPageResult?>(const AssetSelectionRoute());
+
+      if (selectedAsset == null) {
+        ref.watch(assetSelectionProvider.notifier).removeAll();
+      }
+    }
+
+    _buildTitleInputField() {
+      return Padding(
+        padding: const EdgeInsets.only(
+          right: 10,
+          left: 10,
+        ),
+        child: AlbumTitleTextField(
+            isAlbumTitleEmpty: isAlbumTitleEmpty,
+            albumTitleTextFieldFocusNode: albumTitleTextFieldFocusNode,
+            albumTitleController: albumTitleController,
+            isAlbumTitleTextFieldFocus: isAlbumTitleTextFieldFocus),
+      );
+    }
+
+    _buildTitle() {
+      if (selectedAssets.isEmpty) {
+        return const SliverToBoxAdapter(
+          child: Padding(
+            padding: EdgeInsets.only(top: 200, left: 18),
+            child: Text(
+              'ADD ASSETS',
+              style: TextStyle(fontSize: 12),
+            ),
+          ),
+        );
+      }
+
+      return const SliverToBoxAdapter();
+    }
+
+    _buildSelectPhotosButton() {
+      if (selectedAssets.isEmpty) {
+        return SliverToBoxAdapter(
+          child: Padding(
+            padding: const EdgeInsets.only(top: 16, left: 18, right: 18),
+            child: OutlinedButton.icon(
+              style: ButtonStyle(
+                alignment: Alignment.centerLeft,
+                padding:
+                    MaterialStateProperty.all<EdgeInsets>(const EdgeInsets.symmetric(vertical: 22, horizontal: 16)),
+              ),
+              onPressed: _onSelectPhotosButtonPressed,
+              icon: const Icon(Icons.add_rounded),
+              label: Padding(
+                padding: const EdgeInsets.only(left: 8.0),
+                child: Text(
+                  'Select Photos',
+                  style: TextStyle(fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.bold),
+                ),
+              ),
+            ),
+          ),
+        );
+      }
+
+      return const SliverToBoxAdapter();
+    }
+
+    _buildControlButton() {
+      if (selectedAssets.isNotEmpty) {
+        return Padding(
+          padding: const EdgeInsets.only(left: 12.0, top: 16, bottom: 16),
+          child: SizedBox(
+            height: 30,
+            child: ListView(
+              scrollDirection: Axis.horizontal,
+              children: [
+                AlbumActionOutlinedButton(
+                  iconData: Icons.add_photo_alternate_outlined,
+                  onPressed: _onSelectPhotosButtonPressed,
+                  labelText: "Add photos",
+                ),
+              ],
+            ),
+          ),
+        );
+      }
+
+      return Container();
+    }
+
+    _buildSelectedImageGrid() {
+      if (selectedAssets.isNotEmpty) {
+        return SliverPadding(
+          padding: const EdgeInsets.only(top: 16),
+          sliver: SliverGrid(
+            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+              crossAxisCount: 3,
+              crossAxisSpacing: 5.0,
+              mainAxisSpacing: 5,
+            ),
+            delegate: SliverChildBuilderDelegate(
+              (BuildContext context, int index) {
+                return GestureDetector(
+                  onTap: _onBackgroundTapped,
+                  child: SharedAlbumThumbnailImage(asset: selectedAssets.toList()[index]),
+                );
+              },
+              childCount: selectedAssets.length,
+            ),
+          ),
+        );
+      }
+
+      return const SliverToBoxAdapter();
+    }
+
+    return Scaffold(
+        appBar: AppBar(
+          elevation: 0,
+          centerTitle: false,
+          leading: IconButton(
+              onPressed: () {
+                ref.watch(assetSelectionProvider.notifier).removeAll();
+                AutoRouter.of(context).pop();
+              },
+              icon: const Icon(Icons.close_rounded)),
+          title: const Text(
+            'Create album',
+            style: TextStyle(color: Colors.black),
+          ),
+          actions: [
+            TextButton(
+              onPressed: albumTitleController.text.isNotEmpty ? _showSelectUserPage : null,
+              child: const Text(
+                'Share',
+                style: TextStyle(
+                  fontWeight: FontWeight.bold,
+                ),
+              ),
+            ),
+          ],
+        ),
+        body: GestureDetector(
+          onTap: _onBackgroundTapped,
+          child: CustomScrollView(
+            slivers: [
+              SliverAppBar(
+                elevation: 5,
+                leading: Container(),
+                pinned: true,
+                floating: false,
+                bottom: PreferredSize(
+                  child: Column(
+                    children: [
+                      _buildTitleInputField(),
+                      _buildControlButton(),
+                    ],
+                  ),
+                  preferredSize: const Size.fromHeight(66.0),
+                ),
+              ),
+              _buildTitle(),
+              _buildSelectPhotosButton(),
+              _buildSelectedImageGrid(),
+            ],
+          ),
+        ));
+  }
+}
diff --git a/mobile/lib/modules/sharing/views/select_additional_user_for_sharing_page.dart b/mobile/lib/modules/sharing/views/select_additional_user_for_sharing_page.dart
new file mode 100644
index 0000000000..388af8180a
--- /dev/null
+++ b/mobile/lib/modules/sharing/views/select_additional_user_for_sharing_page.dart
@@ -0,0 +1,135 @@
+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/modules/sharing/models/shared_album.model.dart';
+import 'package:immich_mobile/modules/sharing/providers/suggested_shared_users.provider.dart';
+import 'package:immich_mobile/shared/models/user_info.model.dart';
+import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+
+class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
+  final SharedAlbum albumInfo;
+
+  const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    AsyncValue<List<UserInfo>> suggestedShareUsers = ref.watch(suggestedSharedUsersProvider);
+    final sharedUsersList = useState<Set<UserInfo>>({});
+
+    _addNewUsersHandler() {
+      AutoRouter.of(context).pop(sharedUsersList.value.map((e) => e.id).toList());
+    }
+
+    _buildTileIcon(UserInfo user) {
+      if (sharedUsersList.value.contains(user)) {
+        return CircleAvatar(
+          backgroundColor: Theme.of(context).primaryColor,
+          child: const Icon(
+            Icons.check_rounded,
+            size: 25,
+          ),
+        );
+      } else {
+        return CircleAvatar(
+          backgroundImage: const AssetImage('assets/immich-logo-no-outline.png'),
+          backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
+        );
+      }
+    }
+
+    _buildUserList(List<UserInfo> users) {
+      List<Widget> usersChip = [];
+
+      for (var user in sharedUsersList.value) {
+        usersChip.add(
+          Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 8.0),
+            child: Chip(
+              backgroundColor: Theme.of(context).primaryColor.withOpacity(0.15),
+              label: Text(
+                user.email,
+                style: const TextStyle(fontSize: 12, color: Colors.black87, fontWeight: FontWeight.bold),
+              ),
+            ),
+          ),
+        );
+      }
+      return Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Wrap(
+            children: [...usersChip],
+          ),
+          const Padding(
+            padding: EdgeInsets.all(16.0),
+            child: Text(
+              'Suggestions',
+              style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold),
+            ),
+          ),
+          ListView.builder(
+            shrinkWrap: true,
+            itemBuilder: ((context, index) {
+              return ListTile(
+                leading: _buildTileIcon(users[index]),
+                title: Text(
+                  users[index].email,
+                  style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+                ),
+                onTap: () {
+                  if (sharedUsersList.value.contains(users[index])) {
+                    sharedUsersList.value =
+                        sharedUsersList.value.where((selectedUser) => selectedUser.id != users[index].id).toSet();
+                  } else {
+                    sharedUsersList.value = {...sharedUsersList.value, users[index]};
+                  }
+                },
+              );
+            }),
+            itemCount: users.length,
+          ),
+        ],
+      );
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text(
+          'Invite to album',
+          style: TextStyle(color: Colors.black),
+        ),
+        elevation: 0,
+        centerTitle: false,
+        leading: IconButton(
+          icon: const Icon(Icons.close_rounded),
+          onPressed: () {
+            AutoRouter.of(context).pop(null);
+          },
+        ),
+        actions: [
+          TextButton(
+            onPressed: sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
+            child: const Text(
+              "Add",
+              style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+            ),
+          )
+        ],
+      ),
+      body: suggestedShareUsers.when(
+        data: (users) {
+          for (var sharedUsers in albumInfo.sharedUsers) {
+            users.removeWhere((u) => u.id == sharedUsers.sharedUserId || u.id == albumInfo.ownerId);
+          }
+
+          return _buildUserList(users);
+        },
+        error: (e, _) => Text("Error loading suggested users $e"),
+        loading: () => const Center(
+          child: ImmichLoadingIndicator(),
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/views/select_user_for_sharing_page.dart b/mobile/lib/modules/sharing/views/select_user_for_sharing_page.dart
new file mode 100644
index 0000000000..0c1168376a
--- /dev/null
+++ b/mobile/lib/modules/sharing/views/select_user_for_sharing_page.dart
@@ -0,0 +1,145 @@
+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/modules/sharing/providers/album_title.provider.dart';
+import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
+import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
+import 'package:immich_mobile/modules/sharing/providers/suggested_shared_users.provider.dart';
+import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/shared/models/user_info.model.dart';
+import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+
+class SelectUserForSharingPage extends HookConsumerWidget {
+  const SelectUserForSharingPage({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final sharedUsersList = useState<Set<UserInfo>>({});
+    AsyncValue<List<UserInfo>> suggestedShareUsers = ref.watch(suggestedSharedUsersProvider);
+
+    _createSharedAlbum() async {
+      var isSuccess = await SharedAlbumService().createSharedAlbum(
+        ref.watch(albumTitleProvider),
+        ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
+        sharedUsersList.value.map((userInfo) => userInfo.id).toList(),
+      );
+
+      if (isSuccess) {
+        await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
+        ref.watch(assetSelectionProvider.notifier).removeAll();
+        ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
+
+        AutoRouter.of(context).navigate(const TabControllerRoute(children: [SharingRoute()]));
+      }
+
+      const ScaffoldMessenger(child: SnackBar(content: Text('Failed to create album')));
+    }
+
+    _buildTileIcon(UserInfo user) {
+      if (sharedUsersList.value.contains(user)) {
+        return CircleAvatar(
+          backgroundColor: Theme.of(context).primaryColor,
+          child: const Icon(
+            Icons.check_rounded,
+            size: 25,
+          ),
+        );
+      } else {
+        return CircleAvatar(
+          backgroundImage: const AssetImage('assets/immich-logo-no-outline.png'),
+          backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
+        );
+      }
+    }
+
+    _buildUserList(List<UserInfo> users) {
+      List<Widget> usersChip = [];
+
+      for (var user in sharedUsersList.value) {
+        usersChip.add(
+          Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 8.0),
+            child: Chip(
+              backgroundColor: Theme.of(context).primaryColor.withOpacity(0.15),
+              label: Text(
+                user.email,
+                style: const TextStyle(fontSize: 12, color: Colors.black87, fontWeight: FontWeight.bold),
+              ),
+            ),
+          ),
+        );
+      }
+      return Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Wrap(
+            children: [...usersChip],
+          ),
+          const Padding(
+            padding: EdgeInsets.all(16.0),
+            child: Text(
+              'Suggestions',
+              style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold),
+            ),
+          ),
+          ListView.builder(
+            shrinkWrap: true,
+            itemBuilder: ((context, index) {
+              return ListTile(
+                leading: _buildTileIcon(users[index]),
+                title: Text(
+                  users[index].email,
+                  style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+                ),
+                onTap: () {
+                  if (sharedUsersList.value.contains(users[index])) {
+                    sharedUsersList.value =
+                        sharedUsersList.value.where((selectedUser) => selectedUser.id != users[index].id).toSet();
+                  } else {
+                    sharedUsersList.value = {...sharedUsersList.value, users[index]};
+                  }
+                },
+              );
+            }),
+            itemCount: users.length,
+          ),
+        ],
+      );
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text(
+          'Invite to album',
+          style: TextStyle(color: Colors.black),
+        ),
+        elevation: 0,
+        centerTitle: false,
+        leading: IconButton(
+          icon: const Icon(Icons.close_rounded),
+          onPressed: () async {
+            AutoRouter.of(context).pop();
+          },
+        ),
+        actions: [
+          TextButton(
+              onPressed: sharedUsersList.value.isEmpty ? null : _createSharedAlbum,
+              child: const Text(
+                "Create Album",
+                style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+              ))
+        ],
+      ),
+      body: suggestedShareUsers.when(
+        data: (users) {
+          return _buildUserList(users);
+        },
+        error: (e, _) => Text("Error loading suggested users $e"),
+        loading: () => const Center(
+          child: ImmichLoadingIndicator(),
+        ),
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/modules/sharing/views/sharing_page.dart b/mobile/lib/modules/sharing/views/sharing_page.dart
new file mode 100644
index 0000000000..cad49e4ad9
--- /dev/null
+++ b/mobile/lib/modules/sharing/views/sharing_page.dart
@@ -0,0 +1,142 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hive/hive.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/constants/hive_box.dart';
+import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
+import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
+import 'package:immich_mobile/modules/sharing/ui/sharing_sliver_appbar.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:transparent_image/transparent_image.dart';
+
+class SharingPage extends HookConsumerWidget {
+  const SharingPage({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    var box = Hive.box(userInfoBox);
+    var thumbnailRequestUrl = '${box.get(serverEndpointKey)}/asset/thumbnail';
+    final List<SharedAlbum> sharedAlbums = ref.watch(sharedAlbumProvider);
+
+    useEffect(() {
+      ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
+
+      return null;
+    }, []);
+
+    _buildAlbumList() {
+      return SliverList(
+        delegate: SliverChildBuilderDelegate(
+          (BuildContext context, int index) {
+            String thumbnailUrl = sharedAlbums[index].albumThumbnailAssetId != null
+                ? "$thumbnailRequestUrl/${sharedAlbums[index].albumThumbnailAssetId}"
+                : "https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60";
+
+            return ListTile(
+              contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
+              leading: ClipRRect(
+                borderRadius: BorderRadius.circular(8),
+                child: FadeInImage(
+                  width: 60,
+                  height: 60,
+                  fit: BoxFit.cover,
+                  placeholder: MemoryImage(kTransparentImage),
+                  image: NetworkImage(
+                    thumbnailUrl,
+                    headers: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
+                  ),
+                  fadeInDuration: const Duration(milliseconds: 200),
+                  fadeOutDuration: const Duration(milliseconds: 200),
+                ),
+              ),
+              title: Text(
+                sharedAlbums[index].albumName,
+                maxLines: 1,
+                overflow: TextOverflow.ellipsis,
+                style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey.shade800),
+              ),
+              onTap: () {
+                AutoRouter.of(context).push(AlbumViewerRoute(albumId: sharedAlbums[index].id));
+              },
+            );
+          },
+          childCount: sharedAlbums.length,
+        ),
+      );
+    }
+
+    _buildEmptyListIndication() {
+      return SliverToBoxAdapter(
+        child: Padding(
+          padding: const EdgeInsets.all(8.0),
+          child: Card(
+            elevation: 0,
+            shape: RoundedRectangleBorder(
+              borderRadius: BorderRadius.circular(10), // if you need this
+              side: const BorderSide(
+                color: Colors.black12,
+                width: 1,
+              ),
+            ),
+            color: Colors.transparent,
+            child: Padding(
+              padding: const EdgeInsets.all(18.0),
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  Padding(
+                    padding: const EdgeInsets.only(left: 5.0, bottom: 5),
+                    child: Icon(
+                      Icons.offline_share_outlined,
+                      size: 50,
+                      color: Theme.of(context).primaryColor.withAlpha(200),
+                    ),
+                  ),
+                  Padding(
+                    padding: const EdgeInsets.all(8.0),
+                    child: Text(
+                      'EMPTY LIST',
+                      style: TextStyle(
+                        fontSize: 12,
+                        color: Theme.of(context).primaryColor,
+                        fontWeight: FontWeight.bold,
+                      ),
+                    ),
+                  ),
+                  Padding(
+                    padding: const EdgeInsets.all(8.0),
+                    child: Text(
+                      'Create shared albums to share photos and videos with people in your network.',
+                      style: TextStyle(fontSize: 12, color: Colors.grey[700]),
+                    ),
+                  ),
+                ],
+              ),
+            ),
+          ),
+        ),
+      );
+    }
+
+    return Scaffold(
+      body: CustomScrollView(
+        slivers: [
+          const SharingSliverAppBar(),
+          const SliverPadding(
+            padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
+            sliver: SliverToBoxAdapter(
+              child: Text(
+                "Shared albums",
+                style: TextStyle(
+                  fontWeight: FontWeight.bold,
+                ),
+              ),
+            ),
+          ),
+          sharedAlbums.isNotEmpty ? _buildAlbumList() : _buildEmptyListIndication()
+        ],
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart
index ee38bff71b..a25d45c7bd 100644
--- a/mobile/lib/routing/router.dart
+++ b/mobile/lib/routing/router.dart
@@ -1,9 +1,17 @@
 import 'package:auto_route/auto_route.dart';
-import 'package:flutter/widgets.dart';
+import 'package:flutter/material.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';
 import 'package:immich_mobile/modules/search/views/search_result_page.dart';
+import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
+import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
+import 'package:immich_mobile/modules/sharing/views/album_viewer_page.dart';
+import 'package:immich_mobile/modules/sharing/views/asset_selection_page.dart';
+import 'package:immich_mobile/modules/sharing/views/create_shared_album_page.dart';
+import 'package:immich_mobile/modules/sharing/views/select_additional_user_for_sharing_page.dart';
+import 'package:immich_mobile/modules/sharing/views/select_user_for_sharing_page.dart';
+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';
@@ -22,13 +30,31 @@ part 'router.gr.dart';
       guards: [AuthGuard],
       children: [
         AutoRoute(page: HomePage, guards: [AuthGuard]),
-        AutoRoute(page: SearchPage, guards: [AuthGuard])
+        AutoRoute(page: SearchPage, guards: [AuthGuard]),
+        AutoRoute(page: SharingPage, guards: [AuthGuard])
       ],
     ),
     AutoRoute(page: ImageViewerPage, guards: [AuthGuard]),
     AutoRoute(page: VideoViewerPage, guards: [AuthGuard]),
     AutoRoute(page: BackupControllerPage, guards: [AuthGuard]),
     AutoRoute(page: SearchResultPage, guards: [AuthGuard]),
+    AutoRoute(page: CreateSharedAlbumPage, guards: [AuthGuard]),
+    CustomRoute<AssetSelectionPageResult?>(
+      page: AssetSelectionPage,
+      guards: [AuthGuard],
+      transitionsBuilder: TransitionsBuilders.slideBottom,
+    ),
+    CustomRoute<List<String>>(
+      page: SelectUserForSharingPage,
+      guards: [AuthGuard],
+      transitionsBuilder: TransitionsBuilders.slideBottom,
+    ),
+    AutoRoute(page: AlbumViewerPage, guards: [AuthGuard]),
+    CustomRoute<List<String>?>(
+      page: SelectAdditionalUserForSharingPage,
+      guards: [AuthGuard],
+      transitionsBuilder: TransitionsBuilders.slideBottom,
+    ),
   ],
 )
 class AppRouter extends _$AppRouter {
diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart
index 06c0c599b0..0d4f76074d 100644
--- a/mobile/lib/routing/router.gr.dart
+++ b/mobile/lib/routing/router.gr.dart
@@ -57,6 +57,42 @@ class _$AppRouter extends RootStackRouter {
           routeData: routeData,
           child: SearchResultPage(key: args.key, searchTerm: args.searchTerm));
     },
+    CreateSharedAlbumRoute.name: (routeData) {
+      return MaterialPageX<dynamic>(
+          routeData: routeData, child: const CreateSharedAlbumPage());
+    },
+    AssetSelectionRoute.name: (routeData) {
+      return CustomPage<AssetSelectionPageResult?>(
+          routeData: routeData,
+          child: const AssetSelectionPage(),
+          transitionsBuilder: TransitionsBuilders.slideBottom,
+          opaque: true,
+          barrierDismissible: false);
+    },
+    SelectUserForSharingRoute.name: (routeData) {
+      return CustomPage<List<String>>(
+          routeData: routeData,
+          child: const SelectUserForSharingPage(),
+          transitionsBuilder: TransitionsBuilders.slideBottom,
+          opaque: true,
+          barrierDismissible: false);
+    },
+    AlbumViewerRoute.name: (routeData) {
+      final args = routeData.argsAs<AlbumViewerRouteArgs>();
+      return MaterialPageX<dynamic>(
+          routeData: routeData,
+          child: AlbumViewerPage(key: args.key, albumId: args.albumId));
+    },
+    SelectAdditionalUserForSharingRoute.name: (routeData) {
+      final args = routeData.argsAs<SelectAdditionalUserForSharingRouteArgs>();
+      return CustomPage<List<String>?>(
+          routeData: routeData,
+          child: SelectAdditionalUserForSharingPage(
+              key: args.key, albumInfo: args.albumInfo),
+          transitionsBuilder: TransitionsBuilders.slideBottom,
+          opaque: true,
+          barrierDismissible: false);
+    },
     HomeRoute.name: (routeData) {
       return MaterialPageX<dynamic>(
           routeData: routeData, child: const HomePage());
@@ -66,6 +102,10 @@ class _$AppRouter extends RootStackRouter {
           orElse: () => const SearchRouteArgs());
       return MaterialPageX<dynamic>(
           routeData: routeData, child: SearchPage(key: args.key));
+    },
+    SharingRoute.name: (routeData) {
+      return MaterialPageX<dynamic>(
+          routeData: routeData, child: const SharingPage());
     }
   };
 
@@ -85,6 +125,10 @@ class _$AppRouter extends RootStackRouter {
               RouteConfig(SearchRoute.name,
                   path: 'search-page',
                   parent: TabControllerRoute.name,
+                  guards: [authGuard]),
+              RouteConfig(SharingRoute.name,
+                  path: 'sharing-page',
+                  parent: TabControllerRoute.name,
                   guards: [authGuard])
             ]),
         RouteConfig(ImageViewerRoute.name,
@@ -94,7 +138,18 @@ class _$AppRouter extends RootStackRouter {
         RouteConfig(BackupControllerRoute.name,
             path: '/backup-controller-page', guards: [authGuard]),
         RouteConfig(SearchResultRoute.name,
-            path: '/search-result-page', guards: [authGuard])
+            path: '/search-result-page', guards: [authGuard]),
+        RouteConfig(CreateSharedAlbumRoute.name,
+            path: '/create-shared-album-page', guards: [authGuard]),
+        RouteConfig(AssetSelectionRoute.name,
+            path: '/asset-selection-page', guards: [authGuard]),
+        RouteConfig(SelectUserForSharingRoute.name,
+            path: '/select-user-for-sharing-page', guards: [authGuard]),
+        RouteConfig(AlbumViewerRoute.name,
+            path: '/album-viewer-page', guards: [authGuard]),
+        RouteConfig(SelectAdditionalUserForSharingRoute.name,
+            path: '/select-additional-user-for-sharing-page',
+            guards: [authGuard])
       ];
 }
 
@@ -223,6 +278,86 @@ class SearchResultRouteArgs {
   }
 }
 
+/// generated route for
+/// [CreateSharedAlbumPage]
+class CreateSharedAlbumRoute extends PageRouteInfo<void> {
+  const CreateSharedAlbumRoute()
+      : super(CreateSharedAlbumRoute.name, path: '/create-shared-album-page');
+
+  static const String name = 'CreateSharedAlbumRoute';
+}
+
+/// generated route for
+/// [AssetSelectionPage]
+class AssetSelectionRoute extends PageRouteInfo<void> {
+  const AssetSelectionRoute()
+      : super(AssetSelectionRoute.name, path: '/asset-selection-page');
+
+  static const String name = 'AssetSelectionRoute';
+}
+
+/// generated route for
+/// [SelectUserForSharingPage]
+class SelectUserForSharingRoute extends PageRouteInfo<void> {
+  const SelectUserForSharingRoute()
+      : super(SelectUserForSharingRoute.name,
+            path: '/select-user-for-sharing-page');
+
+  static const String name = 'SelectUserForSharingRoute';
+}
+
+/// generated route for
+/// [AlbumViewerPage]
+class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
+  AlbumViewerRoute({Key? key, required String albumId})
+      : super(AlbumViewerRoute.name,
+            path: '/album-viewer-page',
+            args: AlbumViewerRouteArgs(key: key, albumId: albumId));
+
+  static const String name = 'AlbumViewerRoute';
+}
+
+class AlbumViewerRouteArgs {
+  const AlbumViewerRouteArgs({this.key, required this.albumId});
+
+  final Key? key;
+
+  final String albumId;
+
+  @override
+  String toString() {
+    return 'AlbumViewerRouteArgs{key: $key, albumId: $albumId}';
+  }
+}
+
+/// generated route for
+/// [SelectAdditionalUserForSharingPage]
+class SelectAdditionalUserForSharingRoute
+    extends PageRouteInfo<SelectAdditionalUserForSharingRouteArgs> {
+  SelectAdditionalUserForSharingRoute(
+      {Key? key, required SharedAlbum albumInfo})
+      : super(SelectAdditionalUserForSharingRoute.name,
+            path: '/select-additional-user-for-sharing-page',
+            args: SelectAdditionalUserForSharingRouteArgs(
+                key: key, albumInfo: albumInfo));
+
+  static const String name = 'SelectAdditionalUserForSharingRoute';
+}
+
+class SelectAdditionalUserForSharingRouteArgs {
+  const SelectAdditionalUserForSharingRouteArgs(
+      {this.key, required this.albumInfo});
+
+  final Key? key;
+
+  final SharedAlbum albumInfo;
+
+  @override
+  String toString() {
+    return 'SelectAdditionalUserForSharingRouteArgs{key: $key, albumInfo: $albumInfo}';
+  }
+}
+
 /// generated route for
 /// [HomePage]
 class HomeRoute extends PageRouteInfo<void> {
@@ -251,3 +386,11 @@ class SearchRouteArgs {
     return 'SearchRouteArgs{key: $key}';
   }
 }
+
+/// generated route for
+/// [SharingPage]
+class SharingRoute extends PageRouteInfo<void> {
+  const SharingRoute() : super(SharingRoute.name, path: 'sharing-page');
+
+  static const String name = 'SharingRoute';
+}
diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart
index 6b813bd169..5eecc7ec37 100644
--- a/mobile/lib/routing/tab_navigation_observer.dart
+++ b/mobile/lib/routing/tab_navigation_observer.dart
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 
 import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
+import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
 import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 
 class TabNavigationObserver extends AutoRouterObserver {
@@ -21,7 +22,8 @@ class TabNavigationObserver extends AutoRouterObserver {
   }
 
   @override
-  Future<void> didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async {
+  Future<void> didChangeTabRoute(
+      TabPageRoute route, TabPageRoute previousRoute) async {
     // Perform tasks on re-visit to SearchRoute
     if (route.name == 'SearchRoute') {
       // Refresh Location State
@@ -29,6 +31,10 @@ class TabNavigationObserver extends AutoRouterObserver {
       ref.refresh(getCuratedObjectProvider);
     }
 
+    if (route.name == 'SharingRoute') {
+      ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
+    }
+
     ref.watch(serverInfoProvider.notifier).getServerVersion();
   }
 }
diff --git a/mobile/lib/shared/models/user_info.model.dart b/mobile/lib/shared/models/user_info.model.dart
new file mode 100644
index 0000000000..dc5203f9eb
--- /dev/null
+++ b/mobile/lib/shared/models/user_info.model.dart
@@ -0,0 +1,60 @@
+import 'dart:convert';
+
+class UserInfo {
+  final String id;
+  final String email;
+  final String createdAt;
+
+  UserInfo({
+    required this.id,
+    required this.email,
+    required this.createdAt,
+  });
+
+  UserInfo copyWith({
+    String? id,
+    String? email,
+    String? createdAt,
+  }) {
+    return UserInfo(
+      id: id ?? this.id,
+      email: email ?? this.email,
+      createdAt: createdAt ?? this.createdAt,
+    );
+  }
+
+  Map<String, dynamic> toMap() {
+    final result = <String, dynamic>{};
+
+    result.addAll({'id': id});
+    result.addAll({'email': email});
+    result.addAll({'createdAt': createdAt});
+
+    return result;
+  }
+
+  factory UserInfo.fromMap(Map<String, dynamic> map) {
+    return UserInfo(
+      id: map['id'] ?? '',
+      email: map['email'] ?? '',
+      createdAt: map['createdAt'] ?? '',
+    );
+  }
+
+  String toJson() => json.encode(toMap());
+
+  factory UserInfo.fromJson(String source) => UserInfo.fromMap(json.decode(source));
+
+  @override
+  String toString() => 'UserInfo(id: $id, email: $email, createdAt: $createdAt)';
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+
+    return other is UserInfo && other.id == id && other.email == email && other.createdAt == createdAt;
+  }
+
+  @override
+  int get hashCode => id.hashCode ^ email.hashCode ^ createdAt.hashCode;
+}
diff --git a/mobile/lib/modules/home/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart
similarity index 88%
rename from mobile/lib/modules/home/providers/asset.provider.dart
rename to mobile/lib/shared/providers/asset.provider.dart
index f1553575a8..401ef5f540 100644
--- a/mobile/lib/modules/home/providers/asset.provider.dart
+++ b/mobile/lib/shared/providers/asset.provider.dart
@@ -72,3 +72,11 @@ final assetGroupByDateTimeProvider = StateProvider((ref) {
   assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
   return assets.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
 });
+
+final assetGroupByMonthYearProvider = StateProvider((ref) {
+  var assets = ref.watch(assetProvider);
+
+  assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
+
+  return assets.groupListsBy((element) => DateFormat('MMMM, y').format(DateTime.parse(element.createdAt)));
+});
diff --git a/mobile/lib/shared/providers/websocket.provider.dart b/mobile/lib/shared/providers/websocket.provider.dart
index 23565feb33..e5bf907f7d 100644
--- a/mobile/lib/shared/providers/websocket.provider.dart
+++ b/mobile/lib/shared/providers/websocket.provider.dart
@@ -3,7 +3,7 @@ import 'dart:convert';
 import 'package:flutter/foundation.dart';
 import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
+import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:socket_io_client/socket_io_client.dart';
 
diff --git a/mobile/lib/shared/services/network.service.dart b/mobile/lib/shared/services/network.service.dart
index 45d740e4fc..52260e494c 100644
--- a/mobile/lib/shared/services/network.service.dart
+++ b/mobile/lib/shared/services/network.service.dart
@@ -76,9 +76,10 @@ class NetworkService {
       return res;
     } on DioError catch (e) {
       debugPrint("DioError: ${e.response}");
-      return false;
+      return null;
     } catch (e) {
       debugPrint("ERROR BackupService: $e");
+      return null;
     }
   }
 
diff --git a/mobile/lib/shared/services/user.service.dart b/mobile/lib/shared/services/user.service.dart
new file mode 100644
index 0000000000..cbc5f7d94a
--- /dev/null
+++ b/mobile/lib/shared/services/user.service.dart
@@ -0,0 +1,24 @@
+import 'dart:convert';
+
+import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:immich_mobile/shared/models/user_info.model.dart';
+import 'package:immich_mobile/shared/services/network.service.dart';
+
+class UserService {
+  final NetworkService _networkService = NetworkService();
+
+  Future<List<UserInfo>> getAllUsersInfo() async {
+    try {
+      Response res = await _networkService.getRequest(url: 'user');
+      List<dynamic> decodedData = jsonDecode(res.toString());
+      List<UserInfo> result = List.from(decodedData.map((e) => UserInfo.fromMap(e)));
+
+      return result;
+    } catch (e) {
+      debugPrint("Error getAllUsersInfo  ${e.toString()}");
+    }
+
+    return [];
+  }
+}
diff --git a/mobile/lib/shared/ui/immich_loading_indicator.dart b/mobile/lib/shared/ui/immich_loading_indicator.dart
new file mode 100644
index 0000000000..f5735811a0
--- /dev/null
+++ b/mobile/lib/shared/ui/immich_loading_indicator.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_spinkit/flutter_spinkit.dart';
+
+class ImmichLoadingIndicator extends StatelessWidget {
+  const ImmichLoadingIndicator({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: 60,
+      width: 60,
+      decoration: BoxDecoration(
+        color: Theme.of(context).primaryColor.withAlpha(200),
+        borderRadius: BorderRadius.circular(10),
+      ),
+      child: const SpinKitDancingSquare(
+        color: Colors.white,
+        size: 30.0,
+      ),
+    );
+  }
+}
diff --git a/mobile/lib/shared/ui/immich_sliver_persistent_app_bar_delegate.dart b/mobile/lib/shared/ui/immich_sliver_persistent_app_bar_delegate.dart
new file mode 100644
index 0000000000..6ca358dade
--- /dev/null
+++ b/mobile/lib/shared/ui/immich_sliver_persistent_app_bar_delegate.dart
@@ -0,0 +1,31 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+
+class ImmichSliverPersistentAppBarDelegate extends SliverPersistentHeaderDelegate {
+  final double minHeight;
+  final double maxHeight;
+  final Widget child;
+
+  ImmichSliverPersistentAppBarDelegate({
+    required this.minHeight,
+    required this.maxHeight,
+    required this.child,
+  });
+
+  @override
+  double get minExtent => minHeight;
+
+  @override
+  double get maxExtent => max(maxHeight, minHeight);
+
+  @override
+  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
+    return SizedBox.expand(child: child);
+  }
+
+  @override
+  bool shouldRebuild(ImmichSliverPersistentAppBarDelegate oldDelegate) {
+    return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight || child != oldDelegate.child;
+  }
+}
diff --git a/mobile/lib/shared/views/immich_loading_overlay.dart b/mobile/lib/shared/views/immich_loading_overlay.dart
new file mode 100644
index 0000000000..70bfdb9224
--- /dev/null
+++ b/mobile/lib/shared/views/immich_loading_overlay.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+
+class ImmichLoadingOverlay extends StatelessWidget {
+  const ImmichLoadingOverlay({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ValueListenableBuilder<bool>(
+      valueListenable: ImmichLoadingOverlayController.appLoader.loaderShowingNotifier,
+      builder: (context, shouldShow, child) {
+        if (shouldShow) {
+          return const Scaffold(
+            backgroundColor: Colors.black54,
+            body: Center(
+              child: ImmichLoadingIndicator(),
+            ),
+          );
+        } else {
+          return Container();
+        }
+      },
+    );
+  }
+}
+
+class ImmichLoadingOverlayController {
+  static final ImmichLoadingOverlayController appLoader = ImmichLoadingOverlayController();
+  ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
+  ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
+
+  void show() {
+    loaderShowingNotifier.value = true;
+  }
+
+  void hide() {
+    loaderShowingNotifier.value = false;
+  }
+}
diff --git a/mobile/lib/shared/views/tab_controller_page.dart b/mobile/lib/shared/views/tab_controller_page.dart
index f699e1ccaf..2ff1d04de3 100644
--- a/mobile/lib/shared/views/tab_controller_page.dart
+++ b/mobile/lib/shared/views/tab_controller_page.dart
@@ -15,6 +15,7 @@ class TabControllerPage extends ConsumerWidget {
       routes: [
         const HomeRoute(),
         SearchRoute(),
+        const SharingRoute(),
       ],
       builder: (context, child, animation) {
         final tabsRouter = AutoTabsRouter.of(context);
@@ -34,7 +35,8 @@ class TabControllerPage extends ConsumerWidget {
                   },
                   items: const [
                     BottomNavigationBarItem(label: 'Photos', icon: Icon(Icons.photo)),
-                    BottomNavigationBarItem(label: 'Seach', icon: Icon(Icons.search)),
+                    BottomNavigationBarItem(label: 'Search', icon: Icon(Icons.search)),
+                    BottomNavigationBarItem(label: 'Sharing', icon: Icon(Icons.group_outlined)),
                   ],
                 ),
         );
diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock
index d759ed6b54..44d267cc78 100644
--- a/mobile/pubspec.lock
+++ b/mobile/pubspec.lock
@@ -335,6 +335,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "5.1.0"
+  flutter_swipe_detector:
+    dependency: "direct main"
+    description:
+      name: flutter_swipe_detector
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
   flutter_test:
     dependency: "direct dev"
     description: flutter
diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml
index 4d939bd6ad..751ec6c005 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.6.0+10
+version: 1.7.0+11
 
 environment:
   sdk: ">=2.15.1 <3.0.0"
@@ -18,7 +18,6 @@ dependencies:
   hive_flutter:
   dio: ^4.0.4
   cached_network_image: ^3.2.0
-  # google_fonts: ^2.2.0
   percent_indicator: ^3.4.0
   intl: ^0.17.0
   auto_route: ^3.2.2
@@ -37,6 +36,7 @@ dependencies:
   flutter_udid: ^2.0.0
   package_info_plus: ^1.4.0
   flutter_spinkit: ^5.1.0
+  flutter_swipe_detector: ^2.0.0
 
 dev_dependencies:
   flutter_test:
diff --git a/server/src/api-v1/asset/asset.controller.ts b/server/src/api-v1/asset/asset.controller.ts
index 6e7f9b0ad0..4d7b45a7d5 100644
--- a/server/src/api-v1/asset/asset.controller.ts
+++ b/server/src/api-v1/asset/asset.controller.ts
@@ -82,7 +82,7 @@ export class AssetController {
     @Response({ passthrough: true }) res: Res,
     @Query(ValidationPipe) query: ServeFileDto,
   ) {
-    return this.assetService.downloadFile(authUser, query, res);
+    return this.assetService.downloadFile(query, res);
   }
 
   @Get('/file')
@@ -95,6 +95,11 @@ export class AssetController {
     return this.assetService.serveFile(authUser, query, res, headers);
   }
 
+  @Get('/thumbnail/:assetId')
+  async getAssetThumbnail(@Param('assetId') assetId: string): Promise<StreamableFile> {
+    return await this.assetService.getAssetThumbnail(assetId);
+  }
+
   @Get('/allObjects')
   async getCuratedObject(@GetAuthUser() authUser: AuthUserDto) {
     return this.assetService.getCuratedObject(authUser);
diff --git a/server/src/api-v1/asset/asset.service.ts b/server/src/api-v1/asset/asset.service.ts
index 40d8664e02..1c9e2cc2dc 100644
--- a/server/src/api-v1/asset/asset.service.ts
+++ b/server/src/api-v1/asset/asset.service.ts
@@ -76,10 +76,10 @@ export class AssetService {
     }
   }
 
-  public async findOne(authUser: AuthUserDto, deviceId: string, assetId: string): Promise<AssetEntity> {
+  public async findOne(deviceId: string, assetId: string): Promise<AssetEntity> {
     const rows = await this.assetRepository.query(
-      'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."userId" = $2 AND a."deviceId" = $3',
-      [assetId, authUser.id, deviceId],
+      'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
+      [assetId, deviceId],
     );
 
     if (rows.lengh == 0) {
@@ -92,16 +92,15 @@ export class AssetService {
   public async getAssetById(authUser: AuthUserDto, assetId: string) {
     return await this.assetRepository.findOne({
       where: {
-        userId: authUser.id,
         id: assetId,
       },
       relations: ['exifInfo'],
     });
   }
 
-  public async downloadFile(authUser: AuthUserDto, query: ServeFileDto, res: Res) {
+  public async downloadFile(query: ServeFileDto, res: Res) {
     let file = null;
-    const asset = await this.findOne(authUser, query.did, query.aid);
+    const asset = await this.findOne(query.did, query.aid);
 
     if (query.isThumb === 'false' || !query.isThumb) {
       file = createReadStream(asset.originalPath);
@@ -112,10 +111,15 @@ export class AssetService {
     return new StreamableFile(file);
   }
 
+  public async getAssetThumbnail(assetId: string) {
+    const asset = await this.assetRepository.findOne({ id: assetId });
+
+    return new StreamableFile(createReadStream(asset.resizePath));
+  }
+
   public async serveFile(authUser: AuthUserDto, query: ServeFileDto, res: Res, headers: any) {
     let file = null;
-    const asset = await this.findOne(authUser, query.did, query.aid);
-
+    const asset = await this.findOne(query.did, query.aid);
     if (!asset) {
       throw new BadRequestException('Asset does not exist');
     }
diff --git a/server/src/api-v1/sharing/dto/add-assets.dto.ts b/server/src/api-v1/sharing/dto/add-assets.dto.ts
new file mode 100644
index 0000000000..a64a602fcf
--- /dev/null
+++ b/server/src/api-v1/sharing/dto/add-assets.dto.ts
@@ -0,0 +1,10 @@
+import { IsNotEmpty } from 'class-validator';
+import { AssetEntity } from '../../asset/entities/asset.entity';
+
+export class AddAssetsDto {
+  @IsNotEmpty()
+  albumId: string;
+
+  @IsNotEmpty()
+  assetIds: string[];
+}
diff --git a/server/src/api-v1/sharing/dto/add-users.dto.ts b/server/src/api-v1/sharing/dto/add-users.dto.ts
new file mode 100644
index 0000000000..1014218bdf
--- /dev/null
+++ b/server/src/api-v1/sharing/dto/add-users.dto.ts
@@ -0,0 +1,9 @@
+import { IsNotEmpty } from 'class-validator';
+
+export class AddUsersDto {
+  @IsNotEmpty()
+  albumId: string;
+
+  @IsNotEmpty()
+  sharedUserIds: string[];
+}
diff --git a/server/src/api-v1/sharing/dto/create-shared-album.dto.ts b/server/src/api-v1/sharing/dto/create-shared-album.dto.ts
new file mode 100644
index 0000000000..5aa59cd38a
--- /dev/null
+++ b/server/src/api-v1/sharing/dto/create-shared-album.dto.ts
@@ -0,0 +1,13 @@
+import { IsNotEmpty, IsOptional } from 'class-validator';
+import { AssetEntity } from '../../asset/entities/asset.entity';
+
+export class CreateSharedAlbumDto {
+  @IsNotEmpty()
+  albumName: string;
+
+  @IsNotEmpty()
+  sharedWithUserIds: string[];
+
+  @IsOptional()
+  assetIds: string[];
+}
diff --git a/server/src/api-v1/sharing/dto/remove-assets.dto.ts b/server/src/api-v1/sharing/dto/remove-assets.dto.ts
new file mode 100644
index 0000000000..158139c085
--- /dev/null
+++ b/server/src/api-v1/sharing/dto/remove-assets.dto.ts
@@ -0,0 +1,9 @@
+import { IsNotEmpty } from 'class-validator';
+
+export class RemoveAssetsDto {
+  @IsNotEmpty()
+  albumId: string;
+
+  @IsNotEmpty()
+  assetIds: string[];
+}
diff --git a/server/src/api-v1/sharing/entities/asset-shared-album.entity.ts b/server/src/api-v1/sharing/entities/asset-shared-album.entity.ts
new file mode 100644
index 0000000000..69f4f78042
--- /dev/null
+++ b/server/src/api-v1/sharing/entities/asset-shared-album.entity.ts
@@ -0,0 +1,30 @@
+import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
+import { AssetEntity } from '../../asset/entities/asset.entity';
+import { SharedAlbumEntity } from './shared-album.entity';
+
+@Entity('asset_shared_album')
+@Unique('PK_unique_asset_in_album', ['albumId', 'assetId'])
+export class AssetSharedAlbumEntity {
+  @PrimaryGeneratedColumn()
+  id: string;
+
+  @Column()
+  albumId: string;
+
+  @Column()
+  assetId: string;
+
+  @ManyToOne(() => SharedAlbumEntity, (sharedAlbum) => sharedAlbum.sharedAssets, {
+    onDelete: 'CASCADE',
+    nullable: true,
+  })
+  @JoinColumn({ name: 'albumId' })
+  albumInfo: SharedAlbumEntity;
+
+  @ManyToOne(() => AssetEntity, {
+    onDelete: 'CASCADE',
+    nullable: true,
+  })
+  @JoinColumn({ name: 'assetId' })
+  assetInfo: AssetEntity;
+}
diff --git a/server/src/api-v1/sharing/entities/shared-album.entity.ts b/server/src/api-v1/sharing/entities/shared-album.entity.ts
new file mode 100644
index 0000000000..e8f27fcbcf
--- /dev/null
+++ b/server/src/api-v1/sharing/entities/shared-album.entity.ts
@@ -0,0 +1,27 @@
+import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
+import { AssetSharedAlbumEntity } from './asset-shared-album.entity';
+import { UserSharedAlbumEntity } from './user-shared-album.entity';
+
+@Entity('shared_albums')
+export class SharedAlbumEntity {
+  @PrimaryGeneratedColumn('uuid')
+  id: string;
+
+  @Column()
+  ownerId: string;
+
+  @Column({ default: 'Untitled Album' })
+  albumName: string;
+
+  @CreateDateColumn({ type: 'timestamptz' })
+  createdAt: string;
+
+  @Column({ comment: 'Asset ID to be used as thumbnail', nullable: true })
+  albumThumbnailAssetId: string;
+
+  @OneToMany(() => UserSharedAlbumEntity, (userSharedAlbums) => userSharedAlbums.albumInfo)
+  sharedUsers: UserSharedAlbumEntity[];
+
+  @OneToMany(() => AssetSharedAlbumEntity, (assetSharedAlbumEntity) => assetSharedAlbumEntity.albumInfo)
+  sharedAssets: AssetSharedAlbumEntity[];
+}
diff --git a/server/src/api-v1/sharing/entities/user-shared-album.entity.ts b/server/src/api-v1/sharing/entities/user-shared-album.entity.ts
new file mode 100644
index 0000000000..b3041e07c4
--- /dev/null
+++ b/server/src/api-v1/sharing/entities/user-shared-album.entity.ts
@@ -0,0 +1,27 @@
+import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
+import { UserEntity } from '../../user/entities/user.entity';
+import { SharedAlbumEntity } from './shared-album.entity';
+
+@Entity('user_shared_album')
+@Unique('PK_unique_user_in_album', ['albumId', 'sharedUserId'])
+export class UserSharedAlbumEntity {
+  @PrimaryGeneratedColumn()
+  id: string;
+
+  @Column()
+  albumId: string;
+
+  @Column()
+  sharedUserId: string;
+
+  @ManyToOne(() => SharedAlbumEntity, (sharedAlbum) => sharedAlbum.sharedUsers, {
+    onDelete: 'CASCADE',
+    nullable: true,
+  })
+  @JoinColumn({ name: 'albumId' })
+  albumInfo: SharedAlbumEntity;
+
+  @ManyToOne(() => UserEntity)
+  @JoinColumn({ name: 'sharedUserId' })
+  userInfo: UserEntity;
+}
diff --git a/server/src/api-v1/sharing/sharing.controller.ts b/server/src/api-v1/sharing/sharing.controller.ts
new file mode 100644
index 0000000000..4d83fb869e
--- /dev/null
+++ b/server/src/api-v1/sharing/sharing.controller.ts
@@ -0,0 +1,55 @@
+import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, ValidationPipe, Query } from '@nestjs/common';
+import { SharingService } from './sharing.service';
+import { CreateSharedAlbumDto } from './dto/create-shared-album.dto';
+import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
+import { GetAuthUser } from '../../decorators/auth-user.decorator';
+import { AddAssetsDto } from './dto/add-assets.dto';
+import { AddUsersDto } from './dto/add-users.dto';
+import { RemoveAssetsDto } from './dto/remove-assets.dto';
+
+@UseGuards(JwtAuthGuard)
+@Controller('shared')
+export class SharingController {
+  constructor(private readonly sharingService: SharingService) {}
+
+  @Post('/createAlbum')
+  async create(@GetAuthUser() authUser, @Body(ValidationPipe) createSharedAlbumDto: CreateSharedAlbumDto) {
+    return await this.sharingService.create(authUser, createSharedAlbumDto);
+  }
+
+  @Post('/addUsers')
+  async addUsers(@Body(ValidationPipe) addUsersDto: AddUsersDto) {
+    return await this.sharingService.addUsersToAlbum(addUsersDto);
+  }
+
+  @Post('/addAssets')
+  async addAssets(@Body(ValidationPipe) addAssetsDto: AddAssetsDto) {
+    return await this.sharingService.addAssetsToAlbum(addAssetsDto);
+  }
+
+  @Get('/allSharedAlbums')
+  async getAllSharedAlbums(@GetAuthUser() authUser) {
+    return await this.sharingService.getAllSharedAlbums(authUser);
+  }
+
+  @Get('/:albumId')
+  async getAlbumInfo(@GetAuthUser() authUser, @Param('albumId') albumId: string) {
+    return await this.sharingService.getAlbumInfo(authUser, albumId);
+  }
+
+  @Delete('/removeAssets')
+  async removeAssetFromAlbum(@GetAuthUser() authUser, @Body(ValidationPipe) removeAssetsDto: RemoveAssetsDto) {
+    console.log('removeAssets');
+    return await this.sharingService.removeAssetsFromAlbum(authUser, removeAssetsDto);
+  }
+
+  @Delete('/:albumId')
+  async deleteAlbum(@GetAuthUser() authUser, @Param('albumId') albumId: string) {
+    return await this.sharingService.deleteAlbum(authUser, albumId);
+  }
+
+  @Delete('/leaveAlbum/:albumId')
+  async leaveAlbum(@GetAuthUser() authUser, @Param('albumId') albumId: string) {
+    return await this.sharingService.leaveAlbum(authUser, albumId);
+  }
+}
diff --git a/server/src/api-v1/sharing/sharing.module.ts b/server/src/api-v1/sharing/sharing.module.ts
new file mode 100644
index 0000000000..04b511e7d2
--- /dev/null
+++ b/server/src/api-v1/sharing/sharing.module.ts
@@ -0,0 +1,24 @@
+import { Module } from '@nestjs/common';
+import { SharingService } from './sharing.service';
+import { SharingController } from './sharing.controller';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { AssetEntity } from '../asset/entities/asset.entity';
+import { UserEntity } from '../user/entities/user.entity';
+import { SharedAlbumEntity } from './entities/shared-album.entity';
+import { AssetSharedAlbumEntity } from './entities/asset-shared-album.entity';
+import { UserSharedAlbumEntity } from './entities/user-shared-album.entity';
+
+@Module({
+  imports: [
+    TypeOrmModule.forFeature([
+      AssetEntity,
+      UserEntity,
+      SharedAlbumEntity,
+      AssetSharedAlbumEntity,
+      UserSharedAlbumEntity,
+    ]),
+  ],
+  controllers: [SharingController],
+  providers: [SharingService],
+})
+export class SharingModule {}
diff --git a/server/src/api-v1/sharing/sharing.service.ts b/server/src/api-v1/sharing/sharing.service.ts
new file mode 100644
index 0000000000..a249223aaa
--- /dev/null
+++ b/server/src/api-v1/sharing/sharing.service.ts
@@ -0,0 +1,187 @@
+import { BadRequestException, Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+import { getConnection, Repository } from 'typeorm';
+import { AuthUserDto } from '../../decorators/auth-user.decorator';
+import { AssetEntity } from '../asset/entities/asset.entity';
+import { UserEntity } from '../user/entities/user.entity';
+import { AddAssetsDto } from './dto/add-assets.dto';
+import { CreateSharedAlbumDto } from './dto/create-shared-album.dto';
+import { AssetSharedAlbumEntity } from './entities/asset-shared-album.entity';
+import { SharedAlbumEntity } from './entities/shared-album.entity';
+import { UserSharedAlbumEntity } from './entities/user-shared-album.entity';
+import _ from 'lodash';
+import { AddUsersDto } from './dto/add-users.dto';
+import { RemoveAssetsDto } from './dto/remove-assets.dto';
+
+@Injectable()
+export class SharingService {
+  constructor(
+    @InjectRepository(AssetEntity)
+    private assetRepository: Repository<AssetEntity>,
+
+    @InjectRepository(UserEntity)
+    private userRepository: Repository<UserEntity>,
+
+    @InjectRepository(SharedAlbumEntity)
+    private sharedAlbumRepository: Repository<SharedAlbumEntity>,
+
+    @InjectRepository(AssetSharedAlbumEntity)
+    private assetSharedAlbumRepository: Repository<AssetSharedAlbumEntity>,
+
+    @InjectRepository(UserSharedAlbumEntity)
+    private userSharedAlbumRepository: Repository<UserSharedAlbumEntity>,
+  ) {}
+
+  async create(authUser: AuthUserDto, createSharedAlbumDto: CreateSharedAlbumDto) {
+    return await getConnection().transaction(async (transactionalEntityManager) => {
+      // Create album entity
+      const newSharedAlbum = new SharedAlbumEntity();
+      newSharedAlbum.ownerId = authUser.id;
+      newSharedAlbum.albumName = createSharedAlbumDto.albumName;
+
+      const sharedAlbum = await transactionalEntityManager.save(newSharedAlbum);
+
+      // Add shared users
+      for (const sharedUserId of createSharedAlbumDto.sharedWithUserIds) {
+        const newSharedUser = new UserSharedAlbumEntity();
+        newSharedUser.albumId = sharedAlbum.id;
+        newSharedUser.sharedUserId = sharedUserId;
+
+        await transactionalEntityManager.save(newSharedUser);
+      }
+
+      // Add shared assets
+      const newRecords: AssetSharedAlbumEntity[] = [];
+
+      for (const assetId of createSharedAlbumDto.assetIds) {
+        const newAssetSharedAlbum = new AssetSharedAlbumEntity();
+        newAssetSharedAlbum.assetId = assetId;
+        newAssetSharedAlbum.albumId = sharedAlbum.id;
+
+        newRecords.push(newAssetSharedAlbum);
+      }
+
+      if (!sharedAlbum.albumThumbnailAssetId && newRecords.length > 0) {
+        sharedAlbum.albumThumbnailAssetId = newRecords[0].assetId;
+        await transactionalEntityManager.save(sharedAlbum);
+      }
+
+      await transactionalEntityManager.save([...newRecords]);
+
+      return sharedAlbum;
+    });
+  }
+
+  /**
+   * Get all shared album, including owned and shared one.
+   * @param authUser AuthUserDto
+   * @returns All Shared Album And Its Members
+   */
+  async getAllSharedAlbums(authUser: AuthUserDto) {
+    const ownedAlbums = await this.sharedAlbumRepository.find({
+      where: { ownerId: authUser.id },
+      relations: ['sharedUsers', 'sharedUsers.userInfo'],
+    });
+
+    const isSharedWithAlbums = await this.userSharedAlbumRepository.find({
+      where: {
+        sharedUserId: authUser.id,
+      },
+      relations: ['albumInfo', 'albumInfo.sharedUsers', 'albumInfo.sharedUsers.userInfo'],
+      select: ['albumInfo'],
+    });
+
+    return [...ownedAlbums, ...isSharedWithAlbums.map((o) => o.albumInfo)].sort(
+      (a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf(),
+    );
+  }
+
+  async getAlbumInfo(authUser: AuthUserDto, albumId: string) {
+    const albumOwner = await this.sharedAlbumRepository.findOne({ where: { ownerId: authUser.id } });
+    const personShared = await this.userSharedAlbumRepository.findOne({
+      where: { albumId: albumId, sharedUserId: authUser.id },
+    });
+
+    if (!(albumOwner || personShared)) {
+      throw new UnauthorizedException('Unauthorized Album Access');
+    }
+
+    const albumInfo = await this.sharedAlbumRepository.findOne({
+      where: { id: albumId },
+      relations: ['sharedUsers', 'sharedUsers.userInfo', 'sharedAssets', 'sharedAssets.assetInfo'],
+    });
+
+    if (!albumInfo) {
+      throw new NotFoundException('Album Not Found');
+    }
+    const sortedSharedAsset = albumInfo.sharedAssets.sort(
+      (a, b) => new Date(a.assetInfo.createdAt).valueOf() - new Date(b.assetInfo.createdAt).valueOf(),
+    );
+
+    albumInfo.sharedAssets = sortedSharedAsset;
+
+    return albumInfo;
+  }
+
+  async addUsersToAlbum(addUsersDto: AddUsersDto) {
+    const newRecords: UserSharedAlbumEntity[] = [];
+
+    for (const sharedUserId of addUsersDto.sharedUserIds) {
+      const newEntity = new UserSharedAlbumEntity();
+      newEntity.albumId = addUsersDto.albumId;
+      newEntity.sharedUserId = sharedUserId;
+
+      newRecords.push(newEntity);
+    }
+
+    return await this.userSharedAlbumRepository.save([...newRecords]);
+  }
+
+  async deleteAlbum(authUser: AuthUserDto, albumId: string) {
+    return await this.sharedAlbumRepository.delete({ id: albumId, ownerId: authUser.id });
+  }
+
+  async leaveAlbum(authUser: AuthUserDto, albumId: string) {
+    return await this.userSharedAlbumRepository.delete({ albumId: albumId, sharedUserId: authUser.id });
+  }
+
+  async removeUsersFromAlbum() {}
+
+  async removeAssetsFromAlbum(authUser: AuthUserDto, removeAssetsDto: RemoveAssetsDto) {
+    let deleteAssetCount = 0;
+    const album = await this.sharedAlbumRepository.findOne({ id: removeAssetsDto.albumId });
+
+    if (album.ownerId != authUser.id) {
+      throw new BadRequestException("You don't have permission to remove assets in this album");
+    }
+
+    for (const assetId of removeAssetsDto.assetIds) {
+      const res = await this.assetSharedAlbumRepository.delete({ albumId: removeAssetsDto.albumId, assetId: assetId });
+      if (res.affected == 1) deleteAssetCount++;
+    }
+
+    return deleteAssetCount == removeAssetsDto.assetIds.length;
+  }
+
+  async addAssetsToAlbum(addAssetsDto: AddAssetsDto) {
+    const newRecords: AssetSharedAlbumEntity[] = [];
+
+    for (const assetId of addAssetsDto.assetIds) {
+      const newAssetSharedAlbum = new AssetSharedAlbumEntity();
+      newAssetSharedAlbum.assetId = assetId;
+      newAssetSharedAlbum.albumId = addAssetsDto.albumId;
+
+      newRecords.push(newAssetSharedAlbum);
+    }
+
+    // Add album thumbnail if not exist.
+    const album = await this.sharedAlbumRepository.findOne({ id: addAssetsDto.albumId });
+
+    if (!album.albumThumbnailAssetId && newRecords.length > 0) {
+      album.albumThumbnailAssetId = newRecords[0].assetId;
+      await this.sharedAlbumRepository.save(album);
+    }
+
+    return await this.assetSharedAlbumRepository.save([...newRecords]);
+  }
+}
diff --git a/server/src/api-v1/user/user.controller.ts b/server/src/api-v1/user/user.controller.ts
index 4b8420e6bc..473eb84c90 100644
--- a/server/src/api-v1/user/user.controller.ts
+++ b/server/src/api-v1/user/user.controller.ts
@@ -1,9 +1,15 @@
-import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
+import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
 import { UserService } from './user.service';
-import { CreateUserDto } from './dto/create-user.dto';
-import { UpdateUserDto } from './dto/update-user.dto';
+import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
+import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 
+@UseGuards(JwtAuthGuard)
 @Controller('user')
 export class UserController {
   constructor(private readonly userService: UserService) {}
+
+  @Get()
+  async getAllUsers(@GetAuthUser() authUser: AuthUserDto) {
+    return await this.userService.getAllUsers(authUser);
+  }
 }
diff --git a/server/src/api-v1/user/user.service.ts b/server/src/api-v1/user/user.service.ts
index 6ca580934f..614ee4cd88 100644
--- a/server/src/api-v1/user/user.service.ts
+++ b/server/src/api-v1/user/user.service.ts
@@ -1,6 +1,7 @@
 import { Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
-import { Repository } from 'typeorm';
+import { Not, Repository } from 'typeorm';
+import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { CreateUserDto } from './dto/create-user.dto';
 import { UpdateUserDto } from './dto/update-user.dto';
 import { UserEntity } from './entities/user.entity';
@@ -11,4 +12,10 @@ export class UserService {
     @InjectRepository(UserEntity)
     private userRepository: Repository<UserEntity>,
   ) {}
+
+  async getAllUsers(authUser: AuthUserDto) {
+    return await this.userRepository.find({
+      where: { id: Not(authUser.id) },
+    });
+  }
 }
diff --git a/server/src/app.module.ts b/server/src/app.module.ts
index f62b38de6d..a768bca921 100644
--- a/server/src/app.module.ts
+++ b/server/src/app.module.ts
@@ -14,6 +14,7 @@ import { ImageOptimizeModule } from './modules/image-optimize/image-optimize.mod
 import { ServerInfoModule } from './api-v1/server-info/server-info.module';
 import { BackgroundTaskModule } from './modules/background-task/background-task.module';
 import { CommunicationModule } from './api-v1/communication/communication.module';
+import { SharingModule } from './api-v1/sharing/sharing.module';
 
 @Module({
   imports: [
@@ -40,6 +41,8 @@ import { CommunicationModule } from './api-v1/communication/communication.module
     BackgroundTaskModule,
 
     CommunicationModule,
+
+    SharingModule,
   ],
   controllers: [],
   providers: [],
diff --git a/server/src/config/database.config.ts b/server/src/config/database.config.ts
index 71c94064cc..dec963812e 100644
--- a/server/src/config/database.config.ts
+++ b/server/src/config/database.config.ts
@@ -1,11 +1,5 @@
 import { TypeOrmModuleOptions } from '@nestjs/typeorm';
-// import dotenv from 'dotenv';
 
-// const result = dotenv.config();
-
-// if (result.error) {
-//   console.log(result.error);
-// }
 export const databaseConfig: TypeOrmModuleOptions = {
   type: 'postgres',
   host: 'immich_postgres',
diff --git a/server/src/config/multer-option.config.ts b/server/src/config/multer-option.config.ts
index fea33e5dc0..263e162b45 100644
--- a/server/src/config/multer-option.config.ts
+++ b/server/src/config/multer-option.config.ts
@@ -47,7 +47,6 @@ export const multerOption: MulterOptions = {
     },
 
     filename: (req: Request, file: Express.Multer.File, cb: any) => {
-      // console.log(req, file);
       const fileNameUUID = randomUUID();
       if (file.fieldname == 'assetData') {
         cb(null, `${fileNameUUID}${req.body['fileExtension'].toLowerCase()}`);
diff --git a/server/src/constants/server_version.constant.ts b/server/src/constants/server_version.constant.ts
index 031140ec06..21c3be5768 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: 6,
+  minor: 7,
   patch: 0,
-  build: 10,
+  build: 11,
 };
diff --git a/server/src/migration/1646709533213-AddRegionCityToExIf.ts b/server/src/migration/1646709533213-AddRegionCityToExIf.ts
index 9b753cdcf1..e2d226cfa4 100644
--- a/server/src/migration/1646709533213-AddRegionCityToExIf.ts
+++ b/server/src/migration/1646709533213-AddRegionCityToExIf.ts
@@ -4,13 +4,13 @@ export class AddRegionCityToExIf1646709533213 implements MigrationInterface {
   public async up(queryRunner: QueryRunner): Promise<void> {
     await queryRunner.query(`
       ALTER TABLE exif
-        ADD COLUMN city varchar;
+        ADD COLUMN if not exists city varchar;
 
       ALTER TABLE exif
-        ADD COLUMN state varchar;
+        ADD COLUMN if not exists state varchar;
       
       ALTER TABLE exif
-        ADD COLUMN country varchar;
+        ADD COLUMN if not exists country varchar;
     `);
   }
 
diff --git a/server/src/migration/1648317474768-AddObjectColumnToSmartInfo.ts b/server/src/migration/1648317474768-AddObjectColumnToSmartInfo.ts
index 6016eb0553..bdf3dff5df 100644
--- a/server/src/migration/1648317474768-AddObjectColumnToSmartInfo.ts
+++ b/server/src/migration/1648317474768-AddObjectColumnToSmartInfo.ts
@@ -1,12 +1,10 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
+import { MigrationInterface, QueryRunner } from 'typeorm';
 
-export class AddObjectColumnToSmartInfo1648317474768
-  implements MigrationInterface
-{
+export class AddObjectColumnToSmartInfo1648317474768 implements MigrationInterface {
   public async up(queryRunner: QueryRunner): Promise<void> {
     await queryRunner.query(`
       ALTER TABLE smart_info
-        ADD COLUMN objects text[];
+        ADD COLUMN if not exists objects text[];
 
     `);
   }
diff --git a/server/src/migration/1649643216111-CreateSharedAlbumAndRelatedTables.ts b/server/src/migration/1649643216111-CreateSharedAlbumAndRelatedTables.ts
new file mode 100644
index 0000000000..ef633d6f12
--- /dev/null
+++ b/server/src/migration/1649643216111-CreateSharedAlbumAndRelatedTables.ts
@@ -0,0 +1,70 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class CreateSharedAlbumAndRelatedTables1649643216111 implements MigrationInterface {
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    // Create shared_albums
+    await queryRunner.query(`
+    create table if not exists shared_albums
+    (
+        id                      uuid                     default uuid_generate_v4()                  not null
+            constraint "PK_7f71c7b5bc7c87b8f94c9a93a00"
+                primary key,
+        "ownerId"               varchar                                                              not null,
+        "albumName"             varchar                  default 'Untitled Album'::character varying not null,
+        "createdAt"             timestamp with time zone default now()                               not null,
+        "albumThumbnailAssetId" varchar
+    );
+    
+    comment on column shared_albums."albumThumbnailAssetId" is 'Asset ID to be used as thumbnail';
+    `);
+
+    // Create user_shared_album
+    await queryRunner.query(`
+    create table if not exists user_shared_album
+    (
+        id             serial
+            constraint "PK_b6562316a98845a7b3e9a25cdd0"
+                primary key,
+        "albumId"      uuid not null
+            constraint "FK_7b3bf0f5f8da59af30519c25f18"
+                references shared_albums
+                on delete cascade,
+        "sharedUserId" uuid not null
+            constraint "FK_543c31211653e63e080ba882eb5"
+                references users,
+        constraint "PK_unique_user_in_album"
+            unique ("albumId", "sharedUserId")
+    );
+    `);
+
+    // Create asset_shared_album
+    await queryRunner.query(
+      `
+      create table if not exists asset_shared_album
+      (
+          id        serial
+              constraint "PK_a34e076afbc601d81938e2c2277"
+                  primary key,
+          "albumId" uuid not null
+              constraint "FK_a8b79a84996cef6ba6a3662825d"
+                  references shared_albums
+                  on delete cascade,
+          "assetId" uuid not null
+              constraint "FK_64f2e7d68d1d1d8417acc844a4a"
+                  references assets
+                  on delete cascade,
+          constraint "UQ_a1e2734a1ce361e7a26f6b28288"
+              unique ("albumId", "assetId")
+      );
+      `,
+    );
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`
+      drop table asset_shared_album;
+      drop table user_shared_album;
+      drop table shared_albums;
+    `);
+  }
+}