diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 674a6473f..32f2ee415 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,12 +18,12 @@ if (isRunningOnTravisAndIsNotPRBuild) { android { namespace = "fr.free.nrw.commons" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "fr.free.nrw.commons" minSdk = 21 - targetSdk = 34 + targetSdk = 35 versionCode = 1055 versionName = "5.6.1" diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt index ebbb4097a..865ad3ddb 100644 --- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.kt @@ -19,6 +19,7 @@ import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog import java.util.Collections import androidx.core.net.toUri +import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets import fr.free.nrw.commons.utils.handleWebUrl import fr.free.nrw.commons.utils.setUnderlinedText @@ -47,6 +48,7 @@ class AboutActivity : BaseActivity() { */ binding = ActivityAboutBinding.inflate(layoutInflater) val view: View = binding!!.root + applyEdgeToEdgeTopInsets(binding!!.toolbarLayout) setContentView(view) setSupportActionBar(binding!!.toolbarBinding.toolbar) diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt index 439ed1e92..0882ba117 100644 --- a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt @@ -9,6 +9,7 @@ import fr.free.nrw.commons.databinding.ActivityWelcomeBinding import fr.free.nrw.commons.databinding.PopupForCopyrightBinding import fr.free.nrw.commons.quiz.QuizActivity import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour class WelcomeActivity : BaseActivity() { @@ -23,6 +24,7 @@ class WelcomeActivity : BaseActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityWelcomeBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding!!.welcomePager.rootView) setContentView(binding!!.root) isQuiz = intent?.extras?.getBoolean("isQuiz", false) ?: false diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt index 7a665197b..688f508ae 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt @@ -22,6 +22,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.NavUtils import androidx.core.content.ContextCompat +import androidx.core.view.WindowCompat import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.R @@ -32,11 +33,13 @@ import fr.free.nrw.commons.contributions.MainActivity import fr.free.nrw.commons.databinding.ActivityLoginBinding import fr.free.nrw.commons.di.ApplicationlessInjection import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.utils.AbstractTextWatcher import fr.free.nrw.commons.utils.ActivityUtils.startActivityWithFlags import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour import fr.free.nrw.commons.utils.SystemThemeUtils import fr.free.nrw.commons.utils.ViewUtil.hideKeyboard +import fr.free.nrw.commons.utils.handleKeyboardInsets import fr.free.nrw.commons.utils.handleWebUrl import io.reactivex.disposables.CompositeDisposable import timber.log.Timber @@ -79,7 +82,14 @@ class LoginActivity : AccountAuthenticatorActivity() { delegate.installViewFactory() delegate.onCreate(savedInstanceState) + WindowCompat.getInsetsController(window, window.decorView) + .isAppearanceLightStatusBars = !isDarkTheme + + WindowCompat.setDecorFitsSystemWindows(window, false) + binding = ActivityLoginBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding!!.root) + binding?.aboutPrivacyPolicy?.handleKeyboardInsets() with(binding!!) { setContentView(root) diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt index 5b48ecd8f..22f557bcd 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.kt @@ -10,6 +10,7 @@ import android.widget.Toast import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.R import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import timber.log.Timber class SignupActivity : BaseActivity() { @@ -21,6 +22,7 @@ class SignupActivity : BaseActivity() { Timber.d("Signup Activity started") webView = WebView(this) + applyEdgeToEdgeAllInsets(webView!!) with(webView!!) { setContentView(this) webViewClient = MyWebViewClient() diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt index c998f96ac..fefe462a9 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt @@ -23,6 +23,7 @@ import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment import fr.free.nrw.commons.media.MediaDetailPagerFragment import fr.free.nrw.commons.media.MediaDetailProvider import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.utils.handleWebUrl import fr.free.nrw.commons.wikidata.model.WikiSite import fr.free.nrw.commons.wikidata.model.page.PageTitle @@ -57,6 +58,7 @@ class CategoryDetailsActivity : BaseActivity(), binding = ActivityCategoryDetailsBinding.inflate(layoutInflater) val view = binding.root + applyEdgeToEdgeAllInsets(view) setContentView(view) supportFragmentManager = getSupportFragmentManager() viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt index b9fa3e395..5c2c44ab5 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt @@ -34,6 +34,7 @@ import fr.free.nrw.commons.quiz.QuizChecker import fr.free.nrw.commons.settings.SettingsFragment import fr.free.nrw.commons.startWelcome import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.upload.UploadProgressActivity import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest import fr.free.nrw.commons.utils.ViewUtilWrapper @@ -112,6 +113,7 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding!!.root) setContentView(binding!!.root) setSupportActionBar(binding!!.toolbarBinding.toolbar) tabLayout = binding!!.fragmentMainNavTabLayout diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt index 7e7d7e4cd..2534b4aeb 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat +import androidx.core.view.ViewGroupCompat import androidx.lifecycle.ViewModelProvider import fr.free.nrw.commons.R import fr.free.nrw.commons.customselector.database.NotForUploadStatus @@ -56,6 +57,8 @@ import fr.free.nrw.commons.media.ZoomableActivity import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.upload.FileUtilsWrapper import fr.free.nrw.commons.utils.CustomSelectorUtils +import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets +import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -198,6 +201,9 @@ class CustomSelectorActivity : .fillMaxWidth(), ) } + ViewGroupCompat.installCompatInsetsDispatch(binding.root) + applyEdgeToEdgeTopInsets(toolbarBinding.toolbarLayout) + bottomSheetBinding.bottomLayout.applyEdgeToEdgeBottomPaddingInsets() val view = binding.root setContentView(view) diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt index 6ca2b06e4..0c3c5bdd0 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/FolderFragment.kt @@ -18,6 +18,7 @@ import fr.free.nrw.commons.databinding.FragmentCustomSelectorBinding import fr.free.nrw.commons.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.media.MediaClient import fr.free.nrw.commons.upload.FileProcessor +import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets import javax.inject.Inject /** @@ -99,6 +100,7 @@ class FolderFragment : CommonsDaggerSupportFragment() { selectorRV = binding?.selectorRv loader = binding?.loader with(binding?.selectorRv) { + this?.applyEdgeToEdgeBottomPaddingInsets() this?.layoutManager = gridLayoutManager this?.setHasFixedSize(true) this?.adapter = folderAdapter diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt index 6e08e30f1..4f37106cc 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt @@ -41,6 +41,7 @@ import fr.free.nrw.commons.media.MediaClient import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.upload.FileProcessor import fr.free.nrw.commons.upload.FileUtilsWrapper +import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -217,6 +218,7 @@ class ImageFragment : imageAdapter.setSingleSelection(singleSelection) gridLayoutManager = GridLayoutManager(context, getSpanCount()) with(binding?.selectorRv) { + this?.applyEdgeToEdgeBottomPaddingInsets() this?.layoutManager = gridLayoutManager this?.setHasFixedSize(true) this?.adapter = imageAdapter 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 44cefe4d5..89d43845b 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 @@ -7,6 +7,7 @@ import android.speech.RecognizerIntent import android.view.View import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.WindowCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import fr.free.nrw.commons.CommonsApplication @@ -20,9 +21,11 @@ import fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao import fr.free.nrw.commons.settings.Prefs import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets import fr.free.nrw.commons.upload.UploadMediaDetail import fr.free.nrw.commons.upload.UploadMediaDetailAdapter import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog +import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.Consumer import io.reactivex.schedulers.Schedulers @@ -87,6 +90,10 @@ class DescriptionEditActivity : super.onCreate(savedInstanceState) binding = ActivityDescriptionEditBinding.inflate(layoutInflater) + applyEdgeToEdgeBottomInsets(binding.btnEditSubmit) + WindowCompat.getInsetsController(window, window.decorView) + .isAppearanceLightStatusBars = false + binding.toolbar.applyEdgeToEdgeTopPaddingInsets() setContentView(binding.root) val bundle = intent.extras diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt index 7b7bb2cd5..0d7dfd218 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt @@ -22,6 +22,7 @@ import fr.free.nrw.commons.media.MediaDetailProvider import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.utils.FragmentUtils.isFragmentUIActive import fr.free.nrw.commons.utils.ViewUtil.hideKeyboard +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import io.reactivex.android.schedulers.AndroidSchedulers import timber.log.Timber import java.util.Date @@ -48,6 +49,7 @@ class SearchActivity : BaseActivity(), MediaDetailProvider, CategoryImagesCallba override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivitySearchBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding!!.root) setContentView(binding!!.root) title = getString(R.string.title_activity_search) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt index 4696ae8d4..32af67e95 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt @@ -24,6 +24,7 @@ import fr.free.nrw.commons.media.MediaDetailProvider import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.upload.structure.depictions.DepictModel import fr.free.nrw.commons.upload.structure.depictions.DepictedItem +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.utils.handleWebUrl import fr.free.nrw.commons.wikidata.WikidataConstants import io.reactivex.android.schedulers.AndroidSchedulers @@ -55,6 +56,7 @@ class WikidataItemDetailsActivity : BaseActivity(), MediaDetailProvider, Categor super.onCreate(savedInstanceState) binding = ActivityWikidataItemDetailsBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding!!.root) setContentView(binding!!.root) supportFragmentManager = getSupportFragmentManager() viewPagerAdapter = ViewPagerAdapter(this, getSupportFragmentManager()) diff --git a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt index a8b6ddf26..e4fedf2e4 100644 --- a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt @@ -25,6 +25,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.IntentCompat import androidx.core.os.BundleCompat import androidx.core.text.HtmlCompat +import androidx.core.view.WindowCompat import com.google.android.material.floatingactionbutton.FloatingActionButton import fr.free.nrw.commons.CameraPosition import fr.free.nrw.commons.CommonsApplication @@ -44,6 +45,8 @@ import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Compani import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM import fr.free.nrw.commons.utils.DialogUtil import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL +import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets +import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets import fr.free.nrw.commons.utils.handleGeoCoordinates import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers @@ -330,6 +333,9 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { */ private fun getToolbarUI() { val toolbar: ConstraintLayout = findViewById(R.id.location_picker_toolbar) + WindowCompat.getInsetsController(window, window.decorView) + .isAppearanceLightStatusBars = false + toolbar.applyEdgeToEdgeTopPaddingInsets() largeToolbarText = findViewById(R.id.location_picker_toolbar_primary_text_view) smallToolbarText = findViewById(R.id.location_picker_toolbar_secondary_text_view) toolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.primaryColor)) @@ -460,6 +466,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { */ private fun addPlaceSelectedButton() { placeSelectedButton = findViewById(R.id.location_chosen_button) + applyEdgeToEdgeBottomInsets(placeSelectedButton) placeSelectedButton.setOnClickListener { placeSelected() } } diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt index 76975964b..4a43bf470 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View +import androidx.core.view.ViewGroupCompat import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar @@ -19,8 +20,10 @@ import fr.free.nrw.commons.databinding.ActivityNotificationBinding import fr.free.nrw.commons.notification.models.Notification import fr.free.nrw.commons.notification.models.NotificationType import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets import fr.free.nrw.commons.utils.NetworkUtils import fr.free.nrw.commons.utils.ViewUtil +import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets import fr.free.nrw.commons.utils.handleWebUrl import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers @@ -56,6 +59,9 @@ class NotificationActivity : BaseActivity() { super.onCreate(savedInstanceState) isRead = intent.getStringExtra("title") == "read" binding = ActivityNotificationBinding.inflate(layoutInflater) + ViewGroupCompat.installCompatInsetsDispatch(binding.root) + applyEdgeToEdgeTopInsets(binding.toolbar.toolbar) + binding.listView.applyEdgeToEdgeBottomPaddingInsets() setContentView(binding.root) mNotificationWorkerFragment = supportFragmentManager.findFragmentByTag( tagNotificationWorkerFragment diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt index d80be9ea2..c368d6cd4 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt @@ -21,6 +21,7 @@ import fr.free.nrw.commons.databinding.ActivityProfileBinding import fr.free.nrw.commons.profile.achievements.AchievementsFragment import fr.free.nrw.commons.profile.leaderboard.LeaderboardFragment import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.utils.DialogUtil import java.io.File import java.io.FileOutputStream @@ -61,6 +62,7 @@ class ProfileActivity : BaseActivity() { super.onCreate(savedInstanceState) binding = ActivityProfileBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding.root) setContentView(binding.root) setSupportActionBar(binding.toolbarBinding.toolbar) diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt index e65b819e5..11fd1e6a6 100644 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt @@ -3,9 +3,11 @@ package fr.free.nrw.commons.quiz import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle +import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.WindowCompat import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import com.facebook.drawee.drawable.ProgressBarDrawable @@ -15,6 +17,7 @@ import fr.free.nrw.commons.databinding.ActivityQuizBinding import java.util.ArrayList import fr.free.nrw.commons.R +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets class QuizActivity : AppCompatActivity() { @@ -37,7 +40,11 @@ class QuizActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() binding = ActivityQuizBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding.root) + WindowCompat.getInsetsController(window, window.decorView) + .isAppearanceLightStatusBars = true setContentView(binding.root) quizController.initialize(this) diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt index 81372b4a6..6979edd15 100644 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt @@ -12,9 +12,11 @@ import android.view.MenuItem import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.WindowCompat import fr.free.nrw.commons.databinding.ActivityQuizResultBinding import java.io.File @@ -22,6 +24,7 @@ import java.io.FileOutputStream import fr.free.nrw.commons.R import fr.free.nrw.commons.contributions.MainActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets /** @@ -35,7 +38,11 @@ class QuizResultActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() binding = ActivityQuizResultBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding!!.root) + WindowCompat.getInsetsController(window, window.decorView) + .isAppearanceLightStatusBars = true setContentView(binding?.root) setSupportActionBar(binding?.toolbar?.toolbar) diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt index 20f289f8f..42a75aeac 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.kt @@ -16,6 +16,7 @@ import fr.free.nrw.commons.databinding.ActivityReviewBinding import fr.free.nrw.commons.delete.DeleteHelper import fr.free.nrw.commons.media.MediaDetailFragment import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.utils.DialogUtil import fr.free.nrw.commons.utils.ViewUtil import io.reactivex.android.schedulers.AndroidSchedulers @@ -73,6 +74,7 @@ class ReviewActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityReviewBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding.root) setContentView(binding.root) setSupportActionBar(binding.toolbarBinding?.toolbar) diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt index 91c88d7b0..233e688f4 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.MenuItem import fr.free.nrw.commons.databinding.ActivitySettingsBinding import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets /** @@ -21,6 +22,7 @@ class SettingsActivity : BaseActivity() { super.onCreate(savedInstanceState) binding = ActivitySettingsBinding.inflate(layoutInflater) val view = binding.root + applyEdgeToEdgeAllInsets(view) setContentView(view) setSupportActionBar(binding.toolbarBinding.toolbar) diff --git a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt index d2d936460..d317a7d35 100644 --- a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.kt @@ -4,6 +4,7 @@ import android.content.res.Configuration import android.os.Bundle import android.util.DisplayMetrics import android.view.WindowManager +import androidx.activity.enableEdgeToEdge import javax.inject.Inject import javax.inject.Named import fr.free.nrw.commons.R @@ -36,6 +37,7 @@ abstract class BaseActivity : CommonsDaggerAppCompatActivity() { 1f ) adjustFontScale(resources.configuration, fontScale) + enableEdgeToEdge() } override fun onResume() { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt index 38e7dace8..74597bc14 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt @@ -38,6 +38,7 @@ import fr.free.nrw.commons.mwapi.UserClient import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.settings.Prefs import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import fr.free.nrw.commons.upload.ThumbnailsAdapter.OnThumbnailDeletedListener import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment import fr.free.nrw.commons.upload.depicts.DepictsFragment @@ -177,6 +178,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C presenter?.setupBasicKvStoreFactory { BasicKvStore(this@UploadActivity, it) } _binding = ActivityUploadBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(_binding!!.root, false) setContentView(binding.root) // Overrides the back button to make sure the user is prepared to lose their progress diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt index 3cf9d3a65..665f106e2 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt @@ -10,6 +10,7 @@ import fr.free.nrw.commons.ViewPagerAdapter import fr.free.nrw.commons.contributions.ContributionDao import fr.free.nrw.commons.databinding.ActivityUploadProgressBinding import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets import javax.inject.Inject /** @@ -35,6 +36,7 @@ class UploadProgressActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityUploadProgressBinding.inflate(layoutInflater) + applyEdgeToEdgeAllInsets(binding.root) setContentView(binding.root) viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager) binding.uploadProgressViewPager.setAdapter(viewPagerAdapter) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt index 43a9c3236..6ece53170 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt @@ -50,6 +50,7 @@ import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK import fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished import fr.free.nrw.commons.utils.ViewUtil.showLongToast +import fr.free.nrw.commons.utils.handleKeyboardInsets import timber.log.Timber import java.io.File import java.util.ArrayList @@ -153,6 +154,7 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentUploadMediaDetailFragmentBinding.inflate(inflater, container, false) + _binding!!.mediaDetailCardView.handleKeyboardInsets() return binding.root } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt index 332c8d023..95fa62a20 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/ConfigUtils.kt @@ -12,9 +12,9 @@ object ConfigUtils { val isBetaFlavour: Boolean = BuildConfig.FLAVOR == "beta" @JvmStatic - private fun Context.getVersionName(): String = + private fun Context.getVersionName(): String? = try { - packageManager.getPackageInfo(packageName, 0).versionName + packageManager.getPackageInfo(packageName, 0).versionName ?: BuildConfig.VERSION_NAME } catch (e: PackageManager.NameNotFoundException) { BuildConfig.VERSION_NAME } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt new file mode 100644 index 000000000..d0c2b12e8 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/EdgeToEdgeUtils.kt @@ -0,0 +1,229 @@ +package fr.free.nrw.commons.utils + +import android.view.View +import android.view.ViewGroup.MarginLayoutParams +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsAnimationCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.marginBottom +import androidx.core.view.marginLeft +import androidx.core.view.marginRight +import androidx.core.view.marginTop +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding +import fr.free.nrw.commons.R + +/** + * Applies edge-to-edge system bar insets to a [View]’s margins using a custom adjustment block. + * + * Stores the initial margins to ensure inset calculations are additive, and applies the provided + * [block] with an [InsetsAccumulator] containing initial and system bar inset values. + * + * @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars]. + * @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views. + * @param block Lambda applied to update [MarginLayoutParams] using the accumulated insets. + */ +fun View.applyEdgeToEdgeInsets( + typeMask: Int = WindowInsetsCompat.Type.systemBars(), + shouldConsumeInsets: Boolean = true, + block: MarginLayoutParams.(InsetsAccumulator) -> Unit +) { + ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets -> + val insets = windowInsets.getInsets(typeMask) + + val initialTop = if (view.getTag(R.id.initial_margin_top) != null) { + view.getTag(R.id.initial_margin_top) as Int + } else { + view.setTag(R.id.initial_margin_top, view.marginTop) + view.marginTop + } + + val initialBottom = if (view.getTag(R.id.initial_margin_bottom) != null) { + view.getTag(R.id.initial_margin_bottom) as Int + } else { + view.setTag(R.id.initial_margin_bottom, view.marginBottom) + view.marginBottom + } + + val initialLeft = if (view.getTag(R.id.initial_margin_left) != null) { + view.getTag(R.id.initial_margin_left) as Int + } else { + view.setTag(R.id.initial_margin_left, view.marginLeft) + view.marginLeft + } + + val initialRight = if (view.getTag(R.id.initial_margin_right) != null) { + view.getTag(R.id.initial_margin_right) as Int + } else { + view.setTag(R.id.initial_margin_right, view.marginRight) + view.marginRight + } + + val accumulator = InsetsAccumulator( + initialTop, + insets.top, + initialBottom, + insets.bottom, + initialLeft, + insets.left, + initialRight, + insets.right + ) + + view.updateLayoutParams { + apply { block(accumulator) } + } + + if(shouldConsumeInsets) WindowInsetsCompat.CONSUMED else windowInsets + } +} + +/** + * Applies edge-to-edge system bar insets to the top padding of the view. + * + * @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars]. + */ +fun View.applyEdgeToEdgeTopPaddingInsets( + typeMask: Int = WindowInsetsCompat.Type.systemBars(), +) { + ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets -> + val insets = windowInsets.getInsets(typeMask) + + view.updatePadding( + left = insets.left, + right = insets.right, + top = insets.top + ) + + WindowInsetsCompat.CONSUMED + } +} + +/** + * Applies edge-to-edge system bar insets to the bottom padding of the view. + * + * @param typeMask The type of window insets to apply. Defaults to [WindowInsetsCompat.Type.systemBars]. + */ +fun View.applyEdgeToEdgeBottomPaddingInsets( + typeMask: Int = WindowInsetsCompat.Type.systemBars(), +) { + ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets -> + val insets = windowInsets.getInsets(typeMask) + + view.updatePadding( + left = insets.left, + right = insets.right, + bottom = insets.bottom + ) + + WindowInsetsCompat.CONSUMED + } +} + +/** + * Applies system bar insets to all margins (top, bottom, left, right) of the view. + * + * @param view The target view. + * @param shouldConsumeInsets If `true`, the insets are consumed and not propagated to child views. + */ +fun applyEdgeToEdgeAllInsets( + view: View, + shouldConsumeInsets: Boolean = true +) = view.applyEdgeToEdgeInsets(shouldConsumeInsets = shouldConsumeInsets) { insets -> + leftMargin = insets.left + rightMargin = insets.right + topMargin = insets.top + bottomMargin = insets.bottom +} + +/** + * Applies system bar insets to the top and horizontal margins of the view. + * + * @param view The target view. + */ +fun applyEdgeToEdgeTopInsets(view: View) = view.applyEdgeToEdgeInsets { insets -> + leftMargin = insets.left + rightMargin = insets.right + topMargin = insets.top +} + +/** + * Applies system bar insets to the bottom and horizontal margins of the view. + * + * @param view The target view. + */ +fun applyEdgeToEdgeBottomInsets(view: View) = view.applyEdgeToEdgeInsets { insets -> + leftMargin = insets.left + rightMargin = insets.right + bottomMargin = insets.bottom +} + +/** + * Adjusts a [View]'s bottom margin dynamically to account for the on-screen keyboard (IME), + * ensuring the view remains visible above the keyboard during transitions. + * + * Preserves the initial margin, adjusts during IME visibility changes, + * and accounts for navigation bar insets to avoid double offsets. + */ +fun View.handleKeyboardInsets() { + var existingBottomMargin = 0 + + ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets -> + existingBottomMargin = if (view.getTag(R.id.initial_margin_bottom) != null) { + view.getTag(R.id.initial_margin_bottom) as Int + } else { + view.setTag(R.id.initial_margin_bottom, view.marginBottom) + view.marginBottom + } + + WindowInsetsCompat.CONSUMED + } + + // Animate during IME transition + ViewCompat.setWindowInsetsAnimationCallback( + this, + object : WindowInsetsAnimationCompat.Callback( + DISPATCH_MODE_CONTINUE_ON_SUBTREE + ) { + override fun onProgress( + insets: WindowInsetsCompat, + runningAnimations: MutableList + ): WindowInsetsCompat { + val lp = layoutParams as MarginLayoutParams + val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) + val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) + + // Avoid extra space due to system nav bar when the keyboard is shown + val imeBottomMargin = imeInsets.bottom - navBarInsets.bottom + + lp.bottomMargin = if(imeVisible && imeBottomMargin >= existingBottomMargin) + imeBottomMargin + existingBottomMargin + else existingBottomMargin + + layoutParams = lp + return WindowInsetsCompat.CONSUMED + } + } + ) +} + +/** + * Holds both initial margin values and system bar insets, providing summed values + * for each side (top, bottom, left, right) to apply in layout updates. + */ +data class InsetsAccumulator( + private val initialTop: Int, + private val insetTop: Int, + private val initialBottom: Int, + private val insetBottom: Int, + private val initialLeft: Int, + private val insetLeft: Int, + private val initialRight: Int, + private val insetRight: Int +) { + val top = initialTop + insetTop + val bottom = initialBottom + insetBottom + val left = initialLeft + insetLeft + val right = initialRight + insetRight +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt index ebff3d054..fa538bb21 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt @@ -24,6 +24,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers import timber.log.Timber +import androidx.core.graphics.createBitmap /** * Created by blueSir9 on 3/10/17. @@ -307,16 +308,19 @@ object ImageUtils { * * @return */ @JvmStatic - fun addRedBorder(bitmap: Bitmap, borderSize: Int, context: Context): Bitmap { - val bmpWithBorder = Bitmap.createBitmap( - bitmap.width + borderSize * 2, - bitmap.height + borderSize * 2, - bitmap.config - ) - val canvas = Canvas(bmpWithBorder) - canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed)) - canvas.drawBitmap(bitmap, borderSize.toFloat(), borderSize.toFloat(), null) - return bmpWithBorder + fun addRedBorder(bitmap: Bitmap, borderSize: Int, context: Context): Bitmap? { + return bitmap.config?.let { config -> + val bmpWithBorder = + createBitmap( + width = bitmap.width + borderSize * 2, + height = bitmap.height + borderSize * 2, + config = config + ) + val canvas = Canvas(bmpWithBorder) + canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed)) + canvas.drawBitmap(bitmap, borderSize.toFloat(), borderSize.toFloat(), null) + return bmpWithBorder + } } /** diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml index a8b60dea3..800c8aa0b 100644 --- a/app/src/main/res/layout/activity_notification.xml +++ b/app/src/main/res/layout/activity_notification.xml @@ -35,6 +35,7 @@ android:scrollbars="vertical" android:fadeScrollbars="false" android:scrollbarThumbVertical="@color/primaryColor" + android:clipToPadding="false" android:scrollbarSize="@dimen/dimen_6"/> diff --git a/app/src/main/res/layout/fragment_custom_selector.xml b/app/src/main/res/layout/fragment_custom_selector.xml index 03381fd24..b016b6605 100644 --- a/app/src/main/res/layout/fragment_custom_selector.xml +++ b/app/src/main/res/layout/fragment_custom_selector.xml @@ -34,6 +34,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/switchWidget" + android:clipToPadding="false" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 02c314a4a..cdc8ce387 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -123,6 +123,9 @@ @drawable/ic_arrow_back_black false false + + @android:color/transparent + @android:color/transparent