diff --git a/CHANGELOG.md b/CHANGELOG.md
index 405b7d9de..612011688 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Wikimedia Commons for Android
 
+## v4.0.0
+- Added map showing nearby Commons pictures
+- Added custom SPARQL queries
+- Added user profiles
+- Added custom picture selector
+- Various bugfixes
+- Updated target SDK to 30
+
 ## v3.1.1
 - Optimized Nearby query
 - Added Sweden's property for WLM 2021
diff --git a/app/build.gradle b/app/build.gradle
index daff00e52..d94dbb9dd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -152,7 +152,7 @@ dependencies {
     implementation 'com.github.bumptech.glide:glide:4.12.0'
     annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
 
-    implementation("io.github.coordinates2country:coordinates2country-android:1.2") {  exclude group: 'com.google.android', module: 'android' }
+    implementation("io.github.coordinates2country:coordinates2country-android:1.3") {  exclude group: 'com.google.android', module: 'android' }
 }
 
 task disableAnimations(type: Exec) {
@@ -173,8 +173,8 @@ android {
     defaultConfig {
         //applicationId 'fr.free.nrw.commons'
 
-        versionCode 1025
-        versionName '3.1.1'
+        versionCode 1026
+        versionName '4.0.0'
         setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
 
         minSdkVersion 19
diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java
index 08e54a114..70c370836 100644
--- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java
+++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java
@@ -309,22 +309,18 @@ public class BookmarkItemsDao {
             if (from == to) {
                 return;
             }
-            if (from < 7) {
+            if (from < 18) {
+                // doesn't exist yet
                 from++;
                 onUpdate(db, from, to);
                 return;
             }
 
-            if (from == 7) {
+            if (from == 18) {
+                // table added in version 19
                 onCreate(db);
                 from++;
                 onUpdate(db, from, to);
-                return;
-            }
-
-            if (from == 8) {
-                from++;
-                onUpdate(db, from, to);
             }
         }
     }
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
index 25d6e7d33..45ae66e4a 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java
@@ -662,7 +662,7 @@ public class ContributionsFragment
     }
 
     public boolean backButtonClicked() {
-        if (null != mediaDetailPagerFragment && mediaDetailPagerFragment.isVisible()) {
+        if (mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible()) {
             if (store.getBoolean("displayNearbyCardView", true) && !isUserProfile) {
                 if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
                     nearbyNotificationCardView.setVisibility(View.VISIBLE);
@@ -679,9 +679,10 @@ public class ContributionsFragment
             }else {
                 fetchCampaigns();
             }
-            if(getActivity() instanceof MainActivity) {
+            if (getActivity() instanceof MainActivity) {
                 // Fragment is associated with MainActivity
-                ((MainActivity)getActivity()).showTabs();
+                ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+                ((MainActivity) getActivity()).showTabs();
             }
             return true;
         }
diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
index 207101638..7ee417fbc 100644
--- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
@@ -4,9 +4,7 @@ import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
-
 import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao;
-import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table;
 import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
 import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
 import fr.free.nrw.commons.category.CategoryDao;
@@ -16,7 +14,7 @@ import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
 public class DBOpenHelper  extends SQLiteOpenHelper {
 
     private static final String DATABASE_NAME = "commons.db";
-    private static final int DATABASE_VERSION = 19;
+    private static final int DATABASE_VERSION = 20;
     public static final String CONTRIBUTIONS_TABLE = "contributions";
     private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
 
diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
index 04c90e492..8d7795f5e 100644
--- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt
@@ -154,8 +154,7 @@ class DescriptionEditActivity : BaseActivity(), UploadMediaDetailAdapter.EventLi
                     buffer.append("}}, ")
                 }
             }
-            buffer.deleteCharAt(buffer.length - 1)
-            buffer.deleteCharAt(buffer.length - 1)
+            buffer.replace(", $".toRegex(), "")
             buffer.append(descriptionEnd)
         }
         val returningIntent = Intent()
diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java
index 1af1b50e6..c4a4bf518 100644
--- a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java
+++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java
@@ -185,23 +185,17 @@ public class RecentLanguagesDao {
             if (from == to) {
                 return;
             }
-            if (from < 6) {
+            if (from < 19) {
                 // doesn't exist yet
                 from++;
                 onUpdate(db, from, to);
                 return;
             }
-            if (from == 6) {
-                // table added in version 7
+            if (from == 19) {
+                // table added in version 20
                 onCreate(db);
                 from++;
                 onUpdate(db, from, to);
-                return;
-            }
-            if (from == 7) {
-                from++;
-                onUpdate(db, from, to);
-                return;
             }
         }
     }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cc032ef40..a251d21ed 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -674,7 +674,7 @@ Upload your first media by tapping on the add button.
   Done
   Back
   Welcome to Custom Picture Selector
-  This picker shows differently pictures that are already to Commons.
+  This picker shows you which pictures you have already uploaded to Commons.
   Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded. \n Touch and hold for image preview.
   Awesome
   This image has already been uploaded to Commons.
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt
index 73504acdc..92778dea7 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDaoTest.kt
@@ -271,9 +271,84 @@ class BookmarkItemsDaoTest {
     @Test
     fun migrateTableVersionFrom_v7_to_v8() {
         onUpdate(database, 7, 8)
+        // Table didn't change in version 8
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v8_to_v9() {
+        onUpdate(database, 8, 9)
+        // Table didn't change in version 9
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v9_to_v10() {
+        onUpdate(database, 9, 10)
+        // Table didn't change in version 10
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v10_to_v11() {
+        onUpdate(database, 10, 11)
+        // Table didn't change in version 11
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v11_to_v12() {
+        onUpdate(database, 11, 12)
+        // Table didn't change in version 12
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v12_to_v13() {
+        onUpdate(database, 12, 13)
+        // Table didn't change in version 13
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v13_to_v14() {
+        onUpdate(database, 13, 14)
+        // Table didn't change in version 14
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v14_to_v15() {
+        onUpdate(database, 14, 15)
+        // Table didn't change in version 15
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v15_to_v16() {
+        onUpdate(database, 15, 16)
+        // Table didn't change in version 16
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v16_to_v17() {
+        onUpdate(database, 16, 17)
+        // Table didn't change in version 17
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v18_to_v19() {
+        onUpdate(database, 18, 19)
         verify(database).execSQL(CREATE_TABLE_STATEMENT)
     }
 
+    @Test
+    fun migrateTableVersionFrom_v19_to_v19() {
+        onUpdate(database, 19, 19)
+        verifyZeroInteractions(database)
+    }
 
     private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
 
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt
index 122f2e49c..abe8708bf 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/recentlanguages/RecentLanguagesDaoUnitTest.kt
@@ -177,20 +177,103 @@ class RecentLanguagesDaoUnitTest {
     @Test
     fun migrateTableVersionFrom_v5_to_v6() {
         onUpdate(database, 5, 6)
-        // Table didnt exist before v7
+        // Table didnt exist in version 6
         verifyZeroInteractions(database)
     }
 
     @Test
     fun migrateTableVersionFrom_v6_to_v7() {
         onUpdate(database, 6, 7)
-        verify(database).execSQL(CREATE_TABLE_STATEMENT)
+        // Table didnt exist in version 7
+        verifyZeroInteractions(database)
     }
 
     @Test
     fun migrateTableVersionFrom_v7_to_v8() {
         onUpdate(database, 7, 8)
-        // Table didnt change in version 8
+        // Table didnt exist in version 8
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v8_to_v9() {
+        onUpdate(database, 8, 9)
+        // Table didnt exist in version 9
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v9_to_v10() {
+        onUpdate(database, 9, 10)
+        // Table didnt exist in version 10
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v10_to_v11() {
+        onUpdate(database, 10, 11)
+        // Table didnt exist in version 11
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v11_to_v12() {
+        onUpdate(database, 11, 12)
+        // Table didnt exist in version 12
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v12_to_v13() {
+        onUpdate(database, 12, 13)
+        // Table didnt exist in version 13
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v13_to_v14() {
+        onUpdate(database, 13, 14)
+        // Table didnt exist in version 14
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v14_to_v15() {
+        onUpdate(database, 14, 15)
+        // Table didnt exist in version 15
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v15_to_v16() {
+        onUpdate(database, 15, 16)
+        // Table didnt exist in version 16
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v16_to_v17() {
+        onUpdate(database, 16, 17)
+        // Table didnt exist in version 17
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v18_to_v19() {
+        onUpdate(database, 18, 19)
+        // Table didnt exist in version 18
+        verifyZeroInteractions(database)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v19_to_v20() {
+        onUpdate(database, 19, 20)
+        verify(database).execSQL(CREATE_TABLE_STATEMENT)
+    }
+
+    @Test
+    fun migrateTableVersionFrom_v20_to_v20() {
+        onUpdate(database, 20, 20)
         verifyZeroInteractions(database)
     }