#275 system bar transparency review
This commit is contained in:
parent
b29425e322
commit
73e9073407
22 changed files with 124 additions and 90 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -40,7 +40,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
- slideshow
|
- slideshow
|
||||||
- set wallpaper from any media
|
- set wallpaper from any media
|
||||||
- optional dynamic accent color on Android 12+
|
- optional dynamic accent color on Android >=12
|
||||||
- Search: date/dimension/size field equality (undocumented)
|
- Search: date/dimension/size field equality (undocumented)
|
||||||
- support Android 13 (API 33)
|
- support Android 13 (API 33)
|
||||||
- Turkish translation (thanks metezd)
|
- Turkish translation (thanks metezd)
|
||||||
|
@ -135,7 +135,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- app launch despite faulty storage volumes on Android 11+
|
- app launch despite faulty storage volumes on Android >=11
|
||||||
|
|
||||||
## <a id="v1.6.2"></a>[v1.6.2] - 2022-03-07
|
## <a id="v1.6.2"></a>[v1.6.2] - 2022-03-07
|
||||||
|
|
||||||
|
@ -320,7 +320,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- Info: improved display for PNG text metadata, XMP and others
|
- Info: improved display for PNG text metadata, XMP and others
|
||||||
- Export: output format selection
|
- Export: output format selection
|
||||||
- Search: added raw filter
|
- Search: added raw filter
|
||||||
- Support modifying files in the Download folder on Android 11+
|
- Support modifying files in the Download folder on Android >=11
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ All notable changes to this project will be documented in this file.
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- hide root album of hidden path
|
- hide root album of hidden path
|
||||||
- gesture & spacing handling for Android 10+ navigation gestures
|
- gesture & spacing handling for Android >=10 navigation gestures
|
||||||
- renaming was leaving behind obsolete items in some cases
|
- renaming was leaving behind obsolete items in some cases
|
||||||
- speeding up videos on Xiaomi devices
|
- speeding up videos on Xiaomi devices
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- Map & Stats from selection
|
- Map & Stats from selection
|
||||||
- Map: item browsing, rotation control
|
- Map: item browsing, rotation control
|
||||||
- Navigation menu customization
|
- Navigation menu customization
|
||||||
- shortcut support on older devices (API < 26)
|
- shortcut support on older devices (API <26)
|
||||||
- support Android 12/S (API 31)
|
- support Android 12/S (API 31)
|
||||||
|
|
||||||
## [v1.4.8] - 2021-08-08
|
## [v1.4.8] - 2021-08-08
|
||||||
|
@ -407,7 +407,7 @@ All notable changes to this project will be documented in this file.
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- auto album identification and naming
|
- auto album identification and naming
|
||||||
- opening HEIC images from downloads content URI on Android R+
|
- opening HEIC images from downloads content URI on Android >=11
|
||||||
|
|
||||||
## [v1.4.7] - 2021-08-06 [YANKED]
|
## [v1.4.7] - 2021-08-06 [YANKED]
|
||||||
|
|
||||||
|
@ -611,7 +611,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
- Viewer: support for multi-track HEIF
|
- Viewer: support for multi-track HEIF
|
||||||
- Viewer: export image (including multipage TIFF/HEIF and images embedded in XMP)
|
- Viewer: export image (including multipage TIFF/HEIF and images embedded in XMP)
|
||||||
- Info: show owner app (Android Q and up)
|
- Info: show owner app (Android >=10)
|
||||||
- listen to Media Store changes
|
- listen to Media Store changes
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -638,7 +638,7 @@ upgraded libtiff to 4.2.0 for TIFF decoding
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- prevent scrolling when using Android Q style gesture navigation
|
- prevent scrolling when using Android 10 style gesture navigation
|
||||||
|
|
||||||
## [v1.3.1] - 2021-01-04
|
## [v1.3.1] - 2021-01-04
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ This change eventually prevents building the app with Flutter v3.0.2.
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Scoped storage on Android Q is inconvenient because users need to confirm edition on each individual file.
|
Scoped storage on Android 10 is inconvenient because users need to confirm edition on each individual file.
|
||||||
So we request `WRITE_EXTERNAL_STORAGE` until Q (29), and enable `requestLegacyExternalStorage`
|
So we request `WRITE_EXTERNAL_STORAGE` until Android 10 (API 29), and enable `requestLegacyExternalStorage`
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
@ -31,25 +31,25 @@ This change eventually prevents building the app with Flutter v3.0.2.
|
||||||
<!-- to show foreground service progress via notification -->
|
<!-- to show foreground service progress via notification -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<!-- to access media with original metadata with scoped storage (Android Q+) -->
|
<!-- to access media with original metadata with scoped storage (Android >=10) -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||||
|
|
||||||
<!-- TODO TLAD remove this permission when this is fixed: https://github.com/flutter/flutter/issues/42451 -->
|
<!-- TODO TLAD remove this permission when this is fixed: https://github.com/flutter/flutter/issues/42451 -->
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
<!-- for API < 26 -->
|
<!-- for API <26 -->
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
|
|
||||||
<!-- allow install on API 19, but Google Maps is from API 20 -->
|
<!-- allow install on API 19, but Google Maps is from API 20 -->
|
||||||
<uses-sdk tools:overrideLibrary="io.flutter.plugins.googlemaps" />
|
<uses-sdk tools:overrideLibrary="io.flutter.plugins.googlemaps" />
|
||||||
|
|
||||||
<!-- from Android R, we should define <queries> to make other apps visible to this app -->
|
<!-- from Android 11, we should define <queries> to make other apps visible to this app -->
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
</intent>
|
</intent>
|
||||||
<!--
|
<!--
|
||||||
from Android R, `url_launcher` method `canLaunchUrl()` will return false,
|
from Android 11, `url_launcher` method `canLaunchUrl()` will return false,
|
||||||
if appropriate intents are not declared, cf https://pub.dev/packages/url_launcher#configuration=
|
if appropriate intents are not declared, cf https://pub.dev/packages/url_launcher#configuration=
|
||||||
-->
|
-->
|
||||||
<!-- to open https URLs -->
|
<!-- to open https URLs -->
|
||||||
|
|
|
@ -285,7 +285,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
val filters = intent.getStringArrayExtra(EXTRA_KEY_FILTERS_ARRAY)?.toList()
|
val filters = intent.getStringArrayExtra(EXTRA_KEY_FILTERS_ARRAY)?.toList()
|
||||||
if (filters != null) return filters
|
if (filters != null) return filters
|
||||||
|
|
||||||
// fallback for shortcuts created on API < 26
|
// fallback for shortcuts created on API <26
|
||||||
val filterString = intent.getStringExtra(EXTRA_KEY_FILTERS_STRING)
|
val filterString = intent.getStringExtra(EXTRA_KEY_FILTERS_STRING)
|
||||||
if (filterString != null) {
|
if (filterString != null) {
|
||||||
return filterString.split(EXTRA_STRING_ARRAY_SEPARATOR)
|
return filterString.split(EXTRA_STRING_ARRAY_SEPARATOR)
|
||||||
|
|
|
@ -56,7 +56,7 @@ class ThumbnailFetcher internal constructor(
|
||||||
try {
|
try {
|
||||||
if (!customFetch && (width == defaultSize || height == defaultSize) && !isFlipped) {
|
if (!customFetch && (width == defaultSize || height == defaultSize) && !isFlipped) {
|
||||||
// Fetch low quality thumbnails when size is not specified.
|
// Fetch low quality thumbnails when size is not specified.
|
||||||
// As of Android R, the Media Store content resolver may return a thumbnail
|
// As of Android 11, the Media Store content resolver may return a thumbnail
|
||||||
// that is automatically rotated according to EXIF orientation, but not flipped,
|
// that is automatically rotated according to EXIF orientation, but not flipped,
|
||||||
// so we skip this step for flipped entries.
|
// so we skip this step for flipped entries.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
@ -108,7 +108,7 @@ class ThumbnailFetcher internal constructor(
|
||||||
} else {
|
} else {
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
var bitmap = MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null)
|
var bitmap = MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null)
|
||||||
// from Android Q, returned thumbnail is already rotated according to EXIF orientation
|
// from Android 10, returned thumbnail is already rotated according to EXIF orientation
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && bitmap != null) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && bitmap != null) {
|
||||||
bitmap = applyExifOrientation(context, bitmap, rotationDegrees, isFlipped)
|
bitmap = applyExifOrientation(context, bitmap, rotationDegrees, isFlipped)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package deckers.thibault.aves.channel.calls.window
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
@ -60,8 +59,4 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
|
||||||
}
|
}
|
||||||
result.success(true)
|
result.success(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val LOG_TAG = LogUtils.createTag<ActivityWindowHandler>()
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -88,7 +88,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
error("requestMediaFileAccess-unsupported", "media file bulk access is not allowed before Android R", null)
|
error("requestMediaFileAccess-unsupported", "media file bulk access is not allowed before Android 11", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,7 +193,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
val dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)
|
||||||
val dateTakenColumn = cursor.getColumnIndex(MediaColumns.DATE_TAKEN)
|
val dateTakenColumn = cursor.getColumnIndex(MediaColumns.DATE_TAKEN)
|
||||||
|
|
||||||
// image & video for API >= Q, only for images for API < Q
|
// image & video for API >=29, only for images for API <29
|
||||||
val orientationColumn = cursor.getColumnIndex(MediaColumns.ORIENTATION)
|
val orientationColumn = cursor.getColumnIndex(MediaColumns.ORIENTATION)
|
||||||
|
|
||||||
// video only
|
// video only
|
||||||
|
@ -347,7 +347,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
} catch (securityException: SecurityException) {
|
} catch (securityException: SecurityException) {
|
||||||
// even if the app has access permission granted on the containing directory,
|
// even if the app has access permission granted on the containing directory,
|
||||||
// the delete request may yield a `RecoverableSecurityException` on Android 10+
|
// the delete request may yield a `RecoverableSecurityException` on Android >=10
|
||||||
// when the underlying file no longer exists and this is an orphaned entry in the Media Store
|
// when the underlying file no longer exists and this is an orphaned entry in the Media Store
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && contextWrapper is Activity) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && contextWrapper is Activity) {
|
||||||
Log.w(LOG_TAG, "caught a security exception when attempting to delete uri=$uri", securityException)
|
Log.w(LOG_TAG, "caught a security exception when attempting to delete uri=$uri", securityException)
|
||||||
|
@ -876,7 +876,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
private val VIDEO_PROJECTION = arrayOf(
|
private val VIDEO_PROJECTION = arrayOf(
|
||||||
*BASE_PROJECTION,
|
*BASE_PROJECTION,
|
||||||
MediaColumns.DURATION,
|
MediaColumns.DURATION,
|
||||||
// `ORIENTATION` was only available for images before Android Q
|
// `ORIENTATION` was only available for images before Android 10
|
||||||
*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) arrayOf(
|
*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) arrayOf(
|
||||||
MediaStore.MediaColumns.ORIENTATION,
|
MediaStore.MediaColumns.ORIENTATION,
|
||||||
) else emptyArray()
|
) else emptyArray()
|
||||||
|
|
|
@ -2,7 +2,7 @@ package deckers.thibault.aves.utils
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
|
||||||
// compatibility extension for `removeIf` for API < N
|
// compatibility extension for `removeIf` for API <24
|
||||||
fun <E> MutableList<E>.compatRemoveIf(filter: (t: E) -> Boolean): Boolean {
|
fun <E> MutableList<E>.compatRemoveIf(filter: (t: E) -> Boolean): Boolean {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
this.removeIf(filter)
|
this.removeIf(filter)
|
||||||
|
|
|
@ -98,7 +98,7 @@ object PermissionManager {
|
||||||
segments.volumePath?.let { volumePath ->
|
segments.volumePath?.let { volumePath ->
|
||||||
val dirSet = dirsPerVolume[volumePath] ?: HashSet()
|
val dirSet = dirsPerVolume[volumePath] ?: HashSet()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
// request primary directory on volume from Android R
|
// request primary directory on volume from Android 11
|
||||||
val relativeDir = segments.relativeDir
|
val relativeDir = segments.relativeDir
|
||||||
if (relativeDir != null) {
|
if (relativeDir != null) {
|
||||||
val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() }
|
val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() }
|
||||||
|
@ -111,11 +111,11 @@ object PermissionManager {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// the requested path is the volume root itself
|
// the requested path is the volume root itself
|
||||||
// which cannot be granted, due to Android R restrictions
|
// which cannot be granted, due to Android 11 restrictions
|
||||||
dirSet.add("")
|
dirSet.add("")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// request volume root until Android Q
|
// request volume root until Android 10
|
||||||
dirSet.add("")
|
dirSet.add("")
|
||||||
}
|
}
|
||||||
dirsPerVolume[volumePath] = dirSet
|
dirsPerVolume[volumePath] = dirSet
|
||||||
|
@ -236,7 +236,7 @@ object PermissionManager {
|
||||||
return dirs
|
return dirs
|
||||||
}
|
}
|
||||||
|
|
||||||
// As of Android R, `MediaStore.getDocumentUri` fails if any of the persisted
|
// As of Android 11, `MediaStore.getDocumentUri` fails if any of the persisted
|
||||||
// URI permissions we hold points to a folder that no longer exists,
|
// URI permissions we hold points to a folder that no longer exists,
|
||||||
// so we should remove these obsolete URIs before proceeding.
|
// so we should remove these obsolete URIs before proceeding.
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
|
|
@ -468,7 +468,7 @@ object StorageUtils {
|
||||||
fun requireAccessPermission(context: Context, anyPath: String): Boolean {
|
fun requireAccessPermission(context: Context, anyPath: String): Boolean {
|
||||||
if (isAppFile(context, anyPath)) return false
|
if (isAppFile(context, anyPath)) return false
|
||||||
|
|
||||||
// on Android R, we should always require access permission, even on primary volume
|
// on Android 11, we should always require access permission, even on primary volume
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) return true
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) return true
|
||||||
|
|
||||||
val onPrimaryVolume = anyPath.startsWith(getPrimaryVolumePath(context))
|
val onPrimaryVolume = anyPath.startsWith(getPrimaryVolumePath(context))
|
||||||
|
@ -487,7 +487,7 @@ object StorageUtils {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isMediaStoreContentUri(uri)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isMediaStoreContentUri(uri)) {
|
||||||
val path = uri.path
|
val path = uri.path
|
||||||
path ?: return uri
|
path ?: return uri
|
||||||
// from Android R, accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
|
// from Android 11, accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
|
||||||
if (path.startsWith("/external/images/") || path.startsWith("/external/video/")) {
|
if (path.startsWith("/external/images/") || path.startsWith("/external/video/")) {
|
||||||
// "Caller must hold ACCESS_MEDIA_LOCATION permission to access original"
|
// "Caller must hold ACCESS_MEDIA_LOCATION permission to access original"
|
||||||
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
@ -499,7 +499,7 @@ object StorageUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// As of Glide v4.12.0, a special loader `QMediaStoreUriLoader` is automatically used
|
// As of Glide v4.12.0, a special loader `QMediaStoreUriLoader` is automatically used
|
||||||
// to work around a bug from Android Q where metadata redaction corrupts HEIC images.
|
// to work around a bug from Android 10 where metadata redaction corrupts HEIC images.
|
||||||
// This loader relies on `MediaStore.setRequireOriginal` but this yields a `SecurityException`
|
// This loader relies on `MediaStore.setRequireOriginal` but this yields a `SecurityException`
|
||||||
// for some non image/video content URIs (e.g. `downloads`, `file`)
|
// for some non image/video content URIs (e.g. `downloads`, `file`)
|
||||||
fun getGlideSafeUri(context: Context, uri: Uri, mimeType: String): Uri {
|
fun getGlideSafeUri(context: Context, uri: Uri, mimeType: String): Uri {
|
||||||
|
@ -594,7 +594,7 @@ object StorageUtils {
|
||||||
val effectiveUri = getOriginalUri(context, uri)
|
val effectiveUri = getOriginalUri(context, uri)
|
||||||
return try {
|
return try {
|
||||||
MediaMetadataRetriever().apply {
|
MediaMetadataRetriever().apply {
|
||||||
// on Android S preview, setting the data source works but yields an internal IOException
|
// on Android 12 preview, setting the data source works but yields an internal IOException
|
||||||
// (`Input file descriptor already original`), whether we provide the original URI or not
|
// (`Input file descriptor already original`), whether we provide the original URI or not
|
||||||
setDataSource(context, effectiveUri)
|
setDataSource(context, effectiveUri)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<bool name="translucentNavBar">false</bool>
|
|
||||||
</resources>
|
|
9
android/app/src/main/res/values-night/styles.xml
Normal file
9
android/app/src/main/res/values-night/styles.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
|
||||||
|
<!-- API28+, draws next to the notch in fullscreen -->
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="28">shortEdges</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
|
||||||
<item name="android:windowTranslucentNavigation">@bool/translucentNavBar</item> <!-- API19+, tinted background & request the SYSTEM_UI_FLAG_LAYOUT_STABLE and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flags -->
|
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <!-- API28+, draws next to the notch in fullscreen -->
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<bool name="translucentNavBar">true</bool>
|
|
||||||
</resources>
|
|
|
@ -1,6 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:windowTranslucentNavigation">@bool/translucentNavBar</item> <!-- API19+, tinted background & request the SYSTEM_UI_FLAG_LAYOUT_STABLE and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flags -->
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
|
||||||
|
<!-- API28+, draws next to the notch in fullscreen -->
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="28">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/widgets/aves_app.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class Themes {
|
class Themes {
|
||||||
|
@ -35,7 +35,7 @@ class Themes {
|
||||||
static const _lightSecondLayer = Color(0xFFF5F5F5); // aka `Colors.grey[100]`
|
static const _lightSecondLayer = Color(0xFFF5F5F5); // aka `Colors.grey[100]`
|
||||||
static const _lightThirdLayer = Color(0xFFEEEEEE); // aka `Colors.grey[200]`
|
static const _lightThirdLayer = Color(0xFFEEEEEE); // aka `Colors.grey[200]`
|
||||||
|
|
||||||
static ThemeData lightTheme(Color accentColor) => ThemeData(
|
static ThemeData lightTheme(Color accentColor, bool deviceInitialized) => ThemeData(
|
||||||
colorScheme: ColorScheme.light(
|
colorScheme: ColorScheme.light(
|
||||||
primary: accentColor,
|
primary: accentColor,
|
||||||
secondary: accentColor,
|
secondary: accentColor,
|
||||||
|
@ -58,7 +58,7 @@ class Themes {
|
||||||
foregroundColor: _lightActionIconColor,
|
foregroundColor: _lightActionIconColor,
|
||||||
// `titleTextStyle.color` is used by text
|
// `titleTextStyle.color` is used by text
|
||||||
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _lightTitleColor),
|
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _lightTitleColor),
|
||||||
systemOverlayStyle: SystemUiOverlayStyle.dark,
|
systemOverlayStyle: deviceInitialized ? AvesApp.systemUIStyleForBrightness(Brightness.light, _lightFirstLayer) : null,
|
||||||
),
|
),
|
||||||
listTileTheme: const ListTileThemeData(
|
listTileTheme: const ListTileThemeData(
|
||||||
iconColor: _lightActionIconColor,
|
iconColor: _lightActionIconColor,
|
||||||
|
@ -87,7 +87,7 @@ class Themes {
|
||||||
static const _darkSecondLayer = Color(0xFF363636);
|
static const _darkSecondLayer = Color(0xFF363636);
|
||||||
static const _darkThirdLayer = Color(0xFF424242); // aka `Colors.grey[800]`
|
static const _darkThirdLayer = Color(0xFF424242); // aka `Colors.grey[800]`
|
||||||
|
|
||||||
static ThemeData darkTheme(Color accentColor) => ThemeData(
|
static ThemeData darkTheme(Color accentColor, bool deviceInitialized) => ThemeData(
|
||||||
colorScheme: ColorScheme.dark(
|
colorScheme: ColorScheme.dark(
|
||||||
primary: accentColor,
|
primary: accentColor,
|
||||||
secondary: accentColor,
|
secondary: accentColor,
|
||||||
|
@ -112,7 +112,7 @@ class Themes {
|
||||||
foregroundColor: _darkTitleColor,
|
foregroundColor: _darkTitleColor,
|
||||||
// `titleTextStyle.color` is used by text
|
// `titleTextStyle.color` is used by text
|
||||||
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _darkTitleColor),
|
titleTextStyle: _appBarTitleTextStyle.copyWith(color: _darkTitleColor),
|
||||||
systemOverlayStyle: SystemUiOverlayStyle.light,
|
systemOverlayStyle: deviceInitialized ? AvesApp.systemUIStyleForBrightness(Brightness.dark, _darkFirstLayer) : null,
|
||||||
),
|
),
|
||||||
popupMenuTheme: const PopupMenuThemeData(
|
popupMenuTheme: const PopupMenuThemeData(
|
||||||
color: _darkSecondLayer,
|
color: _darkSecondLayer,
|
||||||
|
@ -138,8 +138,8 @@ class Themes {
|
||||||
static const _blackSecondLayer = Color(0xFF212121); // aka `Colors.grey[900]`
|
static const _blackSecondLayer = Color(0xFF212121); // aka `Colors.grey[900]`
|
||||||
static const _blackThirdLayer = Color(0xFF303030); // aka `Colors.grey[850]`
|
static const _blackThirdLayer = Color(0xFF303030); // aka `Colors.grey[850]`
|
||||||
|
|
||||||
static ThemeData blackTheme(Color accentColor) {
|
static ThemeData blackTheme(Color accentColor, bool deviceInitialized) {
|
||||||
final baseTheme = darkTheme(accentColor);
|
final baseTheme = darkTheme(accentColor, deviceInitialized);
|
||||||
return baseTheme.copyWith(
|
return baseTheme.copyWith(
|
||||||
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
|
// `canvasColor` is used by `Drawer`, `DropdownButton` and `ExpansionTileCard`
|
||||||
canvasColor: _blackSecondLayer,
|
canvasColor: _blackSecondLayer,
|
||||||
|
|
|
@ -60,15 +60,44 @@ class AvesApp extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
State<AvesApp> createState() => _AvesAppState();
|
State<AvesApp> createState() => _AvesAppState();
|
||||||
|
|
||||||
static void showSystemUI() {
|
static void setSystemUIStyle(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final style = systemUIStyleForBrightness(theme.brightness, theme.scaffoldBackgroundColor);
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SystemUiOverlayStyle systemUIStyleForBrightness(Brightness themeBrightness, Color scaffoldBackgroundColor) {
|
||||||
|
final barBrightness = themeBrightness == Brightness.light ? Brightness.dark : Brightness.light;
|
||||||
|
const statusBarColor = Colors.transparent;
|
||||||
|
// as of Flutter v3.3.0-0.2.pre, setting `SystemUiOverlayStyle` (whether manually or automatically because of `AppBar`)
|
||||||
|
// prevents the canvas from drawing behind the nav bar on Android <10 (API <29),
|
||||||
|
// so the nav bar is opaque, even when requesting `SystemUiMode.edgeToEdge` from Flutter
|
||||||
|
// or setting `android:windowTranslucentNavigation` in Android themes.
|
||||||
|
final navBarColor = device.supportEdgeToEdgeUIMode ? Colors.transparent : scaffoldBackgroundColor;
|
||||||
|
return SystemUiOverlayStyle(
|
||||||
|
systemNavigationBarColor: navBarColor,
|
||||||
|
systemNavigationBarDividerColor: navBarColor,
|
||||||
|
systemNavigationBarIconBrightness: barBrightness,
|
||||||
|
// shows background scrim when using navigation buttons, but not when using gesture navigation
|
||||||
|
systemNavigationBarContrastEnforced: true,
|
||||||
|
statusBarColor: statusBarColor,
|
||||||
|
statusBarBrightness: barBrightness,
|
||||||
|
statusBarIconBrightness: barBrightness,
|
||||||
|
systemStatusBarContrastEnforced: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> showSystemUI() async {
|
||||||
if (device.supportEdgeToEdgeUIMode) {
|
if (device.supportEdgeToEdgeUIMode) {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
} else {
|
} else {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hideSystemUI() => SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
static Future<void> hideSystemUI() async {
|
||||||
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
|
@ -121,6 +150,9 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
future: _appSetup,
|
future: _appSetup,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final initialized = !snapshot.hasError && snapshot.connectionState == ConnectionState.done;
|
final initialized = !snapshot.hasError && snapshot.connectionState == ConnectionState.done;
|
||||||
|
if (initialized) {
|
||||||
|
AvesApp.showSystemUI();
|
||||||
|
}
|
||||||
final home = initialized
|
final home = initialized
|
||||||
? getFirstPage()
|
? getFirstPage()
|
||||||
: Scaffold(
|
: Scaffold(
|
||||||
|
@ -169,17 +201,22 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
navigatorKey: AvesApp.navigatorKey,
|
navigatorKey: AvesApp.navigatorKey,
|
||||||
home: home,
|
home: home,
|
||||||
navigatorObservers: _navigatorObservers,
|
navigatorObservers: _navigatorObservers,
|
||||||
builder: (context, child) => AvesColorsProvider(
|
builder: (context, child) {
|
||||||
child: Theme(
|
if (initialized) {
|
||||||
data: Theme.of(context).copyWith(
|
WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(context));
|
||||||
pageTransitionsTheme: pageTransitionsTheme,
|
}
|
||||||
|
return AvesColorsProvider(
|
||||||
|
child: Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
pageTransitionsTheme: pageTransitionsTheme,
|
||||||
|
),
|
||||||
|
child: child!,
|
||||||
),
|
),
|
||||||
child: child!,
|
);
|
||||||
),
|
},
|
||||||
),
|
|
||||||
onGenerateTitle: (context) => context.l10n.appName,
|
onGenerateTitle: (context) => context.l10n.appName,
|
||||||
theme: Themes.lightTheme(lightAccent),
|
theme: Themes.lightTheme(lightAccent, initialized),
|
||||||
darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent) : Themes.darkTheme(darkAccent),
|
darkTheme: themeBrightness == AvesThemeBrightness.black ? Themes.blackTheme(darkAccent, initialized) : Themes.darkTheme(darkAccent, initialized),
|
||||||
themeMode: themeBrightness.appThemeMode,
|
themeMode: themeBrightness.appThemeMode,
|
||||||
locale: settingsLocale,
|
locale: settingsLocale,
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:provider/provider.dart';
|
||||||
// This widget should be added on top of Scaffolds with:
|
// This widget should be added on top of Scaffolds with:
|
||||||
// - `resizeToAvoidBottomInset` set to false,
|
// - `resizeToAvoidBottomInset` set to false,
|
||||||
// - a vertically scrollable body.
|
// - a vertically scrollable body.
|
||||||
// It will prevent the body from scrolling when a user swipe from bottom to use Android Q style navigation gestures.
|
// It will prevent the body from scrolling when a user swipe from bottom to use Android 10 style navigation gestures.
|
||||||
class BottomGestureAreaProtector extends StatelessWidget {
|
class BottomGestureAreaProtector extends StatelessWidget {
|
||||||
const BottomGestureAreaProtector({super.key});
|
const BottomGestureAreaProtector({super.key});
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ class TopGestureAreaProtector extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// It will prevent the body from scrolling when a user swipe from edges to use Android Q style navigation gestures.
|
// It will prevent the body from scrolling when a user swipe from edges to use Android 10 style navigation gestures.
|
||||||
class SideGestureAreaProtector extends StatelessWidget {
|
class SideGestureAreaProtector extends StatelessWidget {
|
||||||
const SideGestureAreaProtector({super.key});
|
const SideGestureAreaProtector({super.key});
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
// hide in some countries apps that force quit on permission denial
|
// hide in some countries apps that force quit on permission denial
|
||||||
await [
|
await [
|
||||||
Permission.storage,
|
Permission.storage,
|
||||||
// to access media with unredacted metadata with scoped storage (Android 10+)
|
// to access media with unredacted metadata with scoped storage (Android >=10)
|
||||||
Permission.accessMediaLocation,
|
Permission.accessMediaLocation,
|
||||||
].request();
|
].request();
|
||||||
}
|
}
|
||||||
|
|
|
@ -652,19 +652,20 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLeave() {
|
Future<void> _onLeave() async {
|
||||||
if (!settings.viewerUseCutout) {
|
if (!settings.viewerUseCutout) {
|
||||||
windowService.setCutoutMode(true);
|
await windowService.setCutoutMode(true);
|
||||||
}
|
}
|
||||||
if (settings.viewerMaxBrightness) {
|
if (settings.viewerMaxBrightness) {
|
||||||
ScreenBrightness().resetScreenBrightness();
|
await ScreenBrightness().resetScreenBrightness();
|
||||||
}
|
}
|
||||||
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
||||||
windowService.keepScreenOn(false);
|
await windowService.keepScreenOn(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
windowService.requestOrientation();
|
AvesApp.setSystemUIStyle(context);
|
||||||
|
await windowService.requestOrientation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// overlay
|
// overlay
|
||||||
|
@ -679,7 +680,8 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
|
|
||||||
Future<void> _onOverlayVisibleChange({bool animate = true}) async {
|
Future<void> _onOverlayVisibleChange({bool animate = true}) async {
|
||||||
if (_overlayVisible.value) {
|
if (_overlayVisible.value) {
|
||||||
AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
|
AvesApp.setSystemUIStyle(context);
|
||||||
if (animate) {
|
if (animate) {
|
||||||
await _overlayAnimationController.forward();
|
await _overlayAnimationController.forward();
|
||||||
} else {
|
} else {
|
||||||
|
@ -692,7 +694,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
_frozenViewInsets = mediaQuery.viewInsets;
|
_frozenViewInsets = mediaQuery.viewInsets;
|
||||||
_frozenViewPadding = mediaQuery.viewPadding;
|
_frozenViewPadding = mediaQuery.viewPadding;
|
||||||
});
|
});
|
||||||
AvesApp.hideSystemUI();
|
await AvesApp.hideSystemUI();
|
||||||
if (animate) {
|
if (animate) {
|
||||||
await _overlayAnimationController.reverse();
|
await _overlayAnimationController.reverse();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -159,8 +159,9 @@ class _PanoramaPageState extends State<PanoramaPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLeave() {
|
Future<void> _onLeave() async {
|
||||||
AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
|
AvesApp.setSystemUIStyle(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// system UI
|
// system UI
|
||||||
|
@ -176,9 +177,10 @@ class _PanoramaPageState extends State<PanoramaPage> {
|
||||||
|
|
||||||
Future<void> _onOverlayVisibleChange() async {
|
Future<void> _onOverlayVisibleChange() async {
|
||||||
if (_overlayVisible.value) {
|
if (_overlayVisible.value) {
|
||||||
AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
|
AvesApp.setSystemUIStyle(context);
|
||||||
} else {
|
} else {
|
||||||
AvesApp.hideSystemUI();
|
await AvesApp.hideSystemUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,7 +234,8 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
|
||||||
|
|
||||||
Future<void> _onOverlayVisibleChange({bool animate = true}) async {
|
Future<void> _onOverlayVisibleChange({bool animate = true}) async {
|
||||||
if (_overlayVisible.value) {
|
if (_overlayVisible.value) {
|
||||||
AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
|
AvesApp.setSystemUIStyle(context);
|
||||||
if (animate) {
|
if (animate) {
|
||||||
await _overlayAnimationController.forward();
|
await _overlayAnimationController.forward();
|
||||||
} else {
|
} else {
|
||||||
|
@ -246,7 +247,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
|
||||||
_frozenViewInsets = mediaQuery.viewInsets;
|
_frozenViewInsets = mediaQuery.viewInsets;
|
||||||
_frozenViewPadding = mediaQuery.viewPadding;
|
_frozenViewPadding = mediaQuery.viewPadding;
|
||||||
});
|
});
|
||||||
AvesApp.hideSystemUI();
|
await AvesApp.hideSystemUI();
|
||||||
if (animate) {
|
if (animate) {
|
||||||
await _overlayAnimationController.reverse();
|
await _overlayAnimationController.reverse();
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue