Merge branch 'develop'
This commit is contained in:
commit
ef2953dd2a
94 changed files with 2075 additions and 660 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <a id="unreleased"></a>[Unreleased]
|
||||||
|
|
||||||
|
## <a id="v1.7.10"></a>[v1.7.10] - 2023-01-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Video: optional gestures to adjust brightness/volume
|
||||||
|
- TV: improved support for Search, About, Privacy Policy
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Viewer: do not keep max brightness when viewing info
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- crash when media button events are triggered with no active media session
|
||||||
|
|
||||||
## <a id="v1.7.9"></a>[v1.7.9] - 2023-01-15
|
## <a id="v1.7.9"></a>[v1.7.9] - 2023-01-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -213,6 +213,15 @@ This change eventually prevents building the app with Flutter v3.3.3.
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".MediaPlaybackService"
|
||||||
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="mediaPlayback">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".AnalysisService"
|
android:name=".AnalysisService"
|
||||||
android:description="@string/analysis_service_description"
|
android:description="@string/analysis_service_description"
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package deckers.thibault.aves
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.media.MediaBrowserCompat
|
||||||
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
|
|
||||||
|
// dummy service to handle media button events
|
||||||
|
// when there is no active media sessions
|
||||||
|
class MediaPlaybackService : MediaBrowserServiceCompat() {
|
||||||
|
override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||||
|
val children = mutableListOf<MediaBrowserCompat.MediaItem>()
|
||||||
|
result.sendResult(children)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import com.drew.metadata.xmp.XmpDirectory
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
||||||
import deckers.thibault.aves.metadata.*
|
import deckers.thibault.aves.metadata.*
|
||||||
import deckers.thibault.aves.metadata.XMP.doesPropExist
|
import deckers.thibault.aves.metadata.XMP.doesPropPathExist
|
||||||
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
||||||
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
|
@ -104,7 +104,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
||||||
try {
|
try {
|
||||||
container = xmpDirs.firstNotNullOfOrNull {
|
container = xmpDirs.firstNotNullOfOrNull {
|
||||||
val xmpMeta = it.xmpMeta
|
val xmpMeta = it.xmpMeta
|
||||||
if (xmpMeta.doesPropExist(XMP.GDEVICE_DIRECTORY_PROP_NAME)) {
|
if (xmpMeta.doesPropPathExist(listOf(XMP.GDEVICE_CONTAINER_PROP_NAME, XMP.GDEVICE_CONTAINER_DIRECTORY_PROP_NAME))) {
|
||||||
GoogleDeviceContainer().apply { findItems(xmpMeta) }
|
GoogleDeviceContainer().apply { findItems(xmpMeta) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
|
@ -3,7 +3,7 @@ package deckers.thibault.aves.metadata
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.adobe.internal.xmp.XMPMeta
|
import com.adobe.internal.xmp.XMPMeta
|
||||||
import deckers.thibault.aves.metadata.XMP.countPropArrayItems
|
import deckers.thibault.aves.metadata.XMP.countPropPathArrayItems
|
||||||
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
||||||
import deckers.thibault.aves.utils.indexOfBytes
|
import deckers.thibault.aves.utils.indexOfBytes
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
@ -15,11 +15,12 @@ class GoogleDeviceContainer {
|
||||||
private val offsets: MutableList<Int> = ArrayList()
|
private val offsets: MutableList<Int> = ArrayList()
|
||||||
|
|
||||||
fun findItems(xmpMeta: XMPMeta) {
|
fun findItems(xmpMeta: XMPMeta) {
|
||||||
val count = xmpMeta.countPropArrayItems(XMP.GDEVICE_DIRECTORY_PROP_NAME)
|
val containerDirectoryPath = listOf(XMP.GDEVICE_CONTAINER_PROP_NAME, XMP.GDEVICE_CONTAINER_DIRECTORY_PROP_NAME)
|
||||||
|
val count = xmpMeta.countPropPathArrayItems(containerDirectoryPath)
|
||||||
for (i in 1 until count + 1) {
|
for (i in 1 until count + 1) {
|
||||||
val mimeType = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME))?.value
|
val mimeType = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME))?.value
|
||||||
val length = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME))?.value?.toLongOrNull()
|
val length = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME))?.value?.toLongOrNull()
|
||||||
val dataUri = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME))?.value
|
val dataUri = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME))?.value
|
||||||
if (mimeType != null && length != null && dataUri != null) {
|
if (mimeType != null && length != null && dataUri != null) {
|
||||||
items.add(
|
items.add(
|
||||||
GoogleDeviceContainerItem(
|
GoogleDeviceContainerItem(
|
||||||
|
|
|
@ -49,6 +49,7 @@ object XMP {
|
||||||
private const val GCONTAINER_ITEM_NS_URI = "http://ns.google.com/photos/1.0/container/item/"
|
private const val GCONTAINER_ITEM_NS_URI = "http://ns.google.com/photos/1.0/container/item/"
|
||||||
private const val GDEPTH_NS_URI = "http://ns.google.com/photos/1.0/depthmap/"
|
private const val GDEPTH_NS_URI = "http://ns.google.com/photos/1.0/depthmap/"
|
||||||
private const val GDEVICE_NS_URI = "http://ns.google.com/photos/dd/1.0/device/"
|
private const val GDEVICE_NS_URI = "http://ns.google.com/photos/dd/1.0/device/"
|
||||||
|
private const val GDEVICE_CONTAINER_NS_URI = "http://ns.google.com/photos/dd/1.0/container/"
|
||||||
private const val GDEVICE_ITEM_NS_URI = "http://ns.google.com/photos/dd/1.0/item/"
|
private const val GDEVICE_ITEM_NS_URI = "http://ns.google.com/photos/dd/1.0/item/"
|
||||||
private const val GIMAGE_NS_URI = "http://ns.google.com/photos/1.0/image/"
|
private const val GIMAGE_NS_URI = "http://ns.google.com/photos/1.0/image/"
|
||||||
private const val GPANO_NS_URI = "http://ns.google.com/photos/1.0/panorama/"
|
private const val GPANO_NS_URI = "http://ns.google.com/photos/1.0/panorama/"
|
||||||
|
@ -70,6 +71,7 @@ object XMP {
|
||||||
// cf https://developers.google.com/vr/reference/cardboard-camera-vr-photo-format
|
// cf https://developers.google.com/vr/reference/cardboard-camera-vr-photo-format
|
||||||
private val knownDataProps = listOf(
|
private val knownDataProps = listOf(
|
||||||
XMPPropName(GAUDIO_NS_URI, "Data"),
|
XMPPropName(GAUDIO_NS_URI, "Data"),
|
||||||
|
XMPPropName(GCAMERA_NS_URI, "RelitInputImageData"),
|
||||||
XMPPropName(GIMAGE_NS_URI, "Data"),
|
XMPPropName(GIMAGE_NS_URI, "Data"),
|
||||||
XMPPropName(GDEPTH_NS_URI, "Data"),
|
XMPPropName(GDEPTH_NS_URI, "Data"),
|
||||||
XMPPropName(GDEPTH_NS_URI, "Confidence"),
|
XMPPropName(GDEPTH_NS_URI, "Confidence"),
|
||||||
|
@ -79,7 +81,8 @@ object XMP {
|
||||||
|
|
||||||
// google portrait
|
// google portrait
|
||||||
|
|
||||||
val GDEVICE_DIRECTORY_PROP_NAME = XMPPropName(GDEVICE_NS_URI, "Container/Container:Directory")
|
val GDEVICE_CONTAINER_PROP_NAME = XMPPropName(GDEVICE_NS_URI, "Container")
|
||||||
|
val GDEVICE_CONTAINER_DIRECTORY_PROP_NAME = XMPPropName(GDEVICE_CONTAINER_NS_URI, "Directory")
|
||||||
val GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "DataURI")
|
val GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "DataURI")
|
||||||
val GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Length")
|
val GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Length")
|
||||||
val GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Mime")
|
val GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Mime")
|
||||||
|
@ -254,10 +257,18 @@ object XMP {
|
||||||
return doesPropertyExist(prop.nsUri, prop.toString())
|
return doesPropertyExist(prop.nsUri, prop.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun XMPMeta.doesPropPathExist(props: List<XMPPropName>): Boolean {
|
||||||
|
return doesPropertyExist(props.first().nsUri, props.joinToString("/"))
|
||||||
|
}
|
||||||
|
|
||||||
fun XMPMeta.countPropArrayItems(prop: XMPPropName): Int {
|
fun XMPMeta.countPropArrayItems(prop: XMPPropName): Int {
|
||||||
return countArrayItems(prop.nsUri, prop.toString())
|
return countArrayItems(prop.nsUri, prop.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun XMPMeta.countPropPathArrayItems(props: List<XMPPropName>): Int {
|
||||||
|
return countArrayItems(props.first().nsUri, props.joinToString("/"))
|
||||||
|
}
|
||||||
|
|
||||||
fun XMPMeta.getPropArrayItemValues(prop: XMPPropName): List<String> {
|
fun XMPMeta.getPropArrayItemValues(prop: XMPPropName): List<String> {
|
||||||
val schema = prop.nsUri
|
val schema = prop.nsUri
|
||||||
val propName = prop.toString()
|
val propName = prop.toString()
|
||||||
|
|
12
android/app/src/main/res/values-iw/strings.xml
Normal file
12
android/app/src/main/res/values-iw/strings.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">אייבז</string>
|
||||||
|
<string name="app_widget_label">מסגרת תמונה</string>
|
||||||
|
<string name="wallpaper">טפט</string>
|
||||||
|
<string name="search_shortcut_short_label">חיפוש</string>
|
||||||
|
<string name="videos_shortcut_short_label">סרטים</string>
|
||||||
|
<string name="analysis_channel_name">סריקת מדיה</string>
|
||||||
|
<string name="analysis_service_description">סרוק תמונות וסרטים</string>
|
||||||
|
<string name="analysis_notification_default_title">סורק מדיה</string>
|
||||||
|
<string name="analysis_notification_action_stop">הפסק</string>
|
||||||
|
</resources>
|
|
@ -2,10 +2,10 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_widget_label">Ramka Zdjęcia</string>
|
<string name="app_widget_label">Ramka Zdjęcia</string>
|
||||||
<string name="search_shortcut_short_label">Szukaj</string>
|
<string name="search_shortcut_short_label">Szukaj</string>
|
||||||
<string name="videos_shortcut_short_label">Filmy</string>
|
<string name="videos_shortcut_short_label">Wideo</string>
|
||||||
<string name="analysis_channel_name">Skan mediów</string>
|
<string name="analysis_channel_name">Przeskanuj multimedia</string>
|
||||||
<string name="analysis_service_description">Skan obrazów & filmów</string>
|
<string name="analysis_service_description">Przeskanuj obrazy oraz wideo</string>
|
||||||
<string name="analysis_notification_default_title">Skanowanie mediów</string>
|
<string name="analysis_notification_default_title">Skanowanie multimediów</string>
|
||||||
<string name="analysis_notification_action_stop">Zatrzymaj</string>
|
<string name="analysis_notification_action_stop">Zatrzymaj</string>
|
||||||
<string name="app_name">Aves</string>
|
<string name="app_name">Aves</string>
|
||||||
<string name="wallpaper">Tapeta</string>
|
<string name="wallpaper">Tapeta</string>
|
||||||
|
|
5
fastlane/metadata/android/en-US/changelogs/90.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/90.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
In v1.7.10:
|
||||||
|
- Android TV support (cont'd)
|
||||||
|
- interact with videos via media session controls
|
||||||
|
- enjoy the app in Czech & Polish
|
||||||
|
Full changelog available on GitHub
|
5
fastlane/metadata/android/en-US/changelogs/9001.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/9001.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
In v1.7.10:
|
||||||
|
- Android TV support (cont'd)
|
||||||
|
- interact with videos via media session controls
|
||||||
|
- enjoy the app in Czech & Polish
|
||||||
|
Full changelog available on GitHub
|
5
fastlane/metadata/android/he/full_description.txt
Normal file
5
fastlane/metadata/android/he/full_description.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<i>Aves</i> can handle all sorts of images and videos, including your typical JPEGs and MP4s, but also more exotic things like <b>multi-page TIFFs, SVGs, old AVIs and more</b>! It scans your media collection to identify <b>motion photos</b>, <b>panoramas</b> (aka photo spheres), <b>360° videos</b>, as well as <b>GeoTIFF</b> files.
|
||||||
|
|
||||||
|
<b>Navigation and search</b> is an important part of <i>Aves</i>. The goal is for users to easily flow from albums to photos to tags to maps, etc.
|
||||||
|
|
||||||
|
<i>Aves</i> integrates with Android (from KitKat to Android 13, including Android TV) with features such as <b>widgets</b>, <b>app shortcuts</b>, <b>screen saver</b> and <b>global search</b> handling. It also works as a <b>media viewer and picker</b>.
|
1
fastlane/metadata/android/he/short_description.txt
Normal file
1
fastlane/metadata/android/he/short_description.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Gallery and metadata explorer
|
|
@ -1,4 +1,4 @@
|
||||||
<i>Aves</i> obsługuje wszelkiego rodzaju obrazy i filmy, w tym typowe pliki JPEG i MP4 ale także bardziej egzotyczne formaty takie jak <b>wielostronnicowe pliki TIFF, SVG, stare pliki AVI i wiele więcej</b>! Skanuje twoją kolekcję multimediów aby zidentyfikować <b>ruchome zdjęcia</b>, <b>panoramy</b> (inaczej zdjęcia sferyczne), <b>filmy 360°</b>, a także pliki <b>GeoTIFF</b>.
|
<i>Aves</i> obsługuje wszelkiego rodzaju obrazy i filmy, w tym typowe pliki JPEG i MP4, ale także bardziej egzotyczne formaty, takie jak <b>wielostronicowe pliki TIFF, SVG, stare pliki AVI i wiele innych</b>! Skanuje twoją kolekcję multimediów, aby zidentyfikować <b>ruchome zdjęcia</b>, <b>zdjęcia panoramiczne</b> (inaczej zdjęcia sferyczne), <b>wideo 360°</b>, a także pliki <b>GeoTIFF</b>.
|
||||||
|
|
||||||
<b>Nawigacja i wyszukiwanie</b> jest ważną częścią <i>Aves</i>. Celem jest aby użytkownicy mogli łatwo przechodzić od albumów do zdjęć, tagów, map itd.
|
<b>Nawigacja i wyszukiwanie</b> jest ważną częścią <i>Aves</i>. Celem jest aby użytkownicy mogli łatwo przechodzić od albumów do zdjęć, tagów, map itd.
|
||||||
|
|
||||||
|
|
|
@ -781,6 +781,7 @@
|
||||||
"settingsVideoButtonsTile": "Buttons",
|
"settingsVideoButtonsTile": "Buttons",
|
||||||
"settingsVideoGestureDoubleTapTogglePlay": "Double tap to play/pause",
|
"settingsVideoGestureDoubleTapTogglePlay": "Double tap to play/pause",
|
||||||
"settingsVideoGestureSideDoubleTapSeek": "Double tap on screen edges to seek backward/forward",
|
"settingsVideoGestureSideDoubleTapSeek": "Double tap on screen edges to seek backward/forward",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "Swipe up or down to adjust brightness/volume",
|
||||||
|
|
||||||
"settingsPrivacySectionTitle": "Privacy",
|
"settingsPrivacySectionTitle": "Privacy",
|
||||||
"settingsAllowInstalledAppAccess": "Allow access to app inventory",
|
"settingsAllowInstalledAppAccess": "Allow access to app inventory",
|
||||||
|
|
|
@ -1206,5 +1206,9 @@
|
||||||
"filterLocatedLabel": "Localizado",
|
"filterLocatedLabel": "Localizado",
|
||||||
"@filterLocatedLabel": {},
|
"@filterLocatedLabel": {},
|
||||||
"filterTaggedLabel": "Etiquetado",
|
"filterTaggedLabel": "Etiquetado",
|
||||||
"@filterTaggedLabel": {}
|
"@filterTaggedLabel": {},
|
||||||
|
"tooManyItemsErrorDialogMessage": "Vuelva a intentarlo con menos elementos.",
|
||||||
|
"@tooManyItemsErrorDialogMessage": {},
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "Deslice hacia arriba o hacia abajo para ajustar el brillo o el volumen",
|
||||||
|
"@settingsVideoGestureVerticalDragBrightnessVolume": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1208,5 +1208,7 @@
|
||||||
"filterLocatedLabel": "Localisé",
|
"filterLocatedLabel": "Localisé",
|
||||||
"@filterLocatedLabel": {},
|
"@filterLocatedLabel": {},
|
||||||
"tooManyItemsErrorDialogMessage": "Réessayez avec moins d’éléments.",
|
"tooManyItemsErrorDialogMessage": "Réessayez avec moins d’éléments.",
|
||||||
"@tooManyItemsErrorDialogMessage": {}
|
"@tooManyItemsErrorDialogMessage": {},
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "Balayer verticalement pour ajuster la luminosité et le volume",
|
||||||
|
"@settingsVideoGestureVerticalDragBrightnessVolume": {}
|
||||||
}
|
}
|
||||||
|
|
10
lib/l10n/app_he.arb
Normal file
10
lib/l10n/app_he.arb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"appName": "אייבז",
|
||||||
|
"@appName": {},
|
||||||
|
"welcomeMessage": "ברוך הבא לאייבז",
|
||||||
|
"@welcomeMessage": {},
|
||||||
|
"welcomeOptional": "אופציונלי",
|
||||||
|
"@welcomeOptional": {},
|
||||||
|
"welcomeTermsToggle": "אני מסכימ/ה לתנאים",
|
||||||
|
"@welcomeTermsToggle": {}
|
||||||
|
}
|
|
@ -1208,5 +1208,7 @@
|
||||||
"filterTaggedLabel": "Dilabel",
|
"filterTaggedLabel": "Dilabel",
|
||||||
"@filterTaggedLabel": {},
|
"@filterTaggedLabel": {},
|
||||||
"tooManyItemsErrorDialogMessage": "Coba lagi dengan item yang lebih sedikit.",
|
"tooManyItemsErrorDialogMessage": "Coba lagi dengan item yang lebih sedikit.",
|
||||||
"@tooManyItemsErrorDialogMessage": {}
|
"@tooManyItemsErrorDialogMessage": {},
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "Usap ke atas atau bawah untuk mengatur kecerahan/volume",
|
||||||
|
"@settingsVideoGestureVerticalDragBrightnessVolume": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1208,5 +1208,7 @@
|
||||||
"filterLocatedLabel": "위치 있음",
|
"filterLocatedLabel": "위치 있음",
|
||||||
"@filterLocatedLabel": {},
|
"@filterLocatedLabel": {},
|
||||||
"tooManyItemsErrorDialogMessage": "항목 수를 줄이고 다시 시도하세요.",
|
"tooManyItemsErrorDialogMessage": "항목 수를 줄이고 다시 시도하세요.",
|
||||||
"@tooManyItemsErrorDialogMessage": {}
|
"@tooManyItemsErrorDialogMessage": {},
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "위아래로 스와이프해서 밝기/음량을 조절하기",
|
||||||
|
"@settingsVideoGestureVerticalDragBrightnessVolume": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1265,7 +1265,7 @@
|
||||||
"count": {}
|
"count": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Slett disse albumene og deres element?} other{Slett disse albumene og deres {count} elementer?}}",
|
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Slett disse albumene og elementet i demt?} other{Slett disse albumene og de {count} elementene i dem?}}",
|
||||||
"@deleteMultiAlbumConfirmationDialogMessage": {
|
"@deleteMultiAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -1338,5 +1338,33 @@
|
||||||
"albumTierSpecial": "Ofte åpnet",
|
"albumTierSpecial": "Ofte åpnet",
|
||||||
"@albumTierSpecial": {},
|
"@albumTierSpecial": {},
|
||||||
"editEntryLocationDialogTitle": "Plassering",
|
"editEntryLocationDialogTitle": "Plassering",
|
||||||
"@editEntryLocationDialogTitle": {}
|
"@editEntryLocationDialogTitle": {},
|
||||||
|
"filterLocatedLabel": "Posisjonert",
|
||||||
|
"@filterLocatedLabel": {},
|
||||||
|
"filterTaggedLabel": "Etikettmerket",
|
||||||
|
"@filterTaggedLabel": {},
|
||||||
|
"settingsDisplayUseTvInterface": "Android TV-grensesnitt",
|
||||||
|
"@settingsDisplayUseTvInterface": {},
|
||||||
|
"settingsAccessibilityShowPinchGestureAlternatives": "Vis multi-trykkhåndvendingsalternativer",
|
||||||
|
"@settingsAccessibilityShowPinchGestureAlternatives": {},
|
||||||
|
"columnCount": "{count, plural, =1{1 kolonne} other{{count} kolonner}}",
|
||||||
|
"@columnCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keepScreenOnVideoPlayback": "Under videoavspilling",
|
||||||
|
"@keepScreenOnVideoPlayback": {},
|
||||||
|
"entryActionShareImageOnly": "Del kun bilde",
|
||||||
|
"@entryActionShareImageOnly": {},
|
||||||
|
"entryActionShareVideoOnly": "Del kun video",
|
||||||
|
"@entryActionShareVideoOnly": {},
|
||||||
|
"entryInfoActionRemoveLocation": "Fjern posisjon",
|
||||||
|
"@entryInfoActionRemoveLocation": {},
|
||||||
|
"settingsViewerShowDescription": "Vis beskrivelse",
|
||||||
|
"@settingsViewerShowDescription": {},
|
||||||
|
"settingsModificationWarningDialogMessage": "Andre innstillinger vil bli endret.",
|
||||||
|
"@settingsModificationWarningDialogMessage": {},
|
||||||
|
"tooManyItemsErrorDialogMessage": "Prøv igjen med færre elementer.",
|
||||||
|
"@tooManyItemsErrorDialogMessage": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"@resetTooltip": {},
|
"@resetTooltip": {},
|
||||||
"pickTooltip": "Wybierz",
|
"pickTooltip": "Wybierz",
|
||||||
"@pickTooltip": {},
|
"@pickTooltip": {},
|
||||||
"doubleBackExitMessage": "Tapnij ponownie „wstecz” aby wyjść.",
|
"doubleBackExitMessage": "Dotknij ponownie „wstecz”, aby wyjść.",
|
||||||
"@doubleBackExitMessage": {},
|
"@doubleBackExitMessage": {},
|
||||||
"saveTooltip": "Zapisz",
|
"saveTooltip": "Zapisz",
|
||||||
"@saveTooltip": {},
|
"@saveTooltip": {},
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
"@appName": {},
|
"@appName": {},
|
||||||
"welcomeMessage": "Witaj w Aves",
|
"welcomeMessage": "Witaj w Aves",
|
||||||
"@welcomeMessage": {},
|
"@welcomeMessage": {},
|
||||||
"welcomeOptional": "Opcjonalny",
|
"welcomeOptional": "Opcjonalnie",
|
||||||
"@welcomeOptional": {},
|
"@welcomeOptional": {},
|
||||||
"welcomeTermsToggle": "Akceptuję warunki i zasady",
|
"welcomeTermsToggle": "Akceptuję warunki i zasady",
|
||||||
"@welcomeTermsToggle": {},
|
"@welcomeTermsToggle": {},
|
||||||
|
@ -99,9 +99,9 @@
|
||||||
"@entryInfoActionEditTitleDescription": {},
|
"@entryInfoActionEditTitleDescription": {},
|
||||||
"entryInfoActionEditRating": "Edytuj ocenę",
|
"entryInfoActionEditRating": "Edytuj ocenę",
|
||||||
"@entryInfoActionEditRating": {},
|
"@entryInfoActionEditRating": {},
|
||||||
"entryInfoActionEditTags": "Edytuj tagi",
|
"entryInfoActionEditTags": "Edytuj znaczniki",
|
||||||
"@entryInfoActionEditTags": {},
|
"@entryInfoActionEditTags": {},
|
||||||
"entryInfoActionRemoveMetadata": "Usuń metadatę",
|
"entryInfoActionRemoveMetadata": "Usuń metadane",
|
||||||
"@entryInfoActionRemoveMetadata": {},
|
"@entryInfoActionRemoveMetadata": {},
|
||||||
"filterBinLabel": "Kosz",
|
"filterBinLabel": "Kosz",
|
||||||
"@filterBinLabel": {},
|
"@filterBinLabel": {},
|
||||||
|
@ -111,17 +111,17 @@
|
||||||
"@filterNoDateLabel": {},
|
"@filterNoDateLabel": {},
|
||||||
"filterNoRatingLabel": "Nieoceniony",
|
"filterNoRatingLabel": "Nieoceniony",
|
||||||
"@filterNoRatingLabel": {},
|
"@filterNoRatingLabel": {},
|
||||||
"filterNoTagLabel": "Nieoznakowany",
|
"filterNoTagLabel": "Nieoznaczone",
|
||||||
"@filterNoTagLabel": {},
|
"@filterNoTagLabel": {},
|
||||||
"filterNoTitleLabel": "Bez tytułu",
|
"filterNoTitleLabel": "Bez tytułu",
|
||||||
"@filterNoTitleLabel": {},
|
"@filterNoTitleLabel": {},
|
||||||
"filterOnThisDayLabel": "Tego dnia",
|
"filterOnThisDayLabel": "Tego dnia",
|
||||||
"@filterOnThisDayLabel": {},
|
"@filterOnThisDayLabel": {},
|
||||||
"filterRecentlyAddedLabel": "Ostatnio dodany",
|
"filterRecentlyAddedLabel": "Ostatnio dodane",
|
||||||
"@filterRecentlyAddedLabel": {},
|
"@filterRecentlyAddedLabel": {},
|
||||||
"filterTypeMotionPhotoLabel": "Ruchome Zdjęcie",
|
"filterTypeMotionPhotoLabel": "Ruchome Zdjęcie",
|
||||||
"@filterTypeMotionPhotoLabel": {},
|
"@filterTypeMotionPhotoLabel": {},
|
||||||
"filterTypePanoramaLabel": "Zdjęcie sferyczne",
|
"filterTypePanoramaLabel": "Zdjęcie panoramiczne",
|
||||||
"@filterTypePanoramaLabel": {},
|
"@filterTypePanoramaLabel": {},
|
||||||
"entryActionFlip": "Obróć w poziomie",
|
"entryActionFlip": "Obróć w poziomie",
|
||||||
"@entryActionFlip": {},
|
"@entryActionFlip": {},
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
"@entryActionSetAs": {},
|
"@entryActionSetAs": {},
|
||||||
"entryActionAddFavourite": "Dodaj do ulubionych",
|
"entryActionAddFavourite": "Dodaj do ulubionych",
|
||||||
"@entryActionAddFavourite": {},
|
"@entryActionAddFavourite": {},
|
||||||
"filterNoLocationLabel": "Nieumiejscowiony",
|
"filterNoLocationLabel": "Nieumiejscowione",
|
||||||
"@filterNoLocationLabel": {},
|
"@filterNoLocationLabel": {},
|
||||||
"filterRatingRejectedLabel": "Odrzucony",
|
"filterRatingRejectedLabel": "Odrzucony",
|
||||||
"@filterRatingRejectedLabel": {},
|
"@filterRatingRejectedLabel": {},
|
||||||
|
@ -193,9 +193,9 @@
|
||||||
"@nameConflictStrategySkip": {},
|
"@nameConflictStrategySkip": {},
|
||||||
"videoLoopModeAlways": "Zawsze",
|
"videoLoopModeAlways": "Zawsze",
|
||||||
"@videoLoopModeAlways": {},
|
"@videoLoopModeAlways": {},
|
||||||
"filterLocatedLabel": "Usytuowany",
|
"filterLocatedLabel": "Umiejscowione",
|
||||||
"@filterLocatedLabel": {},
|
"@filterLocatedLabel": {},
|
||||||
"filterTaggedLabel": "Oznaczony",
|
"filterTaggedLabel": "Oznaczone",
|
||||||
"@filterTaggedLabel": {},
|
"@filterTaggedLabel": {},
|
||||||
"nameConflictStrategyReplace": "Zastąp",
|
"nameConflictStrategyReplace": "Zastąp",
|
||||||
"@nameConflictStrategyReplace": {},
|
"@nameConflictStrategyReplace": {},
|
||||||
|
@ -211,9 +211,9 @@
|
||||||
"@filterAspectRatioPortraitLabel": {},
|
"@filterAspectRatioPortraitLabel": {},
|
||||||
"filterNoAddressLabel": "Brak adresu",
|
"filterNoAddressLabel": "Brak adresu",
|
||||||
"@filterNoAddressLabel": {},
|
"@filterNoAddressLabel": {},
|
||||||
"videoControlsPlaySeek": "Odtwórz i szukaj do przodu/do tyłu",
|
"videoControlsPlaySeek": "Odtwórz oraz przeszukuj",
|
||||||
"@videoControlsPlaySeek": {},
|
"@videoControlsPlaySeek": {},
|
||||||
"videoControlsPlayOutside": "Otwórz w innym odtwarzaczu",
|
"videoControlsPlayOutside": "Odtwórz innym odtwarzaczem",
|
||||||
"@videoControlsPlayOutside": {},
|
"@videoControlsPlayOutside": {},
|
||||||
"mapStyleGoogleNormal": "Mapy Google",
|
"mapStyleGoogleNormal": "Mapy Google",
|
||||||
"@mapStyleGoogleNormal": {},
|
"@mapStyleGoogleNormal": {},
|
||||||
|
@ -229,7 +229,7 @@
|
||||||
"@nameConflictStrategyRename": {},
|
"@nameConflictStrategyRename": {},
|
||||||
"mapStyleOsmHot": "Humanitarny OSM",
|
"mapStyleOsmHot": "Humanitarny OSM",
|
||||||
"@mapStyleOsmHot": {},
|
"@mapStyleOsmHot": {},
|
||||||
"keepScreenOnVideoPlayback": "Podczas odtwarzania wideo",
|
"keepScreenOnVideoPlayback": "Przy odtwarzaniu wideo",
|
||||||
"@keepScreenOnVideoPlayback": {},
|
"@keepScreenOnVideoPlayback": {},
|
||||||
"displayRefreshRatePreferLowest": "Najniższa",
|
"displayRefreshRatePreferLowest": "Najniższa",
|
||||||
"@displayRefreshRatePreferLowest": {},
|
"@displayRefreshRatePreferLowest": {},
|
||||||
|
@ -327,11 +327,11 @@
|
||||||
"@filterTypeGeotiffLabel": {},
|
"@filterTypeGeotiffLabel": {},
|
||||||
"filterMimeImageLabel": "Obraz",
|
"filterMimeImageLabel": "Obraz",
|
||||||
"@filterMimeImageLabel": {},
|
"@filterMimeImageLabel": {},
|
||||||
"unitSystemImperial": "Imperialny",
|
"unitSystemImperial": "Imperialne",
|
||||||
"@unitSystemImperial": {},
|
"@unitSystemImperial": {},
|
||||||
"videoLoopModeNever": "Nigdy",
|
"videoLoopModeNever": "Nigdy",
|
||||||
"@videoLoopModeNever": {},
|
"@videoLoopModeNever": {},
|
||||||
"videoControlsNone": "Nic",
|
"videoControlsNone": "Brak",
|
||||||
"@videoControlsNone": {},
|
"@videoControlsNone": {},
|
||||||
"accessibilityAnimationsRemove": "Zapobiegaj efektom ekranu",
|
"accessibilityAnimationsRemove": "Zapobiegaj efektom ekranu",
|
||||||
"@accessibilityAnimationsRemove": {},
|
"@accessibilityAnimationsRemove": {},
|
||||||
|
@ -341,7 +341,7 @@
|
||||||
"@displayRefreshRatePreferHighest": {},
|
"@displayRefreshRatePreferHighest": {},
|
||||||
"keepScreenOnNever": "Nigdy",
|
"keepScreenOnNever": "Nigdy",
|
||||||
"@keepScreenOnNever": {},
|
"@keepScreenOnNever": {},
|
||||||
"keepScreenOnViewerOnly": "Tylko na stronie przeglądarki",
|
"keepScreenOnViewerOnly": "Na stronie przeglądarki",
|
||||||
"@keepScreenOnViewerOnly": {},
|
"@keepScreenOnViewerOnly": {},
|
||||||
"videoPlaybackWithSound": "Odtwarzaj z dźwiękiem",
|
"videoPlaybackWithSound": "Odtwarzaj z dźwiękiem",
|
||||||
"@videoPlaybackWithSound": {},
|
"@videoPlaybackWithSound": {},
|
||||||
|
@ -359,7 +359,7 @@
|
||||||
"@coordinateDmsEast": {},
|
"@coordinateDmsEast": {},
|
||||||
"coordinateDmsWest": "Z",
|
"coordinateDmsWest": "Z",
|
||||||
"@coordinateDmsWest": {},
|
"@coordinateDmsWest": {},
|
||||||
"unitSystemMetric": "Metryczny",
|
"unitSystemMetric": "Metryczne",
|
||||||
"@unitSystemMetric": {},
|
"@unitSystemMetric": {},
|
||||||
"videoControlsPlay": "Odtwórz",
|
"videoControlsPlay": "Odtwórz",
|
||||||
"@videoControlsPlay": {},
|
"@videoControlsPlay": {},
|
||||||
|
@ -375,7 +375,7 @@
|
||||||
"@widgetOpenPageViewer": {},
|
"@widgetOpenPageViewer": {},
|
||||||
"albumTierNew": "Nowy",
|
"albumTierNew": "Nowy",
|
||||||
"@albumTierNew": {},
|
"@albumTierNew": {},
|
||||||
"albumTierSpecial": "Wspólny",
|
"albumTierSpecial": "Wspólne",
|
||||||
"@albumTierSpecial": {},
|
"@albumTierSpecial": {},
|
||||||
"albumTierApps": "Aplikacje",
|
"albumTierApps": "Aplikacje",
|
||||||
"@albumTierApps": {},
|
"@albumTierApps": {},
|
||||||
|
@ -617,7 +617,7 @@
|
||||||
},
|
},
|
||||||
"collectionEmptyFavourites": "Brak ulubionych",
|
"collectionEmptyFavourites": "Brak ulubionych",
|
||||||
"@collectionEmptyFavourites": {},
|
"@collectionEmptyFavourites": {},
|
||||||
"collectionEmptyVideos": "Brak filmów",
|
"collectionEmptyVideos": "Brak wideo",
|
||||||
"@collectionEmptyVideos": {},
|
"@collectionEmptyVideos": {},
|
||||||
"sortByDate": "Według daty",
|
"sortByDate": "Według daty",
|
||||||
"@sortByDate": {},
|
"@sortByDate": {},
|
||||||
|
@ -775,7 +775,7 @@
|
||||||
"@albumPickPageTitleCopy": {},
|
"@albumPickPageTitleCopy": {},
|
||||||
"albumPickPageTitleExport": "Wyeksportuj do albumu",
|
"albumPickPageTitleExport": "Wyeksportuj do albumu",
|
||||||
"@albumPickPageTitleExport": {},
|
"@albumPickPageTitleExport": {},
|
||||||
"tagEmpty": "Bez znaczników",
|
"tagEmpty": "Brak znaczników",
|
||||||
"@tagEmpty": {},
|
"@tagEmpty": {},
|
||||||
"searchCountriesSectionTitle": "Kraje",
|
"searchCountriesSectionTitle": "Kraje",
|
||||||
"@searchCountriesSectionTitle": {},
|
"@searchCountriesSectionTitle": {},
|
||||||
|
@ -1003,7 +1003,7 @@
|
||||||
"@settingsPrivacySectionTitle": {},
|
"@settingsPrivacySectionTitle": {},
|
||||||
"settingsAllowInstalledAppAccess": "Zezwól na dostęp do spisu aplikacji",
|
"settingsAllowInstalledAppAccess": "Zezwól na dostęp do spisu aplikacji",
|
||||||
"@settingsAllowInstalledAppAccess": {},
|
"@settingsAllowInstalledAppAccess": {},
|
||||||
"settingsAllowErrorReporting": "Pozwól na anonimowe zgłaszanie błędów",
|
"settingsAllowErrorReporting": "Zezwól na anonimowe zgłaszanie błędów",
|
||||||
"@settingsAllowErrorReporting": {},
|
"@settingsAllowErrorReporting": {},
|
||||||
"settingsSaveSearchHistory": "Zapisz historię wyszukiwania",
|
"settingsSaveSearchHistory": "Zapisz historię wyszukiwania",
|
||||||
"@settingsSaveSearchHistory": {},
|
"@settingsSaveSearchHistory": {},
|
||||||
|
@ -1045,7 +1045,7 @@
|
||||||
"@filePickerUseThisFolder": {},
|
"@filePickerUseThisFolder": {},
|
||||||
"mapEmptyRegion": "Brak obrazów w tym regionie",
|
"mapEmptyRegion": "Brak obrazów w tym regionie",
|
||||||
"@mapEmptyRegion": {},
|
"@mapEmptyRegion": {},
|
||||||
"settingsKeepScreenOnTile": "Pozostaw ekran załączony",
|
"settingsKeepScreenOnTile": "Pozostaw ekran włączony",
|
||||||
"@settingsKeepScreenOnTile": {},
|
"@settingsKeepScreenOnTile": {},
|
||||||
"filePickerOpenFrom": "Otwórz z",
|
"filePickerOpenFrom": "Otwórz z",
|
||||||
"@filePickerOpenFrom": {},
|
"@filePickerOpenFrom": {},
|
||||||
|
@ -1057,7 +1057,7 @@
|
||||||
"@filePickerDoNotShowHiddenFiles": {},
|
"@filePickerDoNotShowHiddenFiles": {},
|
||||||
"settingsActionImportDialogTitle": "Zaimportuj",
|
"settingsActionImportDialogTitle": "Zaimportuj",
|
||||||
"@settingsActionImportDialogTitle": {},
|
"@settingsActionImportDialogTitle": {},
|
||||||
"settingsKeepScreenOnDialogTitle": "Pozostaw ekran załączony",
|
"settingsKeepScreenOnDialogTitle": "Pozostaw ekran włączony",
|
||||||
"@settingsKeepScreenOnDialogTitle": {},
|
"@settingsKeepScreenOnDialogTitle": {},
|
||||||
"settingsNavigationDrawerTile": "Menu nawigacyjne",
|
"settingsNavigationDrawerTile": "Menu nawigacyjne",
|
||||||
"@settingsNavigationDrawerTile": {},
|
"@settingsNavigationDrawerTile": {},
|
||||||
|
@ -1083,7 +1083,7 @@
|
||||||
"@settingsVideoEnableHardwareAcceleration": {},
|
"@settingsVideoEnableHardwareAcceleration": {},
|
||||||
"settingsVideoAutoPlay": "Odtwarzaj automatycznie",
|
"settingsVideoAutoPlay": "Odtwarzaj automatycznie",
|
||||||
"@settingsVideoAutoPlay": {},
|
"@settingsVideoAutoPlay": {},
|
||||||
"settingsSubtitleThemeSample": "To jest próbka.",
|
"settingsSubtitleThemeSample": "Przykładowy napis.",
|
||||||
"@settingsSubtitleThemeSample": {},
|
"@settingsSubtitleThemeSample": {},
|
||||||
"settingsSubtitleThemeTextAlignmentDialogTitle": "Dopasowanie tekstu",
|
"settingsSubtitleThemeTextAlignmentDialogTitle": "Dopasowanie tekstu",
|
||||||
"@settingsSubtitleThemeTextAlignmentDialogTitle": {},
|
"@settingsSubtitleThemeTextAlignmentDialogTitle": {},
|
||||||
|
@ -1131,7 +1131,7 @@
|
||||||
"@mapStyleTooltip": {},
|
"@mapStyleTooltip": {},
|
||||||
"mapStyleDialogTitle": "Styl mapy",
|
"mapStyleDialogTitle": "Styl mapy",
|
||||||
"@mapStyleDialogTitle": {},
|
"@mapStyleDialogTitle": {},
|
||||||
"wallpaperUseScrollEffect": "Użyj efektu przewijania na ekranie głównym",
|
"wallpaperUseScrollEffect": "Używaj efektu przewijania na ekranie głównym",
|
||||||
"@wallpaperUseScrollEffect": {},
|
"@wallpaperUseScrollEffect": {},
|
||||||
"tagEditorSectionRecent": "Ostatnie",
|
"tagEditorSectionRecent": "Ostatnie",
|
||||||
"@tagEditorSectionRecent": {},
|
"@tagEditorSectionRecent": {},
|
||||||
|
@ -1231,7 +1231,7 @@
|
||||||
"@settingsUnitSystemTile": {},
|
"@settingsUnitSystemTile": {},
|
||||||
"addPathTooltip": "Dodaj ścieżkę",
|
"addPathTooltip": "Dodaj ścieżkę",
|
||||||
"@addPathTooltip": {},
|
"@addPathTooltip": {},
|
||||||
"settingsHiddenPathsBanner": "Zdjęcia i wideo w tych folderach ani w żadnym z ich podfolderów nie pojawią się w kolekcji.",
|
"settingsHiddenPathsBanner": "Zdjęcia i wideo w tych katalogach, ani w żadnym z ich podkatalogów nie pojawią się w kolekcji.",
|
||||||
"@settingsHiddenPathsBanner": {},
|
"@settingsHiddenPathsBanner": {},
|
||||||
"viewerInfoLabelOwner": "Właściciel",
|
"viewerInfoLabelOwner": "Właściciel",
|
||||||
"@viewerInfoLabelOwner": {},
|
"@viewerInfoLabelOwner": {},
|
||||||
|
@ -1307,7 +1307,7 @@
|
||||||
"@settingsVideoButtonsTile": {},
|
"@settingsVideoButtonsTile": {},
|
||||||
"settingsAllowInstalledAppAccessSubtitle": "Używane do poprawy wyświetlania albumu",
|
"settingsAllowInstalledAppAccessSubtitle": "Używane do poprawy wyświetlania albumu",
|
||||||
"@settingsAllowInstalledAppAccessSubtitle": {},
|
"@settingsAllowInstalledAppAccessSubtitle": {},
|
||||||
"settingsEnableBin": "Użyj kosza",
|
"settingsEnableBin": "Używaj kosza",
|
||||||
"@settingsEnableBin": {},
|
"@settingsEnableBin": {},
|
||||||
"settingsWidgetShowOutline": "Zarys",
|
"settingsWidgetShowOutline": "Zarys",
|
||||||
"@settingsWidgetShowOutline": {},
|
"@settingsWidgetShowOutline": {},
|
||||||
|
@ -1364,5 +1364,9 @@
|
||||||
"settingsSubtitleThemeTile": "Napisy",
|
"settingsSubtitleThemeTile": "Napisy",
|
||||||
"@settingsSubtitleThemeTile": {},
|
"@settingsSubtitleThemeTile": {},
|
||||||
"openMapPageTooltip": "Wyświetl na mapie",
|
"openMapPageTooltip": "Wyświetl na mapie",
|
||||||
"@openMapPageTooltip": {}
|
"@openMapPageTooltip": {},
|
||||||
|
"tooManyItemsErrorDialogMessage": "Spróbuj ponownie z mniejszą ilością elementów.",
|
||||||
|
"@tooManyItemsErrorDialogMessage": {},
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "Przesuń palcem w górę lub w dół, aby dostosować jasność/głośność",
|
||||||
|
"@settingsVideoGestureVerticalDragBrightnessVolume": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -432,13 +432,13 @@
|
||||||
"@renameProcessorCounter": {},
|
"@renameProcessorCounter": {},
|
||||||
"renameProcessorName": "Nume",
|
"renameProcessorName": "Nume",
|
||||||
"@renameProcessorName": {},
|
"@renameProcessorName": {},
|
||||||
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergeți acest album și articolul său?} other{Ștergeți acest album și {count} articole ale acestuia?}}",
|
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergi acest album și articolul din el?} other{Ștergi acest album și {count} articolele din el?}}",
|
||||||
"@deleteSingleAlbumConfirmationDialogMessage": {
|
"@deleteSingleAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergeți aceste albume și articolele lor?} other{Ștergeți aceste albume și {count} articole ale lor?}}",
|
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Ștergi aceste albume și articolul din ele?} other{Ștergi aceste albume și {count} articolele din ele?}}",
|
||||||
"@deleteMultiAlbumConfirmationDialogMessage": {
|
"@deleteMultiAlbumConfirmationDialogMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {}
|
"count": {}
|
||||||
|
@ -1364,5 +1364,7 @@
|
||||||
"settingsModificationWarningDialogMessage": "Alte setări vor fi modificate.",
|
"settingsModificationWarningDialogMessage": "Alte setări vor fi modificate.",
|
||||||
"@settingsModificationWarningDialogMessage": {},
|
"@settingsModificationWarningDialogMessage": {},
|
||||||
"settingsDisplayUseTvInterface": "Interfață Android TV",
|
"settingsDisplayUseTvInterface": "Interfață Android TV",
|
||||||
"@settingsDisplayUseTvInterface": {}
|
"@settingsDisplayUseTvInterface": {},
|
||||||
|
"tooManyItemsErrorDialogMessage": "Încearcă din nou cu mai puține elemente.",
|
||||||
|
"@tooManyItemsErrorDialogMessage": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -569,5 +569,87 @@
|
||||||
"viewDialogLayoutSectionTitle": "เค้าโครง",
|
"viewDialogLayoutSectionTitle": "เค้าโครง",
|
||||||
"@viewDialogLayoutSectionTitle": {},
|
"@viewDialogLayoutSectionTitle": {},
|
||||||
"aboutLinkPolicy": "นโยบายความเป็นส่วนตัว",
|
"aboutLinkPolicy": "นโยบายความเป็นส่วนตัว",
|
||||||
"@aboutLinkPolicy": {}
|
"@aboutLinkPolicy": {},
|
||||||
|
"filterLocatedLabel": "ระบุสถานที่",
|
||||||
|
"@filterLocatedLabel": {},
|
||||||
|
"filterTaggedLabel": "ระบุแท็ก",
|
||||||
|
"@filterTaggedLabel": {},
|
||||||
|
"keepScreenOnVideoPlayback": "ระหว่างการเล่นวิดีโอ",
|
||||||
|
"@keepScreenOnVideoPlayback": {},
|
||||||
|
"keepScreenOnViewerOnly": "หน้า Viewer เท่านั้น",
|
||||||
|
"@keepScreenOnViewerOnly": {},
|
||||||
|
"accessibilityAnimationsRemove": "ปิดการเคลื่อนไหว",
|
||||||
|
"@accessibilityAnimationsRemove": {},
|
||||||
|
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{ลบอัลบั้มเหล่านี้และรายการในอัลบั้มทั้งหมด?} other{ลบอัลบั้มเหล่านี้และ {count} รายการในอัลบั้ม?}}",
|
||||||
|
"@deleteMultiAlbumConfirmationDialogMessage": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entryActionShareVideoOnly": "แชร์วิดีโอเท่านั้น",
|
||||||
|
"@entryActionShareVideoOnly": {},
|
||||||
|
"entryActionShowGeoTiffOnMap": "แสดงแผนที่ซ้อน",
|
||||||
|
"@entryActionShowGeoTiffOnMap": {},
|
||||||
|
"videoActionCaptureFrame": "จับภาพเฟรม",
|
||||||
|
"@videoActionCaptureFrame": {},
|
||||||
|
"entryInfoActionRemoveLocation": "ลบสถานที่",
|
||||||
|
"@entryInfoActionRemoveLocation": {},
|
||||||
|
"filterAspectRatioLandscapeLabel": "ภาพแนวนอน",
|
||||||
|
"@filterAspectRatioLandscapeLabel": {},
|
||||||
|
"filterAspectRatioPortraitLabel": "ภาพแนวตั้ง",
|
||||||
|
"@filterAspectRatioPortraitLabel": {},
|
||||||
|
"filterNoAddressLabel": "ไม่มีที่อยู่",
|
||||||
|
"@filterNoAddressLabel": {},
|
||||||
|
"widgetOpenPageViewer": "เปิดหน้า Viewer",
|
||||||
|
"@widgetOpenPageViewer": {},
|
||||||
|
"coordinateDms": "{coordinate} {direction}",
|
||||||
|
"@coordinateDms": {
|
||||||
|
"placeholders": {
|
||||||
|
"coordinate": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "38° 41′ 47.72″"
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "S"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"missingSystemFilePickerDialogMessage": "ตัวเลือกไฟล์ระบบหายไป หรือปิดใช้งาน โปรดเปิดใช้งานและลองอีกครั้ง",
|
||||||
|
"@missingSystemFilePickerDialogMessage": {},
|
||||||
|
"addShortcutDialogLabel": "ป้ายทางลัด",
|
||||||
|
"@addShortcutDialogLabel": {},
|
||||||
|
"binEntriesConfirmationDialogMessage": "{count, plural, =1{ย้ายรายการนี้ไปยังถังขยะ?} other{ย้าย {count} รายการนี้ไปยังถังขยะ?}}",
|
||||||
|
"@binEntriesConfirmationDialogMessage": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{ลบรายการนี้?} other{ลบ {count} รายการนี้?}}",
|
||||||
|
"@deleteEntriesConfirmationDialogMessage": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{ลบอัลบั้มนี้และรายการในอัลบั้มทั้งหมด?} other{ลบอัลบั้มนี้และ {count} รายการในอัลบั้ม?}}",
|
||||||
|
"@deleteSingleAlbumConfirmationDialogMessage": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entryActionShareImageOnly": "แชร์รูปภาพเท่านั้น",
|
||||||
|
"@entryActionShareImageOnly": {},
|
||||||
|
"accessibilityAnimationsKeep": "เปิดการเคลื่อนไหว",
|
||||||
|
"@accessibilityAnimationsKeep": {},
|
||||||
|
"unsupportedTypeDialogMessage": "{count, plural, =1{การดำเนินการนี้ไม่รองรับรายการประเภทต่อไปนี้: {types}.} other{การดำเนินการนี้ไม่รองรับรายการประเภทต่อไปนี้: {types}.}}",
|
||||||
|
"@unsupportedTypeDialogMessage": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {},
|
||||||
|
"types": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "GIF, TIFF, MP4",
|
||||||
|
"description": "a list of unsupported types"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1208,5 +1208,7 @@
|
||||||
"filterTaggedLabel": "Etiketli",
|
"filterTaggedLabel": "Etiketli",
|
||||||
"@filterTaggedLabel": {},
|
"@filterTaggedLabel": {},
|
||||||
"tooManyItemsErrorDialogMessage": "Daha az ögeyle tekrar deneyin.",
|
"tooManyItemsErrorDialogMessage": "Daha az ögeyle tekrar deneyin.",
|
||||||
"@tooManyItemsErrorDialogMessage": {}
|
"@tooManyItemsErrorDialogMessage": {},
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "Parlaklığı/ses seviyesini ayarlamak için yukarı veya aşağı kaydırın",
|
||||||
|
"@settingsVideoGestureVerticalDragBrightnessVolume": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1364,5 +1364,9 @@
|
||||||
"filterLocatedLabel": "Розташований",
|
"filterLocatedLabel": "Розташований",
|
||||||
"@filterLocatedLabel": {},
|
"@filterLocatedLabel": {},
|
||||||
"filterTaggedLabel": "Позначений тегом",
|
"filterTaggedLabel": "Позначений тегом",
|
||||||
"@filterTaggedLabel": {}
|
"@filterTaggedLabel": {},
|
||||||
|
"tooManyItemsErrorDialogMessage": "Спробуйте ще раз з меншою кількістю елементів.",
|
||||||
|
"@tooManyItemsErrorDialogMessage": {},
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume": "Проведіть пальцем угору або вниз, щоб налаштувати яскравість/гучність",
|
||||||
|
"@settingsVideoGestureVerticalDragBrightnessVolume": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -463,7 +463,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
|
||||||
modified |= XMP.removeElements(
|
modified |= XMP.removeElements(
|
||||||
descriptions,
|
descriptions,
|
||||||
XMP.containerDirectory,
|
XMP.containerDirectory,
|
||||||
Namespaces.container,
|
Namespaces.gContainer,
|
||||||
);
|
);
|
||||||
|
|
||||||
modified |= [
|
modified |= [
|
||||||
|
|
|
@ -97,6 +97,7 @@ class SettingsDefaults {
|
||||||
static const videoControls = VideoControls.play;
|
static const videoControls = VideoControls.play;
|
||||||
static const videoGestureDoubleTapTogglePlay = false;
|
static const videoGestureDoubleTapTogglePlay = false;
|
||||||
static const videoGestureSideDoubleTapSeek = true;
|
static const videoGestureSideDoubleTapSeek = true;
|
||||||
|
static const videoGestureVerticalDragBrightnessVolume = false;
|
||||||
|
|
||||||
// subtitles
|
// subtitles
|
||||||
static const subtitleFontSize = 20.0;
|
static const subtitleFontSize = 20.0;
|
||||||
|
|
|
@ -41,7 +41,6 @@ class Settings extends ChangeNotifier {
|
||||||
static const Set<String> _internalKeys = {
|
static const Set<String> _internalKeys = {
|
||||||
hasAcceptedTermsKey,
|
hasAcceptedTermsKey,
|
||||||
catalogTimeZoneKey,
|
catalogTimeZoneKey,
|
||||||
videoShowRawTimedTextKey,
|
|
||||||
searchHistoryKey,
|
searchHistoryKey,
|
||||||
platformAccelerometerRotationKey,
|
platformAccelerometerRotationKey,
|
||||||
platformTransitionAnimationScaleKey,
|
platformTransitionAnimationScaleKey,
|
||||||
|
@ -131,10 +130,10 @@ class Settings extends ChangeNotifier {
|
||||||
static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec';
|
static const enableVideoHardwareAccelerationKey = 'video_hwaccel_mediacodec';
|
||||||
static const videoAutoPlayModeKey = 'video_auto_play_mode';
|
static const videoAutoPlayModeKey = 'video_auto_play_mode';
|
||||||
static const videoLoopModeKey = 'video_loop';
|
static const videoLoopModeKey = 'video_loop';
|
||||||
static const videoShowRawTimedTextKey = 'video_show_raw_timed_text';
|
|
||||||
static const videoControlsKey = 'video_controls';
|
static const videoControlsKey = 'video_controls';
|
||||||
static const videoGestureDoubleTapTogglePlayKey = 'video_gesture_double_tap_toggle_play';
|
static const videoGestureDoubleTapTogglePlayKey = 'video_gesture_double_tap_toggle_play';
|
||||||
static const videoGestureSideDoubleTapSeekKey = 'video_gesture_side_double_tap_skip';
|
static const videoGestureSideDoubleTapSeekKey = 'video_gesture_side_double_tap_skip';
|
||||||
|
static const videoGestureVerticalDragBrightnessVolumeKey = 'video_gesture_vertical_drag_brightness_volume';
|
||||||
|
|
||||||
// subtitles
|
// subtitles
|
||||||
static const subtitleFontSizeKey = 'subtitle_font_size';
|
static const subtitleFontSizeKey = 'subtitle_font_size';
|
||||||
|
@ -637,10 +636,6 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set videoLoopMode(VideoLoopMode newValue) => _set(videoLoopModeKey, newValue.toString());
|
set videoLoopMode(VideoLoopMode newValue) => _set(videoLoopModeKey, newValue.toString());
|
||||||
|
|
||||||
bool get videoShowRawTimedText => getBool(videoShowRawTimedTextKey) ?? SettingsDefaults.videoShowRawTimedText;
|
|
||||||
|
|
||||||
set videoShowRawTimedText(bool newValue) => _set(videoShowRawTimedTextKey, newValue);
|
|
||||||
|
|
||||||
VideoControls get videoControls => getEnumOrDefault(videoControlsKey, SettingsDefaults.videoControls, VideoControls.values);
|
VideoControls get videoControls => getEnumOrDefault(videoControlsKey, SettingsDefaults.videoControls, VideoControls.values);
|
||||||
|
|
||||||
set videoControls(VideoControls newValue) => _set(videoControlsKey, newValue.toString());
|
set videoControls(VideoControls newValue) => _set(videoControlsKey, newValue.toString());
|
||||||
|
@ -653,6 +648,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set videoGestureSideDoubleTapSeek(bool newValue) => _set(videoGestureSideDoubleTapSeekKey, newValue);
|
set videoGestureSideDoubleTapSeek(bool newValue) => _set(videoGestureSideDoubleTapSeekKey, newValue);
|
||||||
|
|
||||||
|
bool get videoGestureVerticalDragBrightnessVolume => getBool(videoGestureVerticalDragBrightnessVolumeKey) ?? SettingsDefaults.videoGestureVerticalDragBrightnessVolume;
|
||||||
|
|
||||||
|
set videoGestureVerticalDragBrightnessVolume(bool newValue) => _set(videoGestureVerticalDragBrightnessVolumeKey, newValue);
|
||||||
|
|
||||||
// subtitles
|
// subtitles
|
||||||
|
|
||||||
double get subtitleFontSize => getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize;
|
double get subtitleFontSize => getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize;
|
||||||
|
@ -1039,6 +1038,7 @@ class Settings extends ChangeNotifier {
|
||||||
case enableVideoHardwareAccelerationKey:
|
case enableVideoHardwareAccelerationKey:
|
||||||
case videoGestureDoubleTapTogglePlayKey:
|
case videoGestureDoubleTapTogglePlayKey:
|
||||||
case videoGestureSideDoubleTapSeekKey:
|
case videoGestureSideDoubleTapSeekKey:
|
||||||
|
case videoGestureVerticalDragBrightnessVolumeKey:
|
||||||
case subtitleShowOutlineKey:
|
case subtitleShowOutlineKey:
|
||||||
case tagEditorCurrentFilterSectionExpandedKey:
|
case tagEditorCurrentFilterSectionExpandedKey:
|
||||||
case saveSearchHistoryKey:
|
case saveSearchHistoryKey:
|
||||||
|
|
|
@ -14,6 +14,8 @@ class AIcons {
|
||||||
static const IconData aspectRatio = Icons.aspect_ratio_outlined;
|
static const IconData aspectRatio = Icons.aspect_ratio_outlined;
|
||||||
static const IconData bin = Icons.delete_outlined;
|
static const IconData bin = Icons.delete_outlined;
|
||||||
static const IconData broken = Icons.broken_image_outlined;
|
static const IconData broken = Icons.broken_image_outlined;
|
||||||
|
static const IconData brightnessMin = Icons.brightness_low_outlined;
|
||||||
|
static const IconData brightnessMax = Icons.brightness_high_outlined;
|
||||||
static const IconData checked = Icons.done_outlined;
|
static const IconData checked = Icons.done_outlined;
|
||||||
static const IconData count = MdiIcons.counter;
|
static const IconData count = MdiIcons.counter;
|
||||||
static const IconData counter = Icons.plus_one_outlined;
|
static const IconData counter = Icons.plus_one_outlined;
|
||||||
|
@ -52,6 +54,8 @@ class AIcons {
|
||||||
static const IconData text = Icons.format_quote_outlined;
|
static const IconData text = Icons.format_quote_outlined;
|
||||||
static const IconData tag = Icons.local_offer_outlined;
|
static const IconData tag = Icons.local_offer_outlined;
|
||||||
static const IconData tagUntagged = MdiIcons.tagOffOutline;
|
static const IconData tagUntagged = MdiIcons.tagOffOutline;
|
||||||
|
static const IconData volumeMin = Icons.volume_mute_outlined;
|
||||||
|
static const IconData volumeMax = Icons.volume_up_outlined;
|
||||||
|
|
||||||
// view
|
// view
|
||||||
static const IconData group = Icons.group_work_outlined;
|
static const IconData group = Icons.group_work_outlined;
|
||||||
|
|
|
@ -123,6 +123,11 @@ class Dependencies {
|
||||||
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE',
|
licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/url_launcher/url_launcher/LICENSE',
|
||||||
sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher',
|
sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher',
|
||||||
),
|
),
|
||||||
|
Dependency(
|
||||||
|
name: 'Volume Controller',
|
||||||
|
license: mit,
|
||||||
|
sourceUrl: 'https://github.com/kurenai7968/volume_controller',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
static const List<Dependency> _googleMobileServices = [
|
static const List<Dependency> _googleMobileServices = [
|
||||||
|
|
|
@ -7,7 +7,6 @@ class Namespaces {
|
||||||
static const avm = 'http://www.communicatingastronomy.org/avm/1.0/';
|
static const avm = 'http://www.communicatingastronomy.org/avm/1.0/';
|
||||||
static const camera = 'http://pix4d.com/camera/1.0/';
|
static const camera = 'http://pix4d.com/camera/1.0/';
|
||||||
static const cc = 'http://creativecommons.org/ns#';
|
static const cc = 'http://creativecommons.org/ns#';
|
||||||
static const container = 'http://ns.google.com/photos/1.0/container/';
|
|
||||||
static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/';
|
static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/';
|
||||||
static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/';
|
static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/';
|
||||||
static const crlcp = 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/';
|
static const crlcp = 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/';
|
||||||
|
@ -26,9 +25,13 @@ class Namespaces {
|
||||||
static const exifEx = 'http://cipa.jp/exif/1.0/';
|
static const exifEx = 'http://cipa.jp/exif/1.0/';
|
||||||
static const gAudio = 'http://ns.google.com/photos/1.0/audio/';
|
static const gAudio = 'http://ns.google.com/photos/1.0/audio/';
|
||||||
static const gCamera = 'http://ns.google.com/photos/1.0/camera/';
|
static const gCamera = 'http://ns.google.com/photos/1.0/camera/';
|
||||||
|
static const gContainer = 'http://ns.google.com/photos/1.0/container/';
|
||||||
static const gCreations = 'http://ns.google.com/photos/1.0/creations/';
|
static const gCreations = 'http://ns.google.com/photos/1.0/creations/';
|
||||||
static const gDepth = 'http://ns.google.com/photos/1.0/depthmap/';
|
static const gDepth = 'http://ns.google.com/photos/1.0/depthmap/';
|
||||||
static const gDevice = 'http://ns.google.com/photos/dd/1.0/device/';
|
static const gDevice = 'http://ns.google.com/photos/dd/1.0/device/';
|
||||||
|
static const gDeviceCamera = 'http://ns.google.com/photos/dd/1.0/camera/';
|
||||||
|
static const gDeviceContainer = 'http://ns.google.com/photos/dd/1.0/container/';
|
||||||
|
static const gDeviceItem = 'http://ns.google.com/photos/dd/1.0/item/';
|
||||||
static const gFocus = 'http://ns.google.com/photos/1.0/focus/';
|
static const gFocus = 'http://ns.google.com/photos/1.0/focus/';
|
||||||
static const gImage = 'http://ns.google.com/photos/1.0/image/';
|
static const gImage = 'http://ns.google.com/photos/1.0/image/';
|
||||||
static const gPano = 'http://ns.google.com/photos/1.0/panorama/';
|
static const gPano = 'http://ns.google.com/photos/1.0/panorama/';
|
||||||
|
@ -83,7 +86,6 @@ class Namespaces {
|
||||||
avm: 'Astronomy Visualization',
|
avm: 'Astronomy Visualization',
|
||||||
camera: 'Pix4D Camera',
|
camera: 'Pix4D Camera',
|
||||||
cc: 'Creative Commons',
|
cc: 'Creative Commons',
|
||||||
container: 'Container',
|
|
||||||
crd: 'Camera Raw Defaults',
|
crd: 'Camera Raw Defaults',
|
||||||
creatorAtom: 'After Effects',
|
creatorAtom: 'After Effects',
|
||||||
crs: 'Camera Raw Settings',
|
crs: 'Camera Raw Settings',
|
||||||
|
@ -97,6 +99,7 @@ class Namespaces {
|
||||||
exifEx: 'Exif Ex',
|
exifEx: 'Exif Ex',
|
||||||
gAudio: 'Google Audio',
|
gAudio: 'Google Audio',
|
||||||
gCamera: 'Google Camera',
|
gCamera: 'Google Camera',
|
||||||
|
gContainer: 'Google Container',
|
||||||
gCreations: 'Google Creations',
|
gCreations: 'Google Creations',
|
||||||
gDepth: 'Google Depth',
|
gDepth: 'Google Depth',
|
||||||
gDevice: 'Google Device',
|
gDevice: 'Google Device',
|
||||||
|
@ -138,7 +141,7 @@ class Namespaces {
|
||||||
};
|
};
|
||||||
|
|
||||||
static final defaultPrefixes = {
|
static final defaultPrefixes = {
|
||||||
container: 'Container',
|
gContainer: 'Container',
|
||||||
dc: 'dc',
|
dc: 'dc',
|
||||||
gCamera: 'GCamera',
|
gCamera: 'GCamera',
|
||||||
microsoftPhoto: 'MicrosoftPhoto',
|
microsoftPhoto: 'MicrosoftPhoto',
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/widgets/about/credits.dart';
|
||||||
import 'package:aves/widgets/about/licenses.dart';
|
import 'package:aves/widgets/about/licenses.dart';
|
||||||
import 'package:aves/widgets/about/translators.dart';
|
import 'package:aves/widgets/about/translators.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
|
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';
|
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -28,7 +29,8 @@ class AboutPage extends StatelessWidget {
|
||||||
sliver: SliverList(
|
sliver: SliverList(
|
||||||
delegate: SliverChildListDelegate(
|
delegate: SliverChildListDelegate(
|
||||||
[
|
[
|
||||||
AppReference(showLogo: !useTvLayout),
|
const TvEdgeFocus(),
|
||||||
|
const AppReference(),
|
||||||
if (!settings.useTvLayout) ...[
|
if (!settings.useTvLayout) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const BugReport(),
|
const BugReport(),
|
||||||
|
|
|
@ -10,12 +10,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
class AppReference extends StatefulWidget {
|
class AppReference extends StatefulWidget {
|
||||||
final bool showLogo;
|
const AppReference({super.key});
|
||||||
|
|
||||||
const AppReference({
|
|
||||||
super.key,
|
|
||||||
required this.showLogo,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppReference> createState() => _AppReferenceState();
|
State<AppReference> createState() => _AppReferenceState();
|
||||||
|
@ -24,6 +19,13 @@ class AppReference extends StatefulWidget {
|
||||||
class _AppReferenceState extends State<AppReference> {
|
class _AppReferenceState extends State<AppReference> {
|
||||||
late Future<PackageInfo> _packageInfoLoader;
|
late Future<PackageInfo> _packageInfoLoader;
|
||||||
|
|
||||||
|
static const _appTitleStyle = TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
letterSpacing: 1.0,
|
||||||
|
fontFeatures: [FontFeature.enable('smcp')],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -44,28 +46,19 @@ class _AppReferenceState extends State<AppReference> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAvesLine() {
|
Widget _buildAvesLine() {
|
||||||
const style = TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
letterSpacing: 1.0,
|
|
||||||
fontFeatures: [FontFeature.enable('smcp')],
|
|
||||||
);
|
|
||||||
|
|
||||||
return FutureBuilder<PackageInfo>(
|
return FutureBuilder<PackageInfo>(
|
||||||
future: _packageInfoLoader,
|
future: _packageInfoLoader,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (widget.showLogo) ...[
|
AvesLogo(
|
||||||
AvesLogo(
|
size: _appTitleStyle.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
|
||||||
size: style.fontSize! * MediaQuery.textScaleFactorOf(context) * 1.3,
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
|
||||||
],
|
|
||||||
Text(
|
Text(
|
||||||
'${context.l10n.appName} ${snapshot.data?.version}',
|
'${context.l10n.appName} ${snapshot.data?.version}',
|
||||||
style: style,
|
style: _appTitleStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/widgets/about/title.dart';
|
||||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -14,13 +14,7 @@ class AboutCredits extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
AboutSectionTitle(text: l10n.aboutCreditsSectionTitle),
|
||||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Text(l10n.aboutCreditsSectionTitle, style: Constants.knownTitleTextStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text.rich(
|
Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:aves/app_flavor.dart';
|
import 'package:aves/app_flavor.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/ref/brand_colors.dart';
|
import 'package:aves/ref/brand_colors.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
|
||||||
import 'package:aves/utils/dependencies.dart';
|
import 'package:aves/utils/dependencies.dart';
|
||||||
|
import 'package:aves/widgets/about/title.dart';
|
||||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
|
@ -50,30 +51,32 @@ class _LicensesState extends State<Licenses> {
|
||||||
[
|
[
|
||||||
_buildHeader(),
|
_buildHeader(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
AvesExpansionTile(
|
if (!settings.useTvLayout) ...[
|
||||||
title: context.l10n.aboutLicensesAndroidLibrariesSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.android),
|
title: context.l10n.aboutLicensesAndroidLibrariesSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.android),
|
||||||
children: _platform.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _platform.map((package) => LicenseRow(package: package)).toList(),
|
||||||
AvesExpansionTile(
|
),
|
||||||
title: context.l10n.aboutLicensesFlutterPluginsSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
title: context.l10n.aboutLicensesFlutterPluginsSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _flutterPlugins.map((package) => LicenseRow(package: package)).toList(),
|
||||||
AvesExpansionTile(
|
),
|
||||||
title: context.l10n.aboutLicensesFlutterPackagesSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
title: context.l10n.aboutLicensesFlutterPackagesSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _flutterPackages.map((package) => LicenseRow(package: package)).toList(),
|
||||||
AvesExpansionTile(
|
),
|
||||||
title: context.l10n.aboutLicensesDartPackagesSectionTitle,
|
AvesExpansionTile(
|
||||||
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
title: context.l10n.aboutLicensesDartPackagesSectionTitle,
|
||||||
expandedNotifier: _expandedNotifier,
|
highlightColor: colors.fromBrandColor(BrandColors.flutter),
|
||||||
children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
|
expandedNotifier: _expandedNotifier,
|
||||||
),
|
children: _dartPackages.map((package) => LicenseRow(package: package)).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
Center(
|
Center(
|
||||||
child: AvesOutlinedButton(
|
child: AvesOutlinedButton(
|
||||||
label: context.l10n.aboutLicensesShowAllButtonLabel,
|
label: context.l10n.aboutLicensesShowAllButtonLabel,
|
||||||
|
@ -104,13 +107,7 @@ class _LicensesState extends State<Licenses> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
AboutSectionTitle(text: context.l10n.aboutLicensesSectionTitle),
|
||||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Text(context.l10n.aboutLicensesSectionTitle, style: Constants.knownTitleTextStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(context.l10n.aboutLicensesBanner),
|
Text(context.l10n.aboutLicensesBanner),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/basic/markdown_container.dart';
|
import 'package:aves/widgets/common/basic/markdown_container.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -14,6 +15,7 @@ class PolicyPage extends StatefulWidget {
|
||||||
|
|
||||||
class _PolicyPageState extends State<PolicyPage> {
|
class _PolicyPageState extends State<PolicyPage> {
|
||||||
late Future<String> _termsLoader;
|
late Future<String> _termsLoader;
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
static const termsPath = 'assets/terms.md';
|
static const termsPath = 'assets/terms.md';
|
||||||
static const termsDirection = TextDirection.ltr;
|
static const termsDirection = TextDirection.ltr;
|
||||||
|
@ -28,26 +30,72 @@ class _PolicyPageState extends State<PolicyPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
automaticallyImplyLeading: !settings.useTvLayout,
|
||||||
title: Text(context.l10n.policyPageTitle),
|
title: Text(context.l10n.policyPageTitle),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Center(
|
child: FocusableActionDetector(
|
||||||
child: FutureBuilder<String>(
|
autofocus: true,
|
||||||
future: _termsLoader,
|
shortcuts: const {
|
||||||
builder: (context, snapshot) {
|
SingleActivator(LogicalKeyboardKey.arrowUp): _ScrollIntent.up(),
|
||||||
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
|
SingleActivator(LogicalKeyboardKey.arrowDown): _ScrollIntent.down(),
|
||||||
final terms = snapshot.data!;
|
},
|
||||||
return Padding(
|
actions: {
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
_ScrollIntent: CallbackAction<_ScrollIntent>(onInvoke: _onScrollIntent),
|
||||||
child: MarkdownContainer(
|
},
|
||||||
data: terms,
|
child: Center(
|
||||||
textDirection: termsDirection,
|
child: FutureBuilder<String>(
|
||||||
),
|
future: _termsLoader,
|
||||||
);
|
builder: (context, snapshot) {
|
||||||
},
|
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
|
||||||
|
final terms = snapshot.data!;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: MarkdownContainer(
|
||||||
|
scrollController: _scrollController,
|
||||||
|
data: terms,
|
||||||
|
textDirection: termsDirection,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onScrollIntent(_ScrollIntent intent) {
|
||||||
|
late int factor;
|
||||||
|
switch (intent.type) {
|
||||||
|
case _ScrollDirection.up:
|
||||||
|
factor = -1;
|
||||||
|
break;
|
||||||
|
case _ScrollDirection.down:
|
||||||
|
factor = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_scrollController.animateTo(
|
||||||
|
_scrollController.offset + factor * 150,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScrollIntent extends Intent {
|
||||||
|
const _ScrollIntent({
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _ScrollIntent.up() : type = _ScrollDirection.up;
|
||||||
|
|
||||||
|
const _ScrollIntent.down() : type = _ScrollDirection.down;
|
||||||
|
|
||||||
|
final _ScrollDirection type;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _ScrollDirection {
|
||||||
|
up,
|
||||||
|
down,
|
||||||
}
|
}
|
||||||
|
|
37
lib/widgets/about/title.dart
Normal file
37
lib/widgets/about/title.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/utils/constants.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AboutSectionTitle extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
const AboutSectionTitle({
|
||||||
|
super.key,
|
||||||
|
required this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget child = Container(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
||||||
|
child: Text(text, style: Constants.knownTitleTextStyle),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (settings.useTvLayout) {
|
||||||
|
child = InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
|
import 'package:aves/widgets/about/title.dart';
|
||||||
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -50,27 +51,21 @@ class AboutTranslators extends StatelessWidget {
|
||||||
// Contributor('slasb37', 'p84haghi@gmail.com'), // Persian
|
// Contributor('slasb37', 'p84haghi@gmail.com'), // Persian
|
||||||
// Contributor('tryvseu', 'tryvseu@tuta.io'), // Nynorsk
|
// Contributor('tryvseu', 'tryvseu@tuta.io'), // Nynorsk
|
||||||
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
|
// Contributor('Nattapong K', 'mixer5056@gmail.com'), // Thai
|
||||||
|
// Contributor('Idj', 'joneltmp+goahn@gmail.com'), // Hebrew
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
AboutSectionTitle(text: context.l10n.aboutTranslatorsSectionTitle),
|
||||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Text(l10n.aboutTranslatorsSectionTitle, style: Constants.knownTitleTextStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_RandomTextSpanHighlighter(
|
_RandomTextSpanHighlighter(
|
||||||
spans: translators.map((v) => v.name).toList(),
|
spans: translators.map((v) => v.name).toList(),
|
||||||
highlightColor: Theme.of(context).colorScheme.onPrimary,
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
@ -81,11 +76,11 @@ class AboutTranslators extends StatelessWidget {
|
||||||
|
|
||||||
class _RandomTextSpanHighlighter extends StatefulWidget {
|
class _RandomTextSpanHighlighter extends StatefulWidget {
|
||||||
final List<String> spans;
|
final List<String> spans;
|
||||||
final Color highlightColor;
|
final Color color;
|
||||||
|
|
||||||
const _RandomTextSpanHighlighter({
|
const _RandomTextSpanHighlighter({
|
||||||
required this.spans,
|
required this.spans,
|
||||||
required this.highlightColor,
|
required this.color,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -102,18 +97,21 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
final color = widget.color;
|
||||||
_baseStyle = TextStyle(
|
_baseStyle = TextStyle(
|
||||||
|
color: color.withOpacity(.7),
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: widget.highlightColor.withOpacity(0),
|
color: color.withOpacity(0),
|
||||||
blurRadius: 0,
|
blurRadius: 0,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
final highlightStyle = TextStyle(
|
final highlightStyle = TextStyle(
|
||||||
|
color: color.withOpacity(1),
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: widget.highlightColor,
|
color: color.withOpacity(1),
|
||||||
blurRadius: 3,
|
blurRadius: 3,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -132,7 +130,7 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
|
||||||
..repeat(reverse: true);
|
..repeat(reverse: true);
|
||||||
_animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(CurvedAnimation(
|
_animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(CurvedAnimation(
|
||||||
parent: _controller,
|
parent: _controller,
|
||||||
curve: Curves.linear,
|
curve: Curves.easeInOutCubic,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class AvesApp extends StatefulWidget {
|
||||||
final AppFlavor flavor;
|
final AppFlavor flavor;
|
||||||
|
|
||||||
// temporary exclude locales not ready yet for prime time
|
// temporary exclude locales not ready yet for prime time
|
||||||
static final _unsupportedLocales = {'ar', 'fa', 'gl', 'nn', 'th'}.map(Locale.new).toSet();
|
static final _unsupportedLocales = {'ar', 'fa', 'gl', 'he', 'nn', 'th'}.map(Locale.new).toSet();
|
||||||
static final List<Locale> supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList();
|
static final List<Locale> supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList();
|
||||||
static final ValueNotifier<EdgeInsets> cutoutInsetsNotifier = ValueNotifier(EdgeInsets.zero);
|
static final ValueNotifier<EdgeInsets> cutoutInsetsNotifier = ValueNotifier(EdgeInsets.zero);
|
||||||
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey(debugLabel: 'app-navigator');
|
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey(debugLabel: 'app-navigator');
|
||||||
|
@ -540,6 +540,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
? 'profile'
|
? 'profile'
|
||||||
: 'debug',
|
: 'debug',
|
||||||
'has_mobile_services': mobileServices.isServiceAvailable,
|
'has_mobile_services': mobileServices.isServiceAvailable,
|
||||||
|
'is_television': device.isTelevision,
|
||||||
'locales': WidgetsBinding.instance.window.locales.join(', '),
|
'locales': WidgetsBinding.instance.window.locales.join(', '),
|
||||||
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
|
'time_zone': '${now.timeZoneName} (${now.timeZoneOffset})',
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,6 +58,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
}) {
|
}) {
|
||||||
final canWrite = !settings.isReadOnly;
|
final canWrite = !settings.isReadOnly;
|
||||||
final isMain = appMode == AppMode.main;
|
final isMain = appMode == AppMode.main;
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
// general
|
// general
|
||||||
case EntrySetAction.configureView:
|
case EntrySetAction.configureView:
|
||||||
|
@ -70,9 +71,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
return isSelecting && selectedItemCount == itemCount;
|
return isSelecting && selectedItemCount == itemCount;
|
||||||
// browsing
|
// browsing
|
||||||
case EntrySetAction.searchCollection:
|
case EntrySetAction.searchCollection:
|
||||||
return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
|
return !useTvLayout && appMode.canNavigate && !isSelecting;
|
||||||
case EntrySetAction.toggleTitleSearch:
|
case EntrySetAction.toggleTitleSearch:
|
||||||
return !isSelecting;
|
return !useTvLayout && !isSelecting;
|
||||||
case EntrySetAction.addShortcut:
|
case EntrySetAction.addShortcut:
|
||||||
return isMain && !isSelecting && device.canPinShortcut && !isTrash;
|
return isMain && !isSelecting && device.canPinShortcut && !isTrash;
|
||||||
case EntrySetAction.emptyBin:
|
case EntrySetAction.emptyBin:
|
||||||
|
@ -83,7 +84,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.stats:
|
case EntrySetAction.stats:
|
||||||
return isMain;
|
return isMain;
|
||||||
case EntrySetAction.rescan:
|
case EntrySetAction.rescan:
|
||||||
return !settings.useTvLayout && isMain && !isTrash;
|
return !useTvLayout && isMain && !isTrash;
|
||||||
// selecting
|
// selecting
|
||||||
case EntrySetAction.share:
|
case EntrySetAction.share:
|
||||||
case EntrySetAction.toggleFavourite:
|
case EntrySetAction.toggleFavourite:
|
||||||
|
|
|
@ -27,7 +27,7 @@ import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/viewer/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -6,11 +6,13 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
class MarkdownContainer extends StatelessWidget {
|
class MarkdownContainer extends StatelessWidget {
|
||||||
final String data;
|
final String data;
|
||||||
final TextDirection? textDirection;
|
final TextDirection? textDirection;
|
||||||
|
final ScrollController? scrollController;
|
||||||
|
|
||||||
const MarkdownContainer({
|
const MarkdownContainer({
|
||||||
super.key,
|
super.key,
|
||||||
required this.data,
|
required this.data,
|
||||||
this.textDirection,
|
this.textDirection,
|
||||||
|
this.scrollController,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const double maxWidth = 460;
|
static const double maxWidth = 460;
|
||||||
|
@ -44,6 +46,7 @@ class MarkdownContainer extends StatelessWidget {
|
||||||
data: data,
|
data: data,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
|
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
|
||||||
|
controller: scrollController,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
25
lib/widgets/common/basic/tv_edge_focus.dart
Normal file
25
lib/widgets/common/basic/tv_edge_focus.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
// to be placed at the edges of lists and grids,
|
||||||
|
// so that TV can reach them with D-pad
|
||||||
|
class TvEdgeFocus extends StatelessWidget {
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
|
||||||
|
const TvEdgeFocus({
|
||||||
|
super.key,
|
||||||
|
this.focusNode,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final useTvLayout = context.select<Settings, bool>((s) => s.useTvLayout);
|
||||||
|
return useTvLayout
|
||||||
|
? Focus(
|
||||||
|
focusNode: focusNode,
|
||||||
|
child: const SizedBox(),
|
||||||
|
)
|
||||||
|
: const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
|
@ -31,26 +32,49 @@ class TitledExpandableFilterRow extends StatelessWidget {
|
||||||
|
|
||||||
final isExpanded = expandedNotifier.value == title;
|
final isExpanded = expandedNotifier.value == title;
|
||||||
|
|
||||||
|
Widget header = Text(
|
||||||
|
title,
|
||||||
|
style: Constants.knownTitleTextStyle,
|
||||||
|
);
|
||||||
|
void toggle() => expandedNotifier.value = isExpanded ? null : title;
|
||||||
|
if (settings.useTvLayout) {
|
||||||
|
header = Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: toggle,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
header,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
header = Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
header,
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(isExpanded ? AIcons.collapse : AIcons.expand),
|
||||||
|
onPressed: toggle,
|
||||||
|
tooltip: isExpanded ? MaterialLocalizations.of(context).expandedIconTapHint : MaterialLocalizations.of(context).collapsedIconTapHint,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
header,
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: Constants.knownTitleTextStyle,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(isExpanded ? AIcons.collapse : AIcons.expand),
|
|
||||||
onPressed: () => expandedNotifier.value = isExpanded ? null : title,
|
|
||||||
tooltip: isExpanded ? MaterialLocalizations.of(context).expandedIconTapHint : MaterialLocalizations.of(context).collapsedIconTapHint,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ExpandableFilterRow(
|
ExpandableFilterRow(
|
||||||
filters: filters,
|
filters: filters,
|
||||||
isExpanded: isExpanded,
|
isExpanded: isExpanded,
|
||||||
|
|
|
@ -34,18 +34,10 @@ class SectionHeader<T> extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget child = _buildContent(context);
|
Widget child = _buildContent(context);
|
||||||
if (settings.useTvLayout) {
|
if (settings.useTvLayout) {
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
child = InkWell(
|
||||||
child = Material(
|
onTap: _onTap(context),
|
||||||
type: MaterialType.transparency,
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
child: InkResponse(
|
child: child,
|
||||||
onTap: _onTap(context),
|
|
||||||
containedInkWell: true,
|
|
||||||
highlightShape: BoxShape.rectangle,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
|
||||||
hoverColor: primaryColor.withOpacity(0.04),
|
|
||||||
splashColor: primaryColor.withOpacity(0.12),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
@ -8,7 +8,7 @@ class CaptionedButton extends StatefulWidget {
|
||||||
final Animation<double> scale;
|
final Animation<double> scale;
|
||||||
final Widget captionText;
|
final Widget captionText;
|
||||||
final CaptionedIconButtonBuilder iconButtonBuilder;
|
final CaptionedIconButtonBuilder iconButtonBuilder;
|
||||||
final bool showCaption;
|
final bool autofocus, showCaption;
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8);
|
static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8);
|
||||||
|
@ -21,6 +21,7 @@ class CaptionedButton extends StatefulWidget {
|
||||||
CaptionedIconButtonBuilder? iconButtonBuilder,
|
CaptionedIconButtonBuilder? iconButtonBuilder,
|
||||||
String? caption,
|
String? caption,
|
||||||
Widget? captionText,
|
Widget? captionText,
|
||||||
|
this.autofocus = false,
|
||||||
this.showCaption = true,
|
this.showCaption = true,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : assert(icon != null || iconButtonBuilder != null),
|
}) : assert(icon != null || iconButtonBuilder != null),
|
||||||
|
@ -57,6 +58,7 @@ class CaptionedButton extends StatefulWidget {
|
||||||
class _CaptionedButtonState extends State<CaptionedButton> {
|
class _CaptionedButtonState extends State<CaptionedButton> {
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
final ValueNotifier<bool> _focusedNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _focusedNotifier = ValueNotifier(false);
|
||||||
|
bool _didAutofocus = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -65,12 +67,21 @@ class _CaptionedButtonState extends State<CaptionedButton> {
|
||||||
_focusNode.addListener(_onFocusChanged);
|
_focusNode.addListener(_onFocusChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_handleAutofocus();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CaptionedButton oldWidget) {
|
void didUpdateWidget(covariant CaptionedButton oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.onPressed != widget.onPressed) {
|
if (oldWidget.onPressed != widget.onPressed) {
|
||||||
_updateTraversal();
|
_updateTraversal();
|
||||||
}
|
}
|
||||||
|
if (oldWidget.autofocus != widget.autofocus) {
|
||||||
|
_handleAutofocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -120,6 +131,13 @@ class _CaptionedButtonState extends State<CaptionedButton> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleAutofocus() {
|
||||||
|
if (!_didAutofocus && widget.autofocus) {
|
||||||
|
FocusScope.of(context).autofocus(_focusNode);
|
||||||
|
_didAutofocus = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onFocusChanged() => _focusedNotifier.value = _focusNode.hasFocus;
|
void _onFocusChanged() => _focusedNotifier.value = _focusNode.hasFocus;
|
||||||
|
|
||||||
void _updateTraversal() {
|
void _updateTraversal() {
|
||||||
|
|
|
@ -18,6 +18,9 @@ abstract class AvesSearchDelegate extends SearchDelegate {
|
||||||
query = initialQuery ?? '';
|
query = initialQuery ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mustCallSuper
|
||||||
|
void dispose() {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? buildLeading(BuildContext context) {
|
Widget? buildLeading(BuildContext context) {
|
||||||
if (settings.useTvLayout) {
|
if (settings.useTvLayout) {
|
||||||
|
@ -44,7 +47,7 @@ abstract class AvesSearchDelegate extends SearchDelegate {
|
||||||
@override
|
@override
|
||||||
List<Widget>? buildActions(BuildContext context) {
|
List<Widget>? buildActions(BuildContext context) {
|
||||||
return [
|
return [
|
||||||
if (query.isNotEmpty)
|
if (!settings.useTvLayout && query.isNotEmpty)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(AIcons.clear),
|
icon: const Icon(AIcons.clear),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -63,28 +66,40 @@ abstract class AvesSearchDelegate extends SearchDelegate {
|
||||||
|
|
||||||
void clean() {
|
void clean() {
|
||||||
currentBody = null;
|
currentBody = null;
|
||||||
focusNode?.unfocus();
|
searchFieldFocusNode?.unfocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// adapted from Flutter `SearchDelegate` in `/material/search.dart`
|
// adapted from Flutter `SearchDelegate` in `/material/search.dart`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void showResults(BuildContext context) {
|
void showResults(BuildContext context) {
|
||||||
focusNode?.unfocus();
|
if (settings.useTvLayout) {
|
||||||
currentBody = SearchBody.results;
|
suggestionsScrollController?.jumpTo(0);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
suggestionsFocusNode?.requestFocus();
|
||||||
|
FocusScope.of(context).nextFocus();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
searchFieldFocusNode?.unfocus();
|
||||||
|
currentBody = SearchBody.results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void showSuggestions(BuildContext context) {
|
void showSuggestions(BuildContext context) {
|
||||||
assert(focusNode != null, '_focusNode must be set by route before showSuggestions is called.');
|
assert(searchFieldFocusNode != null, '_focusNode must be set by route before showSuggestions is called.');
|
||||||
focusNode!.requestFocus();
|
searchFieldFocusNode!.requestFocus();
|
||||||
currentBody = SearchBody.suggestions;
|
currentBody = SearchBody.suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Animation<double> get transitionAnimation => proxyAnimation;
|
Animation<double> get transitionAnimation => proxyAnimation;
|
||||||
|
|
||||||
FocusNode? focusNode;
|
FocusNode? searchFieldFocusNode;
|
||||||
|
|
||||||
|
FocusNode? get suggestionsFocusNode => null;
|
||||||
|
|
||||||
|
ScrollController? get suggestionsScrollController => null;
|
||||||
|
|
||||||
final TextEditingController queryTextController = TextEditingController();
|
final TextEditingController queryTextController = TextEditingController();
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ class SearchPage extends StatefulWidget {
|
||||||
|
|
||||||
class _SearchPageState extends State<SearchPage> {
|
class _SearchPageState extends State<SearchPage> {
|
||||||
final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay);
|
final Debouncer _debouncer = Debouncer(delay: Durations.searchDebounceDelay);
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _searchFieldFocusNode = FocusNode();
|
||||||
final DoubleBackPopHandler _doubleBackPopHandler = DoubleBackPopHandler();
|
final DoubleBackPopHandler _doubleBackPopHandler = DoubleBackPopHandler();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -37,7 +37,7 @@ class _SearchPageState extends State<SearchPage> {
|
||||||
super.initState();
|
super.initState();
|
||||||
_registerWidget(widget);
|
_registerWidget(widget);
|
||||||
widget.animation.addStatusListener(_onAnimationStatusChanged);
|
widget.animation.addStatusListener(_onAnimationStatusChanged);
|
||||||
_focusNode.addListener(_onFocusChanged);
|
_searchFieldFocusNode.addListener(_onFocusChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -53,21 +53,22 @@ class _SearchPageState extends State<SearchPage> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_unregisterWidget(widget);
|
_unregisterWidget(widget);
|
||||||
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
||||||
_focusNode.dispose();
|
_searchFieldFocusNode.dispose();
|
||||||
_doubleBackPopHandler.dispose();
|
_doubleBackPopHandler.dispose();
|
||||||
|
widget.delegate.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _registerWidget(SearchPage widget) {
|
void _registerWidget(SearchPage widget) {
|
||||||
widget.delegate.queryTextController.addListener(_onQueryChanged);
|
widget.delegate.queryTextController.addListener(_onQueryChanged);
|
||||||
widget.delegate.currentBodyNotifier.addListener(_onSearchBodyChanged);
|
widget.delegate.currentBodyNotifier.addListener(_onSearchBodyChanged);
|
||||||
widget.delegate.focusNode = _focusNode;
|
widget.delegate.searchFieldFocusNode = _searchFieldFocusNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(SearchPage widget) {
|
void _unregisterWidget(SearchPage widget) {
|
||||||
widget.delegate.queryTextController.removeListener(_onQueryChanged);
|
widget.delegate.queryTextController.removeListener(_onQueryChanged);
|
||||||
widget.delegate.currentBodyNotifier.removeListener(_onSearchBodyChanged);
|
widget.delegate.currentBodyNotifier.removeListener(_onSearchBodyChanged);
|
||||||
widget.delegate.focusNode = null;
|
widget.delegate.searchFieldFocusNode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAnimationStatusChanged(AnimationStatus status) {
|
void _onAnimationStatusChanged(AnimationStatus status) {
|
||||||
|
@ -77,12 +78,12 @@ class _SearchPageState extends State<SearchPage> {
|
||||||
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
widget.animation.removeStatusListener(_onAnimationStatusChanged);
|
||||||
Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) {
|
Future.delayed(Durations.pageTransitionAnimation * timeDilation).then((_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_focusNode.requestFocus();
|
_searchFieldFocusNode.requestFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFocusChanged() {
|
void _onFocusChanged() {
|
||||||
if (_focusNode.hasFocus && widget.delegate.currentBody != SearchBody.suggestions) {
|
if (_searchFieldFocusNode.hasFocus && widget.delegate.currentBody != SearchBody.suggestions) {
|
||||||
widget.delegate.showSuggestions(context);
|
widget.delegate.showSuggestions(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +137,7 @@ class _SearchPageState extends State<SearchPage> {
|
||||||
style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
|
style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: widget.delegate.queryTextController,
|
controller: widget.delegate.queryTextController,
|
||||||
focusNode: _focusNode,
|
focusNode: _searchFieldFocusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
hintText: widget.delegate.searchFieldLabel,
|
hintText: widget.delegate.searchFieldLabel,
|
||||||
|
|
|
@ -43,11 +43,6 @@ class DebugSettingsSection extends StatelessWidget {
|
||||||
onChanged: (v) => settings.canUseAnalysisService = v,
|
onChanged: (v) => settings.canUseAnalysisService = v,
|
||||||
title: const Text('canUseAnalysisService'),
|
title: const Text('canUseAnalysisService'),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
|
||||||
value: settings.videoShowRawTimedText,
|
|
||||||
onChanged: (v) => settings.videoShowRawTimedText = v,
|
|
||||||
title: const Text('videoShowRawTimedText'),
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||||
child: InfoRowGroup(
|
child: InfoRowGroup(
|
||||||
|
|
|
@ -135,16 +135,14 @@ class SelectionRadioListTile<T> extends StatelessWidget {
|
||||||
reselectable: true,
|
reselectable: true,
|
||||||
title: Text(
|
title: Text(
|
||||||
title,
|
title,
|
||||||
softWrap: false,
|
overflow: TextOverflow.ellipsis,
|
||||||
overflow: TextOverflow.fade,
|
maxLines: 2,
|
||||||
maxLines: 1,
|
|
||||||
),
|
),
|
||||||
subtitle: subtitle != null
|
subtitle: subtitle != null
|
||||||
? Text(
|
? Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
maxLines: 1,
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
dense: dense,
|
dense: dense,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/basic/menu.dart';
|
import 'package:aves/widgets/common/basic/menu.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
|
||||||
import 'package:aves/widgets/common/identity/empty.dart';
|
import 'package:aves/widgets/common/identity/empty.dart';
|
||||||
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
||||||
import 'package:aves/widgets/dialogs/filter_editors/create_album_dialog.dart';
|
import 'package:aves/widgets/dialogs/filter_editors/create_album_dialog.dart';
|
||||||
|
@ -128,6 +129,53 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
|
||||||
Selection<FilterGridItem<AlbumFilter>> selection,
|
Selection<FilterGridItem<AlbumFilter>> selection,
|
||||||
AlbumChipSetActionDelegate actionDelegate,
|
AlbumChipSetActionDelegate actionDelegate,
|
||||||
) {
|
) {
|
||||||
|
final itemCount = actionDelegate.allItems.length;
|
||||||
|
final isSelecting = selection.isSelecting;
|
||||||
|
final selectedItems = selection.selectedItems;
|
||||||
|
final selectedFilters = selectedItems.map((v) => v.filter).toSet();
|
||||||
|
|
||||||
|
bool isVisible(ChipSetAction action) => actionDelegate.isVisible(
|
||||||
|
action,
|
||||||
|
appMode: appMode,
|
||||||
|
isSelecting: isSelecting,
|
||||||
|
itemCount: itemCount,
|
||||||
|
selectedFilters: selectedFilters,
|
||||||
|
);
|
||||||
|
|
||||||
|
return settings.useTvLayout
|
||||||
|
? _buildTelevisionActions(
|
||||||
|
context: context,
|
||||||
|
isVisible: isVisible,
|
||||||
|
actionDelegate: actionDelegate,
|
||||||
|
)
|
||||||
|
: _buildMobileActions(
|
||||||
|
context: context,
|
||||||
|
isVisible: isVisible,
|
||||||
|
actionDelegate: actionDelegate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTelevisionActions({
|
||||||
|
required BuildContext context,
|
||||||
|
required bool Function(ChipSetAction action) isVisible,
|
||||||
|
required AlbumChipSetActionDelegate actionDelegate,
|
||||||
|
}) {
|
||||||
|
return [
|
||||||
|
...ChipSetActions.general,
|
||||||
|
].where(isVisible).map((action) {
|
||||||
|
return CaptionedButton(
|
||||||
|
icon: action.getIcon(),
|
||||||
|
caption: action.getText(context),
|
||||||
|
onPressed: () => actionDelegate.onActionSelected(context, {}, action),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildMobileActions({
|
||||||
|
required BuildContext context,
|
||||||
|
required bool Function(ChipSetAction action) isVisible,
|
||||||
|
required AlbumChipSetActionDelegate actionDelegate,
|
||||||
|
}) {
|
||||||
return [
|
return [
|
||||||
if (widget.moveType != null)
|
if (widget.moveType != null)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -149,7 +197,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
|
||||||
child: PopupMenuButton<ChipSetAction>(
|
child: PopupMenuButton<ChipSetAction>(
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
FilterGridAppBar.toMenuItem(context, ChipSetAction.configureView, enabled: true),
|
...ChipSetActions.general.where(isVisible).map((action) => FilterGridAppBar.toMenuItem(context, action, enabled: true)),
|
||||||
const PopupMenuDivider(),
|
const PopupMenuDivider(),
|
||||||
FilterGridAppBar.toMenuItem(context, ChipSetAction.toggleTitleSearch, enabled: true),
|
FilterGridAppBar.toMenuItem(context, ChipSetAction.toggleTitleSearch, enabled: true),
|
||||||
];
|
];
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
import 'package:aves/image_providers/app_icon_image_provider.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/common/basic/query_bar.dart';
|
import 'package:aves/widgets/common/basic/query_bar.dart';
|
||||||
|
@ -37,8 +38,10 @@ class _AppPickPageState extends State<AppPickPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
automaticallyImplyLeading: !useTvLayout,
|
||||||
title: Text(context.l10n.appPickDialogTitle),
|
title: Text(context.l10n.appPickDialogTitle),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
@ -57,7 +60,7 @@ class _AppPickPageState extends State<AppPickPage> {
|
||||||
final packages = allPackages.where((package) => package.categoryLauncher).toList()..sort((a, b) => compareAsciiUpperCase(_displayName(a), _displayName(b)));
|
final packages = allPackages.where((package) => package.categoryLauncher).toList()..sort((a, b) => compareAsciiUpperCase(_displayName(a), _displayName(b)));
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
QueryBar(queryNotifier: _queryNotifier),
|
if (!useTvLayout) QueryBar(queryNotifier: _queryNotifier),
|
||||||
ValueListenableBuilder<String>(
|
ValueListenableBuilder<String>(
|
||||||
valueListenable: _queryNotifier,
|
valueListenable: _queryNotifier,
|
||||||
builder: (context, query, child) {
|
builder: (context, query, child) {
|
||||||
|
|
|
@ -70,6 +70,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
||||||
final selectedItemCount = selectedFilters.length;
|
final selectedItemCount = selectedFilters.length;
|
||||||
final hasSelection = selectedFilters.isNotEmpty;
|
final hasSelection = selectedFilters.isNotEmpty;
|
||||||
final isMain = appMode == AppMode.main;
|
final isMain = appMode == AppMode.main;
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
// general
|
// general
|
||||||
case ChipSetAction.configureView:
|
case ChipSetAction.configureView:
|
||||||
|
@ -82,9 +83,9 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
||||||
return isSelecting && selectedItemCount == itemCount;
|
return isSelecting && selectedItemCount == itemCount;
|
||||||
// browsing
|
// browsing
|
||||||
case ChipSetAction.search:
|
case ChipSetAction.search:
|
||||||
return !settings.useTvLayout && appMode.canNavigate && !isSelecting;
|
return !useTvLayout && appMode.canNavigate && !isSelecting;
|
||||||
case ChipSetAction.toggleTitleSearch:
|
case ChipSetAction.toggleTitleSearch:
|
||||||
return !isSelecting;
|
return !useTvLayout && !isSelecting;
|
||||||
case ChipSetAction.createAlbum:
|
case ChipSetAction.createAlbum:
|
||||||
return false;
|
return false;
|
||||||
// browsing or selecting
|
// browsing or selecting
|
||||||
|
|
|
@ -115,15 +115,23 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (useTvLayout) {
|
if (useTvLayout) {
|
||||||
|
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Row(
|
body: canNavigate
|
||||||
children: [
|
? Row(
|
||||||
TvRail(
|
children: [
|
||||||
controller: context.read<TvRailController>(),
|
TvRail(
|
||||||
),
|
controller: context.read<TvRailController>(),
|
||||||
Expanded(child: body),
|
),
|
||||||
],
|
Expanded(child: body),
|
||||||
),
|
],
|
||||||
|
)
|
||||||
|
: DirectionalSafeArea(
|
||||||
|
top: false,
|
||||||
|
end: false,
|
||||||
|
bottom: false,
|
||||||
|
child: body,
|
||||||
|
),
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
);
|
);
|
||||||
|
|
|
@ -277,11 +277,12 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
||||||
MapAction.zoomIn,
|
MapAction.zoomIn,
|
||||||
MapAction.zoomOut,
|
MapAction.zoomOut,
|
||||||
]
|
]
|
||||||
.map((action) => Padding(
|
.mapIndexed((i, action) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: CaptionedButton(
|
child: CaptionedButton(
|
||||||
icon: action.getIcon(),
|
icon: action.getIcon(),
|
||||||
caption: action.getText(context),
|
caption: action.getText(context),
|
||||||
|
autofocus: i == 0,
|
||||||
onPressed: () => MapActionDelegate(_mapController).onActionSelected(context, action),
|
onPressed: () => MapActionDelegate(_mapController).onActionSelected(context, action),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
|
@ -19,6 +19,7 @@ import 'package:aves/model/source/location.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
|
||||||
import 'package:aves/widgets/common/expandable_filter_row.dart';
|
import 'package:aves/widgets/common/expandable_filter_row.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
|
@ -33,6 +34,14 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final CollectionLens? parentCollection;
|
final CollectionLens? parentCollection;
|
||||||
final ValueNotifier<String?> _expandedSectionNotifier = ValueNotifier(null);
|
final ValueNotifier<String?> _expandedSectionNotifier = ValueNotifier(null);
|
||||||
|
final FocusNode _suggestionsTopFocusNode = FocusNode();
|
||||||
|
final ScrollController _suggestionsScrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
FocusNode? get suggestionsFocusNode => _suggestionsTopFocusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ScrollController get suggestionsScrollController => _suggestionsScrollController;
|
||||||
|
|
||||||
static const int searchHistoryCount = 10;
|
static const int searchHistoryCount = 10;
|
||||||
static final typeFilters = [
|
static final typeFilters = [
|
||||||
|
@ -64,6 +73,14 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
|
||||||
query = initialQuery ?? '';
|
query = initialQuery ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_expandedSectionNotifier.dispose();
|
||||||
|
_suggestionsTopFocusNode.dispose();
|
||||||
|
_suggestionsScrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildSuggestions(BuildContext context) {
|
Widget buildSuggestions(BuildContext context) {
|
||||||
final upQuery = query.trim().toUpperCase();
|
final upQuery = query.trim().toUpperCase();
|
||||||
|
@ -91,8 +108,12 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
|
||||||
final history = settings.searchHistory.where(notHidden).toList();
|
final history = settings.searchHistory.where(notHidden).toList();
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
|
controller: _suggestionsScrollController,
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
children: [
|
children: [
|
||||||
|
TvEdgeFocus(
|
||||||
|
focusNode: _suggestionsTopFocusNode,
|
||||||
|
),
|
||||||
_buildFilterRow(
|
_buildFilterRow(
|
||||||
context: context,
|
context: context,
|
||||||
filters: [
|
filters: [
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/outlined_button.dart';
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/navigation/drawer/tile.dart';
|
import 'package:aves/widgets/navigation/drawer/tile.dart';
|
||||||
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
|
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -29,8 +30,10 @@ class _DrawerAlbumTabState extends State<DrawerAlbumTab> {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const DrawerEditorBanner(),
|
if (!settings.useTvLayout) ...[
|
||||||
const Divider(height: 0),
|
const DrawerEditorBanner(),
|
||||||
|
const Divider(height: 0),
|
||||||
|
],
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ReorderableListView.builder(
|
child: ReorderableListView.builder(
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
|
import 'package:aves/widgets/settings/navigation/drawer_editor_banner.dart';
|
||||||
|
@ -30,8 +31,10 @@ class _DrawerFixedListTabState<T> extends State<DrawerFixedListTab<T>> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const DrawerEditorBanner(),
|
if (!settings.useTvLayout) ...[
|
||||||
const Divider(height: 0),
|
const DrawerEditorBanner(),
|
||||||
|
const Divider(height: 0),
|
||||||
|
],
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ReorderableListView.builder(
|
child: ReorderableListView.builder(
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
|
|
@ -36,6 +36,11 @@ class VideoControlsPage extends StatelessWidget {
|
||||||
onChanged: (v) => settings.videoGestureSideDoubleTapSeek = v,
|
onChanged: (v) => settings.videoGestureSideDoubleTapSeek = v,
|
||||||
title: context.l10n.settingsVideoGestureSideDoubleTapSeek,
|
title: context.l10n.settingsVideoGestureSideDoubleTapSeek,
|
||||||
),
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
selector: (context, s) => s.videoGestureVerticalDragBrightnessVolume,
|
||||||
|
onChanged: (v) => settings.videoGestureVerticalDragBrightnessVolume = v,
|
||||||
|
title: context.l10n.settingsVideoGestureVerticalDragBrightnessVolume,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:aves/widgets/common/basic/text/background_painter.dart';
|
||||||
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/borders.dart';
|
import 'package:aves/widgets/common/fx/borders.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/subtitle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
|
|
@ -111,45 +111,37 @@ class _MimeDonutState extends State<MimeDonut> with AutomaticKeepAliveClientMixi
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
|
||||||
final legend = SizedBox(
|
final legend = SizedBox(
|
||||||
width: dim,
|
width: dim,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: seriesData
|
children: seriesData
|
||||||
.map((d) => Material(
|
.map((d) => InkWell(
|
||||||
type: MaterialType.transparency,
|
onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
|
||||||
child: InkResponse(
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
|
child: Row(
|
||||||
containedInkWell: true,
|
mainAxisSize: MainAxisSize.min,
|
||||||
highlightShape: BoxShape.rectangle,
|
children: [
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
Icon(AIcons.disc, color: d.color),
|
||||||
hoverColor: primaryColor.withOpacity(0.04),
|
const SizedBox(width: 8),
|
||||||
splashColor: primaryColor.withOpacity(0.12),
|
Flexible(
|
||||||
child: Row(
|
child: Text(
|
||||||
mainAxisSize: MainAxisSize.min,
|
d.displayText,
|
||||||
children: [
|
overflow: TextOverflow.fade,
|
||||||
Icon(AIcons.disc, color: d.color),
|
softWrap: false,
|
||||||
const SizedBox(width: 8),
|
maxLines: 1,
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
d.displayText,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
softWrap: false,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
),
|
||||||
Text(
|
const SizedBox(width: 8),
|
||||||
numberFormat.format(d.entryCount),
|
Text(
|
||||||
style: TextStyle(
|
numberFormat.format(d.entryCount),
|
||||||
color: Theme.of(context).textTheme.bodySmall!.color,
|
style: TextStyle(
|
||||||
),
|
color: Theme.of(context).textTheme.bodySmall!.color,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
),
|
||||||
],
|
const SizedBox(width: 4),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
|
@ -96,6 +97,7 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final useTvLayout = settings.useTvLayout;
|
||||||
return ValueListenableBuilder<bool>(
|
return ValueListenableBuilder<bool>(
|
||||||
valueListenable: _isPageAnimatingNotifier,
|
valueListenable: _isPageAnimatingNotifier,
|
||||||
builder: (context, animating, child) {
|
builder: (context, animating, child) {
|
||||||
|
@ -196,6 +198,7 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
|
const TvEdgeFocus(),
|
||||||
mimeDonuts,
|
mimeDonuts,
|
||||||
Histogram(
|
Histogram(
|
||||||
entries: entries,
|
entries: entries,
|
||||||
|
@ -218,7 +221,7 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: !settings.useTvLayout,
|
automaticallyImplyLeading: !useTvLayout,
|
||||||
title: Text(l10n.statsPageTitle),
|
title: Text(l10n.statsPageTitle),
|
||||||
),
|
),
|
||||||
body: GestureAreaProtectorStack(
|
body: GestureAreaProtectorStack(
|
||||||
|
@ -274,23 +277,15 @@ class _StatsPageState extends State<StatsPage> {
|
||||||
style: Constants.knownTitleTextStyle,
|
style: Constants.knownTitleTextStyle,
|
||||||
);
|
);
|
||||||
if (settings.useTvLayout) {
|
if (settings.useTvLayout) {
|
||||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
|
||||||
header = Container(
|
header = Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Material(
|
child: InkWell(
|
||||||
type: MaterialType.transparency,
|
onTap: onHeaderPressed,
|
||||||
child: InkResponse(
|
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||||
onTap: onHeaderPressed,
|
child: Padding(
|
||||||
containedInkWell: true,
|
padding: const EdgeInsets.all(16),
|
||||||
highlightShape: BoxShape.rectangle,
|
child: header,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
|
||||||
hoverColor: primaryColor.withOpacity(0.04),
|
|
||||||
splashColor: primaryColor.withOpacity(0.12),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: header,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,7 +31,7 @@ import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/export_entry_dialog.dart';
|
import 'package:aves/widgets/dialogs/export_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/filter_grids/album_pick.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
||||||
import 'package:aves/widgets/viewer/action/printer.dart';
|
import 'package:aves/widgets/viewer/action/printer.dart';
|
||||||
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
||||||
|
|
|
@ -138,10 +138,12 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
|
||||||
child: child!,
|
child: child!,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: InfoPage(
|
child: FocusScope(
|
||||||
collection: collection,
|
child: InfoPage(
|
||||||
entryNotifier: widget.entryNotifier,
|
collection: collection,
|
||||||
isScrollingNotifier: _isVerticallyScrollingNotifier,
|
entryNotifier: widget.entryNotifier,
|
||||||
|
isScrollingNotifier: _isVerticallyScrollingNotifier,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -286,10 +288,10 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
|
||||||
final opacity = min(1.0, page);
|
final opacity = min(1.0, page);
|
||||||
_backgroundOpacityNotifier.value = opacity * opacity;
|
_backgroundOpacityNotifier.value = opacity * opacity;
|
||||||
|
|
||||||
if (page <= 1 && settings.viewerMaxBrightness) {
|
if (settings.viewerMaxBrightness) {
|
||||||
_systemBrightness?.then((system) {
|
_systemBrightness?.then((system) {
|
||||||
final transition = max(system, lerpDouble(system, maximumBrightness, page / 2)!);
|
final value = lerpDouble(maximumBrightness, system, ((1 - page).abs() * 2).clamp(0, 1))!;
|
||||||
ScreenBrightness().setScreenBrightness(transition);
|
ScreenBrightness().setScreenBrightness(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -680,9 +680,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLeave() async {
|
Future<void> _onLeave() async {
|
||||||
if (settings.viewerMaxBrightness) {
|
await ScreenBrightness().resetScreenBrightness();
|
||||||
await ScreenBrightness().resetScreenBrightness();
|
|
||||||
}
|
|
||||||
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
||||||
await windowService.keepScreenOn(false);
|
await windowService.keepScreenOn(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/tv_edge_focus.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
||||||
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
||||||
import 'package:aves/widgets/viewer/embedded/embedded_data_opener.dart';
|
import 'package:aves/widgets/viewer/embedded/embedded_data_opener.dart';
|
||||||
|
@ -281,6 +282,9 @@ class _InfoPageContentState extends State<_InfoPageContent> {
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: widget.scrollController,
|
controller: widget.scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: TvEdgeFocus(),
|
||||||
|
),
|
||||||
InfoAppBar(
|
InfoAppBar(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
|
|
|
@ -22,56 +22,64 @@ import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class XmpNamespace extends Equatable {
|
class XmpNamespace extends Equatable {
|
||||||
|
final Map<String, String> schemaRegistryPrefixes;
|
||||||
final String nsUri, nsPrefix;
|
final String nsUri, nsPrefix;
|
||||||
final Map<String, String> rawProps;
|
final Map<String, String> rawProps;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [nsUri, nsPrefix];
|
List<Object?> get props => [nsUri, nsPrefix];
|
||||||
|
|
||||||
const XmpNamespace(this.nsUri, this.nsPrefix, this.rawProps);
|
XmpNamespace({
|
||||||
|
required this.nsUri,
|
||||||
|
required this.schemaRegistryPrefixes,
|
||||||
|
required this.rawProps,
|
||||||
|
}) : nsPrefix = prefixForUri(schemaRegistryPrefixes, nsUri);
|
||||||
|
|
||||||
factory XmpNamespace.create(String nsUri, String nsPrefix, Map<String, String> rawProps) {
|
factory XmpNamespace.create(Map<String, String> schemaRegistryPrefixes, String nsPrefix, Map<String, String> rawProps) {
|
||||||
|
final nsUri = schemaRegistryPrefixes[nsPrefix] ?? '';
|
||||||
switch (nsUri) {
|
switch (nsUri) {
|
||||||
case Namespaces.container:
|
|
||||||
return XmpContainer(nsPrefix, rawProps);
|
|
||||||
case Namespaces.creatorAtom:
|
case Namespaces.creatorAtom:
|
||||||
return XmpCreatorAtom(nsPrefix, rawProps);
|
return XmpCreatorAtom(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.crs:
|
case Namespaces.crs:
|
||||||
return XmpCrsNamespace(nsPrefix, rawProps);
|
return XmpCrsNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.darktable:
|
case Namespaces.darktable:
|
||||||
return XmpDarktableNamespace(nsPrefix, rawProps);
|
return XmpDarktableNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.dwc:
|
case Namespaces.dwc:
|
||||||
return XmpDwcNamespace(nsPrefix, rawProps);
|
return XmpDwcNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.exif:
|
case Namespaces.exif:
|
||||||
return XmpExifNamespace(nsPrefix, rawProps);
|
return XmpExifNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.gAudio:
|
case Namespaces.gAudio:
|
||||||
return XmpGAudioNamespace(nsPrefix, rawProps);
|
return XmpGAudioNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
|
case Namespaces.gCamera:
|
||||||
|
return XmpGCameraNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
|
case Namespaces.gContainer:
|
||||||
|
return XmpGContainer(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.gDepth:
|
case Namespaces.gDepth:
|
||||||
return XmpGDepthNamespace(nsPrefix, rawProps);
|
return XmpGDepthNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.gDevice:
|
case Namespaces.gDevice:
|
||||||
return XmpGDeviceNamespace(nsPrefix, rawProps);
|
return XmpGDeviceNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.gImage:
|
case Namespaces.gImage:
|
||||||
return XmpGImageNamespace(nsPrefix, rawProps);
|
return XmpGImageNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.iptc4xmpCore:
|
case Namespaces.iptc4xmpCore:
|
||||||
return XmpIptcCoreNamespace(nsPrefix, rawProps);
|
return XmpIptcCoreNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.iptc4xmpExt:
|
case Namespaces.iptc4xmpExt:
|
||||||
return XmpIptc4xmpExtNamespace(nsPrefix, rawProps);
|
return XmpIptc4xmpExtNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.mwgrs:
|
case Namespaces.mwgrs:
|
||||||
return XmpMgwRegionsNamespace(nsPrefix, rawProps);
|
return XmpMgwRegionsNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.mp:
|
case Namespaces.mp:
|
||||||
return XmpMPNamespace(nsPrefix, rawProps);
|
return XmpMPNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.photoshop:
|
case Namespaces.photoshop:
|
||||||
return XmpPhotoshopNamespace(nsPrefix, rawProps);
|
return XmpPhotoshopNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.plus:
|
case Namespaces.plus:
|
||||||
return XmpPlusNamespace(nsPrefix, rawProps);
|
return XmpPlusNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.tiff:
|
case Namespaces.tiff:
|
||||||
return XmpTiffNamespace(nsPrefix, rawProps);
|
return XmpTiffNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.xmp:
|
case Namespaces.xmp:
|
||||||
return XmpBasicNamespace(nsPrefix, rawProps);
|
return XmpBasicNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
case Namespaces.xmpMM:
|
case Namespaces.xmpMM:
|
||||||
return XmpMMNamespace(nsPrefix, rawProps);
|
return XmpMMNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
default:
|
default:
|
||||||
return XmpNamespace(nsUri, nsPrefix, rawProps);
|
return XmpNamespace(nsUri: nsUri, schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +138,8 @@ class XmpNamespace extends Equatable {
|
||||||
String formatValue(XmpProp prop) => prop.value;
|
String formatValue(XmpProp prop) => prop.value;
|
||||||
|
|
||||||
Map<String, InfoValueSpanBuilder> linkifyValues(List<XmpProp> props) => {};
|
Map<String, InfoValueSpanBuilder> linkifyValues(List<XmpProp> props) => {};
|
||||||
|
|
||||||
|
static String prefixForUri(Map<String, String> schemaRegistryPrefixes, String nsUri) => schemaRegistryPrefixes.entries.firstWhereOrNull((kv) => kv.value == nsUri)?.key ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpProp implements Comparable<XmpProp> {
|
class XmpProp implements Comparable<XmpProp> {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/utils/xmp_utils.dart';
|
||||||
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
||||||
|
|
||||||
class XmpCrsNamespace extends XmpNamespace {
|
class XmpCrsNamespace extends XmpNamespace {
|
||||||
XmpCrsNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.crs, nsPrefix, rawProps);
|
XmpCrsNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.crs);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
|
|
@ -4,68 +4,69 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
||||||
|
|
||||||
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/exif.md
|
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/exif.md
|
||||||
class XmpExifNamespace extends XmpNamespace {
|
class XmpExifNamespace extends XmpNamespace {
|
||||||
const XmpExifNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.exif, nsPrefix, rawProps);
|
XmpExifNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.exif);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String formatValue(XmpProp prop) {
|
String formatValue(XmpProp prop) {
|
||||||
final v = prop.value;
|
final v = prop.value;
|
||||||
switch (prop.path) {
|
final field = prop.path.replaceAll(nsPrefix, '');
|
||||||
case 'exif:ColorSpace':
|
switch (field) {
|
||||||
|
case 'ColorSpace':
|
||||||
return Exif.getColorSpaceDescription(v);
|
return Exif.getColorSpaceDescription(v);
|
||||||
case 'exif:Contrast':
|
case 'Contrast':
|
||||||
return Exif.getContrastDescription(v);
|
return Exif.getContrastDescription(v);
|
||||||
case 'exif:CustomRendered':
|
case 'CustomRendered':
|
||||||
return Exif.getCustomRenderedDescription(v);
|
return Exif.getCustomRenderedDescription(v);
|
||||||
case 'exif:ExifVersion':
|
case 'ExifVersion':
|
||||||
case 'exif:FlashpixVersion':
|
case 'FlashpixVersion':
|
||||||
return Exif.getExifVersionDescription(v);
|
return Exif.getExifVersionDescription(v);
|
||||||
case 'exif:ExposureMode':
|
case 'ExposureMode':
|
||||||
return Exif.getExposureModeDescription(v);
|
return Exif.getExposureModeDescription(v);
|
||||||
case 'exif:ExposureProgram':
|
case 'ExposureProgram':
|
||||||
return Exif.getExposureProgramDescription(v);
|
return Exif.getExposureProgramDescription(v);
|
||||||
case 'exif:FileSource':
|
case 'FileSource':
|
||||||
return Exif.getFileSourceDescription(v);
|
return Exif.getFileSourceDescription(v);
|
||||||
case 'exif:Flash/exif:Mode':
|
case 'Flash/Mode':
|
||||||
return Exif.getFlashModeDescription(v);
|
return Exif.getFlashModeDescription(v);
|
||||||
case 'exif:Flash/exif:Return':
|
case 'Flash/Return':
|
||||||
return Exif.getFlashReturnDescription(v);
|
return Exif.getFlashReturnDescription(v);
|
||||||
case 'exif:FocalPlaneResolutionUnit':
|
case 'FocalPlaneResolutionUnit':
|
||||||
return Exif.getResolutionUnitDescription(v);
|
return Exif.getResolutionUnitDescription(v);
|
||||||
case 'exif:GainControl':
|
case 'GainControl':
|
||||||
return Exif.getGainControlDescription(v);
|
return Exif.getGainControlDescription(v);
|
||||||
case 'exif:LightSource':
|
case 'LightSource':
|
||||||
return Exif.getLightSourceDescription(v);
|
return Exif.getLightSourceDescription(v);
|
||||||
case 'exif:MeteringMode':
|
case 'MeteringMode':
|
||||||
return Exif.getMeteringModeDescription(v);
|
return Exif.getMeteringModeDescription(v);
|
||||||
case 'exif:Saturation':
|
case 'Saturation':
|
||||||
return Exif.getSaturationDescription(v);
|
return Exif.getSaturationDescription(v);
|
||||||
case 'exif:SceneCaptureType':
|
case 'SceneCaptureType':
|
||||||
return Exif.getSceneCaptureTypeDescription(v);
|
return Exif.getSceneCaptureTypeDescription(v);
|
||||||
case 'exif:SceneType':
|
case 'SceneType':
|
||||||
return Exif.getSceneTypeDescription(v);
|
return Exif.getSceneTypeDescription(v);
|
||||||
case 'exif:SensingMethod':
|
case 'SensingMethod':
|
||||||
return Exif.getSensingMethodDescription(v);
|
return Exif.getSensingMethodDescription(v);
|
||||||
case 'exif:Sharpness':
|
case 'Sharpness':
|
||||||
return Exif.getSharpnessDescription(v);
|
return Exif.getSharpnessDescription(v);
|
||||||
case 'exif:SubjectDistanceRange':
|
case 'SubjectDistanceRange':
|
||||||
return Exif.getSubjectDistanceRangeDescription(v);
|
return Exif.getSubjectDistanceRangeDescription(v);
|
||||||
case 'exif:WhiteBalance':
|
case 'WhiteBalance':
|
||||||
return Exif.getWhiteBalanceDescription(v);
|
return Exif.getWhiteBalanceDescription(v);
|
||||||
case 'exif:GPSAltitudeRef':
|
case 'GPSAltitudeRef':
|
||||||
return Exif.getGPSAltitudeRefDescription(v);
|
return Exif.getGPSAltitudeRefDescription(v);
|
||||||
case 'exif:GPSDestBearingRef':
|
case 'GPSDestBearingRef':
|
||||||
case 'exif:GPSImgDirectionRef':
|
case 'GPSImgDirectionRef':
|
||||||
case 'exif:GPSTrackRef':
|
case 'GPSTrackRef':
|
||||||
return Exif.getGPSDirectionRefDescription(v);
|
return Exif.getGPSDirectionRefDescription(v);
|
||||||
case 'exif:GPSDestDistanceRef':
|
case 'GPSDestDistanceRef':
|
||||||
return Exif.getGPSDestDistanceRefDescription(v);
|
return Exif.getGPSDestDistanceRefDescription(v);
|
||||||
case 'exif:GPSDifferential':
|
case 'GPSDifferential':
|
||||||
return Exif.getGPSDifferentialDescription(v);
|
return Exif.getGPSDifferentialDescription(v);
|
||||||
case 'exif:GPSMeasureMode':
|
case 'GPSMeasureMode':
|
||||||
return Exif.getGPSMeasureModeDescription(v);
|
return Exif.getGPSMeasureModeDescription(v);
|
||||||
case 'exif:GPSSpeedRef':
|
case 'GPSSpeedRef':
|
||||||
return Exif.getGPSSpeedRefDescription(v);
|
return Exif.getGPSSpeedRefDescription(v);
|
||||||
case 'exif:GPSStatus':
|
case 'GPSStatus':
|
||||||
return Exif.getGPSStatusDescription(v);
|
return Exif.getGPSStatusDescription(v);
|
||||||
default:
|
default:
|
||||||
return v;
|
return v;
|
||||||
|
|
|
@ -7,7 +7,11 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
abstract class XmpGoogleNamespace extends XmpNamespace {
|
abstract class XmpGoogleNamespace extends XmpNamespace {
|
||||||
const XmpGoogleNamespace(String nsUri, String nsPrefix, Map<String, String> rawProps) : super(nsUri, nsPrefix, rawProps);
|
XmpGoogleNamespace({
|
||||||
|
required super.nsUri,
|
||||||
|
required super.schemaRegistryPrefixes,
|
||||||
|
required super.rawProps,
|
||||||
|
});
|
||||||
|
|
||||||
List<Tuple2<String, String>> get dataProps;
|
List<Tuple2<String, String>> get dataProps;
|
||||||
|
|
||||||
|
@ -53,14 +57,34 @@ abstract class XmpGoogleNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpGAudioNamespace extends XmpGoogleNamespace {
|
class XmpGAudioNamespace extends XmpGoogleNamespace {
|
||||||
const XmpGAudioNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gAudio, nsPrefix, rawProps);
|
XmpGAudioNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gAudio);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Tuple2<String, String>> get dataProps => [Tuple2('${nsPrefix}Data', '${nsPrefix}Mime')];
|
List<Tuple2<String, String>> get dataProps => [
|
||||||
|
Tuple2('${nsPrefix}Data', '${nsPrefix}Mime'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
class XmpGCameraNamespace extends XmpGoogleNamespace {
|
||||||
|
XmpGCameraNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gCamera);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Tuple2<String, String>> get dataProps => [
|
||||||
|
Tuple2('${nsPrefix}RelitInputImageData', '${nsPrefix}RelitInputImageMime'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
class XmpGContainer extends XmpNamespace {
|
||||||
|
XmpGContainer({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gContainer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
late final List<XmpCardData> cards = [
|
||||||
|
XmpCardData(RegExp(nsPrefix + r'Directory\[(\d+)\]/' + nsPrefix + r'Item/(.*)'), title: 'Directory Item'),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpGDepthNamespace extends XmpGoogleNamespace {
|
class XmpGDepthNamespace extends XmpGoogleNamespace {
|
||||||
const XmpGDepthNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gDepth, nsPrefix, rawProps);
|
XmpGDepthNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gDepth);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Tuple2<String, String>> get dataProps => [
|
List<Tuple2<String, String>> get dataProps => [
|
||||||
|
@ -70,8 +94,16 @@ class XmpGDepthNamespace extends XmpGoogleNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpGDeviceNamespace extends XmpNamespace {
|
class XmpGDeviceNamespace extends XmpNamespace {
|
||||||
XmpGDeviceNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gDevice, nsPrefix, rawProps) {
|
late final String _cameraNsPrefix;
|
||||||
final mimePattern = RegExp(nsPrefix + r'Container/Container:Directory\[(\d+)\]/Item:Mime');
|
late final String _containerNsPrefix;
|
||||||
|
late final String _itemNsPrefix;
|
||||||
|
|
||||||
|
XmpGDeviceNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gDevice) {
|
||||||
|
_cameraNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, Namespaces.gDeviceCamera);
|
||||||
|
_containerNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, Namespaces.gDeviceContainer);
|
||||||
|
_itemNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, Namespaces.gDeviceItem);
|
||||||
|
|
||||||
|
final mimePattern = RegExp(nsPrefix + r'Container/' + _containerNsPrefix + r'Directory\[(\d+)\]/' + _itemNsPrefix + r'Mime');
|
||||||
final originalProps = rawProps.entries.toList();
|
final originalProps = rawProps.entries.toList();
|
||||||
originalProps.forEach((kv) {
|
originalProps.forEach((kv) {
|
||||||
final path = kv.key;
|
final path = kv.key;
|
||||||
|
@ -81,7 +113,7 @@ class XmpGDeviceNamespace extends XmpNamespace {
|
||||||
if (indexString != null) {
|
if (indexString != null) {
|
||||||
final index = int.tryParse(indexString);
|
final index = int.tryParse(indexString);
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
final dataPath = '${nsPrefix}Container/Container:Directory[$index]/Item:Data';
|
final dataPath = '${nsPrefix}Container/${_containerNsPrefix}Directory[$index]/${_itemNsPrefix}Data';
|
||||||
rawProps[dataPath] = '[skipped]';
|
rawProps[dataPath] = '[skipped]';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,16 +126,16 @@ class XmpGDeviceNamespace extends XmpNamespace {
|
||||||
XmpCardData(
|
XmpCardData(
|
||||||
RegExp(nsPrefix + r'Cameras\[(\d+)\]/(.*)'),
|
RegExp(nsPrefix + r'Cameras\[(\d+)\]/(.*)'),
|
||||||
cards: [
|
cards: [
|
||||||
XmpCardData(RegExp(r'Camera:DepthMap/(.*)')),
|
XmpCardData(RegExp(_cameraNsPrefix + r'DepthMap/(.*)')),
|
||||||
XmpCardData(RegExp(r'Camera:Image/(.*)')),
|
XmpCardData(RegExp(_cameraNsPrefix + r'Image/(.*)')),
|
||||||
XmpCardData(RegExp(r'Camera:ImagingModel/(.*)')),
|
XmpCardData(RegExp(_cameraNsPrefix + r'ImagingModel/(.*)')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
XmpCardData(
|
XmpCardData(
|
||||||
RegExp(nsPrefix + r'Container/Container:Directory\[(\d+)\]/(.*)'),
|
RegExp(nsPrefix + r'Container/' + _containerNsPrefix + r'Directory\[(\d+)\]/(.*)'),
|
||||||
spanBuilders: (index, struct) {
|
spanBuilders: (index, struct) {
|
||||||
if (struct.containsKey('Item:Data') && struct.containsKey('Item:DataURI')) {
|
if (struct.containsKey('${_itemNsPrefix}Data') && struct.containsKey('${_itemNsPrefix}DataURI')) {
|
||||||
final dataUriProp = struct['Item:DataURI'];
|
final dataUriProp = struct['${_itemNsPrefix}DataURI'];
|
||||||
if (dataUriProp != null) {
|
if (dataUriProp != null) {
|
||||||
return {
|
return {
|
||||||
'Data': InfoRowGroup.linkSpanBuilder(
|
'Data': InfoRowGroup.linkSpanBuilder(
|
||||||
|
@ -121,17 +153,10 @@ class XmpGDeviceNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpGImageNamespace extends XmpGoogleNamespace {
|
class XmpGImageNamespace extends XmpGoogleNamespace {
|
||||||
const XmpGImageNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gImage, nsPrefix, rawProps);
|
XmpGImageNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gImage);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Tuple2<String, String>> get dataProps => [Tuple2('${nsPrefix}Data', '${nsPrefix}Mime')];
|
List<Tuple2<String, String>> get dataProps => [
|
||||||
}
|
Tuple2('${nsPrefix}Data', '${nsPrefix}Mime'),
|
||||||
|
];
|
||||||
class XmpContainer extends XmpNamespace {
|
|
||||||
XmpContainer(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.container, nsPrefix, rawProps);
|
|
||||||
|
|
||||||
@override
|
|
||||||
late final List<XmpCardData> cards = [
|
|
||||||
XmpCardData(RegExp('${nsPrefix}Directory\\[(\\d+)\\]/${nsPrefix}Item/(.*)'), title: 'Directory Item'),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/utils/xmp_utils.dart';
|
||||||
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
||||||
|
|
||||||
class XmpCreatorAtom extends XmpNamespace {
|
class XmpCreatorAtom extends XmpNamespace {
|
||||||
XmpCreatorAtom(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.creatorAtom, nsPrefix, rawProps);
|
XmpCreatorAtom({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.creatorAtom);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -11,7 +11,7 @@ class XmpCreatorAtom extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpDarktableNamespace extends XmpNamespace {
|
class XmpDarktableNamespace extends XmpNamespace {
|
||||||
XmpDarktableNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.darktable, nsPrefix, rawProps);
|
XmpDarktableNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.darktable);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -20,7 +20,7 @@ class XmpDarktableNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpDwcNamespace extends XmpNamespace {
|
class XmpDwcNamespace extends XmpNamespace {
|
||||||
XmpDwcNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.dwc, nsPrefix, rawProps);
|
XmpDwcNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.dwc);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -37,7 +37,7 @@ class XmpDwcNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpIptcCoreNamespace extends XmpNamespace {
|
class XmpIptcCoreNamespace extends XmpNamespace {
|
||||||
XmpIptcCoreNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.iptc4xmpCore, nsPrefix, rawProps);
|
XmpIptcCoreNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.iptc4xmpCore);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -46,7 +46,7 @@ class XmpIptcCoreNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpIptc4xmpExtNamespace extends XmpNamespace {
|
class XmpIptc4xmpExtNamespace extends XmpNamespace {
|
||||||
XmpIptc4xmpExtNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.iptc4xmpExt, nsPrefix, rawProps);
|
XmpIptc4xmpExtNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.iptc4xmpExt);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -55,7 +55,7 @@ class XmpIptc4xmpExtNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpMPNamespace extends XmpNamespace {
|
class XmpMPNamespace extends XmpNamespace {
|
||||||
XmpMPNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.mp, nsPrefix, rawProps);
|
XmpMPNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.mp);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -65,7 +65,7 @@ class XmpMPNamespace extends XmpNamespace {
|
||||||
|
|
||||||
// cf www.metadataworkinggroup.org/pdf/mwg_guidance.pdf (down, as of 2021/02/15)
|
// cf www.metadataworkinggroup.org/pdf/mwg_guidance.pdf (down, as of 2021/02/15)
|
||||||
class XmpMgwRegionsNamespace extends XmpNamespace {
|
class XmpMgwRegionsNamespace extends XmpNamespace {
|
||||||
XmpMgwRegionsNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.mwgrs, nsPrefix, rawProps);
|
XmpMgwRegionsNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.mwgrs);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -75,7 +75,7 @@ class XmpMgwRegionsNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpPlusNamespace extends XmpNamespace {
|
class XmpPlusNamespace extends XmpNamespace {
|
||||||
XmpPlusNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.plus, nsPrefix, rawProps);
|
XmpPlusNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.plus);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
@ -86,7 +86,7 @@ class XmpPlusNamespace extends XmpNamespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmpMMNamespace extends XmpNamespace {
|
class XmpMMNamespace extends XmpNamespace {
|
||||||
XmpMMNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.xmpMM, nsPrefix, rawProps);
|
XmpMMNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.xmpMM);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
||||||
|
|
||||||
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/photoshop.md
|
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/photoshop.md
|
||||||
class XmpPhotoshopNamespace extends XmpNamespace {
|
class XmpPhotoshopNamespace extends XmpNamespace {
|
||||||
XmpPhotoshopNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.photoshop, nsPrefix, rawProps);
|
XmpPhotoshopNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.photoshop);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
||||||
|
|
||||||
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/tiff.md
|
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/tiff.md
|
||||||
class XmpTiffNamespace extends XmpNamespace {
|
class XmpTiffNamespace extends XmpNamespace {
|
||||||
const XmpTiffNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.tiff, nsPrefix, rawProps);
|
XmpTiffNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.tiff);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String formatValue(XmpProp prop) {
|
String formatValue(XmpProp prop) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:aves/widgets/viewer/info/common.dart';
|
||||||
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
|
||||||
|
|
||||||
class XmpBasicNamespace extends XmpNamespace {
|
class XmpBasicNamespace extends XmpNamespace {
|
||||||
XmpBasicNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.xmp, nsPrefix, rawProps);
|
XmpBasicNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.xmp);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
late final List<XmpCardData> cards = [
|
late final List<XmpCardData> cards = [
|
||||||
|
|
|
@ -56,9 +56,8 @@ class _XmpDirTileState extends State<XmpDirTile> {
|
||||||
return nsPrefix;
|
return nsPrefix;
|
||||||
}).entries.map((kv) {
|
}).entries.map((kv) {
|
||||||
final nsPrefix = kv.key;
|
final nsPrefix = kv.key;
|
||||||
final nsUri = _schemaRegistryPrefixes[nsPrefix] ?? '';
|
|
||||||
final rawProps = Map.fromEntries(kv.value);
|
final rawProps = Map.fromEntries(kv.value);
|
||||||
return XmpNamespace.create(nsUri, nsPrefix, rawProps);
|
return XmpNamespace.create(_schemaRegistryPrefixes, nsPrefix, rawProps);
|
||||||
}).toList()
|
}).toList()
|
||||||
..sort((a, b) => compareAsciiUpperCase(a.displayTitle, b.displayTitle));
|
..sort((a, b) => compareAsciiUpperCase(a.displayTitle, b.displayTitle));
|
||||||
return AvesExpansionTile(
|
return AvesExpansionTile(
|
||||||
|
|
|
@ -3,30 +3,27 @@ import 'dart:async';
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/model/actions/entry_actions.dart';
|
import 'package:aves/model/actions/entry_actions.dart';
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/entry_images.dart';
|
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/media_session_service.dart';
|
import 'package:aves/services/media/media_session_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
|
||||||
import 'package:aves/widgets/viewer/controller.dart';
|
import 'package:aves/widgets/viewer/controller.dart';
|
||||||
import 'package:aves/widgets/viewer/hero.dart';
|
import 'package:aves/widgets/viewer/hero.dart';
|
||||||
import 'package:aves/widgets/viewer/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
|
||||||
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/error.dart';
|
import 'package:aves/widgets/viewer/visual/error.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/raster.dart';
|
import 'package:aves/widgets/viewer/visual/raster.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/state.dart';
|
import 'package:aves/widgets/viewer/visual/state.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
|
|
||||||
import 'package:aves/widgets/viewer/visual/vector.dart';
|
import 'package:aves/widgets/viewer/visual/vector.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/video.dart';
|
import 'package:aves/widgets/viewer/visual/video/cover.dart';
|
||||||
|
import 'package:aves/widgets/viewer/visual/video/subtitle/subtitle.dart';
|
||||||
|
import 'package:aves/widgets/viewer/visual/video/swipe_action.dart';
|
||||||
|
import 'package:aves/widgets/viewer/visual/video/video_view.dart';
|
||||||
import 'package:aves_magnifier/aves_magnifier.dart';
|
import 'package:aves_magnifier/aves_magnifier.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:decorated_icon/decorated_icon.dart';
|
import 'package:decorated_icon/decorated_icon.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -55,17 +52,8 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
late ValueNotifier<ViewState> _viewStateNotifier;
|
late ValueNotifier<ViewState> _viewStateNotifier;
|
||||||
late AvesMagnifierController _magnifierController;
|
late AvesMagnifierController _magnifierController;
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
ImageStream? _videoCoverStream;
|
|
||||||
late ImageStreamListener _videoCoverStreamListener;
|
|
||||||
final ValueNotifier<ImageInfo?> _videoCoverInfoNotifier = ValueNotifier(null);
|
|
||||||
final ValueNotifier<Widget?> _actionFeedbackChildNotifier = ValueNotifier(null);
|
final ValueNotifier<Widget?> _actionFeedbackChildNotifier = ValueNotifier(null);
|
||||||
|
OverlayEntry? _actionFeedbackOverlayEntry;
|
||||||
AvesMagnifierController? _dismissedCoverMagnifierController;
|
|
||||||
|
|
||||||
AvesMagnifierController get dismissedCoverMagnifierController {
|
|
||||||
_dismissedCoverMagnifierController ??= AvesMagnifierController();
|
|
||||||
return _dismissedCoverMagnifierController!;
|
|
||||||
}
|
|
||||||
|
|
||||||
AvesEntry get mainEntry => widget.mainEntry;
|
AvesEntry get mainEntry => widget.mainEntry;
|
||||||
|
|
||||||
|
@ -73,9 +61,6 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
|
|
||||||
ViewerController get viewerController => widget.viewerController;
|
ViewerController get viewerController => widget.viewerController;
|
||||||
|
|
||||||
// use the high res photo as cover for the video part of a motion photo
|
|
||||||
ImageProvider get videoCoverUriImage => mainEntry.isMotionPhoto ? mainEntry.uriImage : entry.uriImage;
|
|
||||||
|
|
||||||
static const rasterMaxScale = ScaleLevel(factor: 5);
|
static const rasterMaxScale = ScaleLevel(factor: 5);
|
||||||
static const vectorMaxScale = ScaleLevel(factor: 25);
|
static const vectorMaxScale = ScaleLevel(factor: 25);
|
||||||
|
|
||||||
|
@ -110,9 +95,6 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
_subscriptions.add(_magnifierController.scaleBoundariesStream.listen(_onViewScaleBoundariesChanged));
|
_subscriptions.add(_magnifierController.scaleBoundariesStream.listen(_onViewScaleBoundariesChanged));
|
||||||
if (entry.isVideo) {
|
if (entry.isVideo) {
|
||||||
_subscriptions.add(mediaSessionService.mediaCommands.listen(_onMediaCommand));
|
_subscriptions.add(mediaSessionService.mediaCommands.listen(_onMediaCommand));
|
||||||
_videoCoverStreamListener = ImageStreamListener((image, _) => _videoCoverInfoNotifier.value = image);
|
|
||||||
_videoCoverStream = videoCoverUriImage.resolve(ImageConfiguration.empty);
|
|
||||||
_videoCoverStream!.addListener(_videoCoverStreamListener);
|
|
||||||
}
|
}
|
||||||
viewerController.startAutopilotAnimation(
|
viewerController.startAutopilotAnimation(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
|
@ -127,9 +109,6 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
|
|
||||||
void _unregisterWidget(EntryPageView oldWidget) {
|
void _unregisterWidget(EntryPageView oldWidget) {
|
||||||
viewerController.stopAutopilotAnimation(vsync: this);
|
viewerController.stopAutopilotAnimation(vsync: this);
|
||||||
_videoCoverStream?.removeListener(_videoCoverStreamListener);
|
|
||||||
_videoCoverStream = null;
|
|
||||||
_videoCoverInfoNotifier.value = null;
|
|
||||||
_magnifierController.dispose();
|
_magnifierController.dispose();
|
||||||
_subscriptions
|
_subscriptions
|
||||||
..forEach((sub) => sub.cancel())
|
..forEach((sub) => sub.cancel())
|
||||||
|
@ -222,169 +201,169 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
builder: (context, sar, child) {
|
builder: (context, sar, child) {
|
||||||
final videoDisplaySize = entry.videoDisplaySize(sar);
|
final videoDisplaySize = entry.videoDisplaySize(sar);
|
||||||
|
|
||||||
return Selector<Settings, Tuple2<bool, bool>>(
|
return Selector<Settings, Tuple3<bool, bool, bool>>(
|
||||||
selector: (context, s) => Tuple2(s.videoGestureDoubleTapTogglePlay, s.videoGestureSideDoubleTapSeek),
|
selector: (context, s) => Tuple3(
|
||||||
|
s.videoGestureDoubleTapTogglePlay,
|
||||||
|
s.videoGestureSideDoubleTapSeek,
|
||||||
|
s.videoGestureVerticalDragBrightnessVolume,
|
||||||
|
),
|
||||||
builder: (context, s, child) {
|
builder: (context, s, child) {
|
||||||
final playGesture = s.item1;
|
final playGesture = s.item1;
|
||||||
final seekGesture = s.item2;
|
final seekGesture = s.item2;
|
||||||
final useActionGesture = playGesture || seekGesture;
|
final useVerticalDragGesture = s.item3;
|
||||||
|
final useTapGesture = playGesture || seekGesture;
|
||||||
|
|
||||||
void _applyAction(EntryAction action, {IconData? Function()? icon}) {
|
MagnifierDoubleTapCallback? onDoubleTap;
|
||||||
_actionFeedbackChildNotifier.value = DecoratedIcon(
|
MagnifierGestureScaleStartCallback? onScaleStart;
|
||||||
icon?.call() ?? action.getIconData(),
|
MagnifierGestureScaleUpdateCallback? onScaleUpdate;
|
||||||
size: 48,
|
MagnifierGestureScaleEndCallback? onScaleEnd;
|
||||||
color: Colors.white,
|
|
||||||
shadows: const [
|
if (useTapGesture) {
|
||||||
Shadow(
|
void _applyAction(EntryAction action, {IconData? Function()? icon}) {
|
||||||
color: Colors.black,
|
_actionFeedbackChildNotifier.value = DecoratedIcon(
|
||||||
blurRadius: 4,
|
icon?.call() ?? action.getIconData(),
|
||||||
)
|
size: 48,
|
||||||
],
|
color: Colors.white,
|
||||||
);
|
shadows: const [
|
||||||
VideoActionNotification(
|
Shadow(
|
||||||
controller: videoController,
|
color: Colors.black,
|
||||||
action: action,
|
blurRadius: 4,
|
||||||
).dispatch(context);
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
VideoActionNotification(
|
||||||
|
controller: videoController,
|
||||||
|
action: action,
|
||||||
|
).dispatch(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDoubleTap = (alignment) {
|
||||||
|
final x = alignment.x;
|
||||||
|
if (seekGesture) {
|
||||||
|
if (x < sideRatio) {
|
||||||
|
_applyAction(EntryAction.videoReplay10);
|
||||||
|
return true;
|
||||||
|
} else if (x > 1 - sideRatio) {
|
||||||
|
_applyAction(EntryAction.videoSkip10);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (playGesture) {
|
||||||
|
_applyAction(
|
||||||
|
EntryAction.videoTogglePlay,
|
||||||
|
icon: () => videoController.isPlaying ? AIcons.pause : AIcons.play,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
MagnifierDoubleTapCallback? _onDoubleTap = useActionGesture
|
if (useVerticalDragGesture) {
|
||||||
? (alignment) {
|
SwipeAction? swipeAction;
|
||||||
final x = alignment.x;
|
var move = Offset.zero;
|
||||||
if (seekGesture) {
|
var dropped = false;
|
||||||
if (x < sideRatio) {
|
double? startValue;
|
||||||
_applyAction(EntryAction.videoReplay10);
|
final valueNotifier = ValueNotifier<double?>(null);
|
||||||
return true;
|
|
||||||
} else if (x > 1 - sideRatio) {
|
|
||||||
_applyAction(EntryAction.videoSkip10);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (playGesture) {
|
|
||||||
_applyAction(
|
|
||||||
EntryAction.videoTogglePlay,
|
|
||||||
icon: () => videoController.isPlaying ? AIcons.pause : AIcons.play,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
|
|
||||||
|
onScaleStart = (details, doubleTap, boundaries) {
|
||||||
|
dropped = details.pointerCount > 1 || doubleTap;
|
||||||
|
if (dropped) return;
|
||||||
|
|
||||||
|
startValue = null;
|
||||||
|
valueNotifier.value = null;
|
||||||
|
final alignmentX = details.focalPoint.dx / boundaries.viewportSize.width;
|
||||||
|
final action = alignmentX > .5 ? SwipeAction.volume : SwipeAction.brightness;
|
||||||
|
action.get().then((v) => startValue = v);
|
||||||
|
swipeAction = action;
|
||||||
|
move = Offset.zero;
|
||||||
|
_actionFeedbackOverlayEntry = OverlayEntry(
|
||||||
|
builder: (context) => SwipeActionFeedback(
|
||||||
|
action: action,
|
||||||
|
valueNotifier: valueNotifier,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Overlay.of(context)!.insert(_actionFeedbackOverlayEntry!);
|
||||||
|
};
|
||||||
|
onScaleUpdate = (details) {
|
||||||
|
move += details.focalPointDelta;
|
||||||
|
dropped |= details.pointerCount > 1;
|
||||||
|
if (valueNotifier.value == null) {
|
||||||
|
dropped |= MagnifierGestureRecognizer.isXPan(move);
|
||||||
|
}
|
||||||
|
if (dropped) return false;
|
||||||
|
|
||||||
|
final _startValue = startValue;
|
||||||
|
if (_startValue != null) {
|
||||||
|
final double value = (_startValue - move.dy / SwipeActionFeedback.height).clamp(0, 1);
|
||||||
|
valueNotifier.value = value;
|
||||||
|
swipeAction?.set(value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
onScaleEnd = (details) {
|
||||||
|
if (_actionFeedbackOverlayEntry != null) {
|
||||||
|
_actionFeedbackOverlayEntry!.remove();
|
||||||
|
_actionFeedbackOverlayEntry = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget videoChild = Stack(
|
||||||
|
children: [
|
||||||
|
_buildMagnifier(
|
||||||
|
displaySize: videoDisplaySize,
|
||||||
|
onScaleStart: onScaleStart,
|
||||||
|
onScaleUpdate: onScaleUpdate,
|
||||||
|
onScaleEnd: onScaleEnd,
|
||||||
|
onDoubleTap: onDoubleTap,
|
||||||
|
child: VideoView(
|
||||||
|
entry: entry,
|
||||||
|
controller: videoController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VideoSubtitles(
|
||||||
|
controller: videoController,
|
||||||
|
viewStateNotifier: _viewStateNotifier,
|
||||||
|
),
|
||||||
|
if (useTapGesture)
|
||||||
|
ValueListenableBuilder<Widget?>(
|
||||||
|
valueListenable: _actionFeedbackChildNotifier,
|
||||||
|
builder: (context, feedbackChild, child) => ActionFeedback(
|
||||||
|
child: feedbackChild,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (useVerticalDragGesture) {
|
||||||
|
videoChild = MagnifierGestureDetectorScope.of(context)!.copyWith(
|
||||||
|
acceptPointerEvent: MagnifierGestureRecognizer.isYPan,
|
||||||
|
child: videoChild,
|
||||||
|
);
|
||||||
|
}
|
||||||
return Stack(
|
return Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
Stack(
|
videoChild,
|
||||||
children: [
|
VideoCover(
|
||||||
_buildMagnifier(
|
mainEntry: mainEntry,
|
||||||
displaySize: videoDisplaySize,
|
pageEntry: entry,
|
||||||
onDoubleTap: _onDoubleTap,
|
magnifierController: _magnifierController,
|
||||||
child: VideoView(
|
|
||||||
entry: entry,
|
|
||||||
controller: videoController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
VideoSubtitles(
|
|
||||||
controller: videoController,
|
|
||||||
viewStateNotifier: _viewStateNotifier,
|
|
||||||
),
|
|
||||||
if (settings.videoShowRawTimedText)
|
|
||||||
VideoSubtitles(
|
|
||||||
controller: videoController,
|
|
||||||
viewStateNotifier: _viewStateNotifier,
|
|
||||||
debugMode: true,
|
|
||||||
),
|
|
||||||
if (useActionGesture)
|
|
||||||
ValueListenableBuilder<Widget?>(
|
|
||||||
valueListenable: _actionFeedbackChildNotifier,
|
|
||||||
builder: (context, feedbackChild, child) => ActionFeedback(
|
|
||||||
child: feedbackChild,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
_buildVideoCover(
|
|
||||||
videoController: videoController,
|
videoController: videoController,
|
||||||
videoDisplaySize: videoDisplaySize,
|
videoDisplaySize: videoDisplaySize,
|
||||||
onDoubleTap: _onDoubleTap,
|
onTap: _onTap,
|
||||||
),
|
magnifierBuilder: (coverController, coverSize, videoCoverUriImage) => _buildMagnifier(
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamBuilder<VideoStatus> _buildVideoCover({
|
|
||||||
required AvesVideoController videoController,
|
|
||||||
required Size videoDisplaySize,
|
|
||||||
required MagnifierDoubleTapCallback? onDoubleTap,
|
|
||||||
}) {
|
|
||||||
// fade out image to ease transition with the player
|
|
||||||
return StreamBuilder<VideoStatus>(
|
|
||||||
stream: videoController.statusStream,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
final showCover = !videoController.isReady;
|
|
||||||
return IgnorePointer(
|
|
||||||
ignoring: !showCover,
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: showCover ? 1 : 0,
|
|
||||||
curve: Curves.easeInCirc,
|
|
||||||
duration: Durations.viewerVideoPlayerTransition,
|
|
||||||
onEnd: () {
|
|
||||||
// while cover is fading out, the same controller is used for both the cover and the video,
|
|
||||||
// and both fire scale boundaries events, so we make sure that in the end
|
|
||||||
// the scale boundaries from the video are used after the cover is gone
|
|
||||||
final boundaries = _magnifierController.scaleBoundaries;
|
|
||||||
if (boundaries != null) {
|
|
||||||
_magnifierController.setScaleBoundaries(
|
|
||||||
boundaries.copyWith(
|
|
||||||
childSize: videoDisplaySize,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: ValueListenableBuilder<ImageInfo?>(
|
|
||||||
valueListenable: _videoCoverInfoNotifier,
|
|
||||||
builder: (context, videoCoverInfo, child) {
|
|
||||||
if (videoCoverInfo != null) {
|
|
||||||
// full cover image may have a different size and different aspect ratio
|
|
||||||
final coverSize = Size(
|
|
||||||
videoCoverInfo.image.width.toDouble(),
|
|
||||||
videoCoverInfo.image.height.toDouble(),
|
|
||||||
);
|
|
||||||
// when the cover is the same size as the video itself
|
|
||||||
// (which is often the case when the cover is not embedded but just a frame),
|
|
||||||
// we can reuse the same magnifier and preserve its state when switching from cover to video
|
|
||||||
final coverController = showCover || coverSize == videoDisplaySize ? _magnifierController : dismissedCoverMagnifierController;
|
|
||||||
return _buildMagnifier(
|
|
||||||
controller: coverController,
|
controller: coverController,
|
||||||
displaySize: coverSize,
|
displaySize: coverSize,
|
||||||
onDoubleTap: onDoubleTap,
|
onDoubleTap: onDoubleTap,
|
||||||
child: Image(
|
child: Image(
|
||||||
image: videoCoverUriImage,
|
image: videoCoverUriImage,
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
),
|
||||||
|
],
|
||||||
// default to cached thumbnail, if any
|
);
|
||||||
final extent = entry.cachedThumbnails.firstOrNull?.key.extent;
|
},
|
||||||
if (extent != null && extent > 0) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: _onTap,
|
|
||||||
child: ThumbnailImage(
|
|
||||||
entry: entry,
|
|
||||||
extent: extent,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
showLoadingBackground: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return const SizedBox();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -396,6 +375,9 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
ScaleLevel maxScale = rasterMaxScale,
|
ScaleLevel maxScale = rasterMaxScale,
|
||||||
ScaleStateCycle scaleStateCycle = defaultScaleStateCycle,
|
ScaleStateCycle scaleStateCycle = defaultScaleStateCycle,
|
||||||
bool applyScale = true,
|
bool applyScale = true,
|
||||||
|
MagnifierGestureScaleStartCallback? onScaleStart,
|
||||||
|
MagnifierGestureScaleUpdateCallback? onScaleUpdate,
|
||||||
|
MagnifierGestureScaleEndCallback? onScaleEnd,
|
||||||
MagnifierDoubleTapCallback? onDoubleTap,
|
MagnifierDoubleTapCallback? onDoubleTap,
|
||||||
required Widget child,
|
required Widget child,
|
||||||
}) {
|
}) {
|
||||||
|
@ -413,6 +395,9 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
initialScale: viewerController.initialScale,
|
initialScale: viewerController.initialScale,
|
||||||
scaleStateCycle: scaleStateCycle,
|
scaleStateCycle: scaleStateCycle,
|
||||||
applyScale: applyScale,
|
applyScale: applyScale,
|
||||||
|
onScaleStart: onScaleStart,
|
||||||
|
onScaleUpdate: onScaleUpdate,
|
||||||
|
onScaleEnd: onScaleEnd,
|
||||||
onTap: (c, s, a, p) => _onTap(alignment: a),
|
onTap: (c, s, a, p) => _onTap(alignment: a),
|
||||||
onDoubleTap: onDoubleTap,
|
onDoubleTap: onDoubleTap,
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -487,5 +472,3 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef MagnifierTapCallback = void Function(Offset childPosition);
|
|
||||||
|
|
160
lib/widgets/viewer/visual/video/cover.dart
Normal file
160
lib/widgets/viewer/visual/video/cover.dart
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import 'package:aves/model/entry.dart';
|
||||||
|
import 'package:aves/model/entry_images.dart';
|
||||||
|
import 'package:aves/theme/durations.dart';
|
||||||
|
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||||
|
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||||
|
import 'package:aves_magnifier/aves_magnifier.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class VideoCover extends StatefulWidget {
|
||||||
|
final AvesEntry mainEntry, pageEntry;
|
||||||
|
final AvesMagnifierController magnifierController;
|
||||||
|
final AvesVideoController videoController;
|
||||||
|
final Size videoDisplaySize;
|
||||||
|
final void Function({Alignment? alignment}) onTap;
|
||||||
|
final Widget Function(
|
||||||
|
AvesMagnifierController coverController,
|
||||||
|
Size coverSize,
|
||||||
|
ImageProvider videoCoverUriImage,
|
||||||
|
) magnifierBuilder;
|
||||||
|
|
||||||
|
const VideoCover({
|
||||||
|
super.key,
|
||||||
|
required this.mainEntry,
|
||||||
|
required this.pageEntry,
|
||||||
|
required this.magnifierController,
|
||||||
|
required this.videoController,
|
||||||
|
required this.videoDisplaySize,
|
||||||
|
required this.onTap,
|
||||||
|
required this.magnifierBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VideoCover> createState() => _VideoCoverState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoCoverState extends State<VideoCover> {
|
||||||
|
ImageStream? _videoCoverStream;
|
||||||
|
late ImageStreamListener _videoCoverStreamListener;
|
||||||
|
final ValueNotifier<ImageInfo?> _videoCoverInfoNotifier = ValueNotifier(null);
|
||||||
|
|
||||||
|
AvesMagnifierController? _dismissedCoverMagnifierController;
|
||||||
|
|
||||||
|
AvesMagnifierController get dismissedCoverMagnifierController {
|
||||||
|
_dismissedCoverMagnifierController ??= AvesMagnifierController();
|
||||||
|
return _dismissedCoverMagnifierController!;
|
||||||
|
}
|
||||||
|
|
||||||
|
AvesEntry get mainEntry => widget.mainEntry;
|
||||||
|
|
||||||
|
AvesEntry get entry => widget.pageEntry;
|
||||||
|
|
||||||
|
AvesMagnifierController get magnifierController => widget.magnifierController;
|
||||||
|
|
||||||
|
AvesVideoController get videoController => widget.videoController;
|
||||||
|
|
||||||
|
Size get videoDisplaySize => widget.videoDisplaySize;
|
||||||
|
|
||||||
|
// use the high res photo as cover for the video part of a motion photo
|
||||||
|
ImageProvider get videoCoverUriImage => mainEntry.isMotionPhoto ? mainEntry.uriImage : entry.uriImage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_registerWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant VideoCover oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.pageEntry != widget.pageEntry) {
|
||||||
|
_unregisterWidget(oldWidget);
|
||||||
|
_registerWidget(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_unregisterWidget(widget);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerWidget(VideoCover widget) {
|
||||||
|
_videoCoverStreamListener = ImageStreamListener((image, _) => _videoCoverInfoNotifier.value = image);
|
||||||
|
_videoCoverStream = videoCoverUriImage.resolve(ImageConfiguration.empty);
|
||||||
|
_videoCoverStream!.addListener(_videoCoverStreamListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unregisterWidget(VideoCover oldWidget) {
|
||||||
|
_videoCoverStream?.removeListener(_videoCoverStreamListener);
|
||||||
|
_videoCoverStream = null;
|
||||||
|
_videoCoverInfoNotifier.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// fade out image to ease transition with the player
|
||||||
|
return StreamBuilder<VideoStatus>(
|
||||||
|
stream: videoController.statusStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final showCover = !videoController.isReady;
|
||||||
|
return IgnorePointer(
|
||||||
|
ignoring: !showCover,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: showCover ? 1 : 0,
|
||||||
|
curve: Curves.easeInCirc,
|
||||||
|
duration: Durations.viewerVideoPlayerTransition,
|
||||||
|
onEnd: () {
|
||||||
|
// while cover is fading out, the same controller is used for both the cover and the video,
|
||||||
|
// and both fire scale boundaries events, so we make sure that in the end
|
||||||
|
// the scale boundaries from the video are used after the cover is gone
|
||||||
|
final boundaries = magnifierController.scaleBoundaries;
|
||||||
|
if (boundaries != null) {
|
||||||
|
magnifierController.setScaleBoundaries(
|
||||||
|
boundaries.copyWith(
|
||||||
|
childSize: videoDisplaySize,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: ValueListenableBuilder<ImageInfo?>(
|
||||||
|
valueListenable: _videoCoverInfoNotifier,
|
||||||
|
builder: (context, videoCoverInfo, child) {
|
||||||
|
if (videoCoverInfo != null) {
|
||||||
|
// full cover image may have a different size and different aspect ratio
|
||||||
|
final coverSize = Size(
|
||||||
|
videoCoverInfo.image.width.toDouble(),
|
||||||
|
videoCoverInfo.image.height.toDouble(),
|
||||||
|
);
|
||||||
|
// when the cover is the same size as the video itself
|
||||||
|
// (which is often the case when the cover is not embedded but just a frame),
|
||||||
|
// we can reuse the same magnifier and preserve its state when switching from cover to video
|
||||||
|
final coverController = showCover || coverSize == videoDisplaySize ? magnifierController : dismissedCoverMagnifierController;
|
||||||
|
return widget.magnifierBuilder(coverController, coverSize, videoCoverUriImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to cached thumbnail, if any
|
||||||
|
final extent = entry.cachedThumbnails.firstOrNull?.key.extent;
|
||||||
|
if (extent != null && extent > 0) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: widget.onTap,
|
||||||
|
child: ThumbnailImage(
|
||||||
|
entry: entry,
|
||||||
|
extent: extent,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
showLoadingBackground: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/line.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/line.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/span.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/span.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/style.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/style.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/span.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/span.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/style.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/style.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
|
@ -4,9 +4,9 @@ import 'package:aves/widgets/common/basic/text/background_painter.dart';
|
||||||
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
||||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/state.dart';
|
import 'package:aves/widgets/viewer/visual/state.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/ass_parser.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/ass_parser.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/span.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/span.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/style.dart';
|
import 'package:aves/widgets/viewer/visual/video/subtitle/style.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
138
lib/widgets/viewer/visual/video/swipe_action.dart
Normal file
138
lib/widgets/viewer/visual/video/swipe_action.dart
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:decorated_icon/decorated_icon.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
import 'package:volume_controller/volume_controller.dart';
|
||||||
|
|
||||||
|
enum SwipeAction { brightness, volume }
|
||||||
|
|
||||||
|
extension ExtraSwipeAction on SwipeAction {
|
||||||
|
Future<double> get() {
|
||||||
|
switch (this) {
|
||||||
|
case SwipeAction.brightness:
|
||||||
|
return ScreenBrightness().current;
|
||||||
|
case SwipeAction.volume:
|
||||||
|
return VolumeController().getVolume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> set(double value) async {
|
||||||
|
switch (this) {
|
||||||
|
case SwipeAction.brightness:
|
||||||
|
await ScreenBrightness().setScreenBrightness(value);
|
||||||
|
break;
|
||||||
|
case SwipeAction.volume:
|
||||||
|
VolumeController().setVolume(value, showSystemUI: false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwipeActionFeedback extends StatelessWidget {
|
||||||
|
final SwipeAction action;
|
||||||
|
final ValueNotifier<double?> valueNotifier;
|
||||||
|
|
||||||
|
const SwipeActionFeedback({
|
||||||
|
super.key,
|
||||||
|
required this.action,
|
||||||
|
required this.valueNotifier,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const double width = 32;
|
||||||
|
static const double height = 160;
|
||||||
|
static const Radius radius = Radius.circular(width / 2);
|
||||||
|
static const double borderWidth = 2;
|
||||||
|
static const Color borderColor = Colors.white;
|
||||||
|
static final Color fillColor = Colors.white.withOpacity(.8);
|
||||||
|
static final Color backgroundColor = Colors.black.withOpacity(.2);
|
||||||
|
static final Color innerBorderColor = Colors.black.withOpacity(.5);
|
||||||
|
static const Color iconColor = Colors.white;
|
||||||
|
static const Color shadowColor = Colors.black;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: ValueListenableBuilder<double?>(
|
||||||
|
valueListenable: valueNotifier,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
if (value == null) return const SizedBox();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildIcon(_getMaxIcon()),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
|
border: Border.all(
|
||||||
|
width: borderWidth * 2,
|
||||||
|
color: innerBorderColor,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(radius),
|
||||||
|
),
|
||||||
|
foregroundDecoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: borderColor,
|
||||||
|
width: borderWidth,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(radius),
|
||||||
|
),
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(radius),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Container(
|
||||||
|
color: fillColor,
|
||||||
|
width: width,
|
||||||
|
height: height * value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildIcon(_getMinIcon()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIcon(IconData icon) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: DecoratedIcon(
|
||||||
|
icon,
|
||||||
|
size: width,
|
||||||
|
color: iconColor,
|
||||||
|
shadows: const [
|
||||||
|
Shadow(
|
||||||
|
color: shadowColor,
|
||||||
|
blurRadius: 4,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData _getMinIcon() {
|
||||||
|
switch (action) {
|
||||||
|
case SwipeAction.brightness:
|
||||||
|
return AIcons.brightnessMin;
|
||||||
|
case SwipeAction.volume:
|
||||||
|
return AIcons.volumeMin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData _getMaxIcon() {
|
||||||
|
switch (action) {
|
||||||
|
case SwipeAction.brightness:
|
||||||
|
return AIcons.brightnessMax;
|
||||||
|
case SwipeAction.volume:
|
||||||
|
return AIcons.volumeMax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ library aves_magnifier;
|
||||||
|
|
||||||
export 'src/controller/controller.dart';
|
export 'src/controller/controller.dart';
|
||||||
export 'src/controller/state.dart';
|
export 'src/controller/state.dart';
|
||||||
|
export 'src/core/scale_gesture_recognizer.dart';
|
||||||
export 'src/magnifier.dart';
|
export 'src/magnifier.dart';
|
||||||
export 'src/pan/gesture_detector_scope.dart';
|
export 'src/pan/gesture_detector_scope.dart';
|
||||||
export 'src/pan/scroll_physics.dart';
|
export 'src/pan/scroll_physics.dart';
|
||||||
|
|
|
@ -18,6 +18,9 @@ class MagnifierCore extends StatefulWidget {
|
||||||
final ScaleStateCycle scaleStateCycle;
|
final ScaleStateCycle scaleStateCycle;
|
||||||
final bool applyScale;
|
final bool applyScale;
|
||||||
final double panInertia;
|
final double panInertia;
|
||||||
|
final MagnifierGestureScaleStartCallback? onScaleStart;
|
||||||
|
final MagnifierGestureScaleUpdateCallback? onScaleUpdate;
|
||||||
|
final MagnifierGestureScaleEndCallback? onScaleEnd;
|
||||||
final MagnifierTapCallback? onTap;
|
final MagnifierTapCallback? onTap;
|
||||||
final MagnifierDoubleTapCallback? onDoubleTap;
|
final MagnifierDoubleTapCallback? onDoubleTap;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
@ -28,6 +31,9 @@ class MagnifierCore extends StatefulWidget {
|
||||||
required this.scaleStateCycle,
|
required this.scaleStateCycle,
|
||||||
required this.applyScale,
|
required this.applyScale,
|
||||||
this.panInertia = .2,
|
this.panInertia = .2,
|
||||||
|
this.onScaleStart,
|
||||||
|
this.onScaleUpdate,
|
||||||
|
this.onScaleEnd,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onDoubleTap,
|
this.onDoubleTap,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
@ -40,7 +46,7 @@ class MagnifierCore extends StatefulWidget {
|
||||||
class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMixin, AvesMagnifierControllerDelegate, CornerHitDetector {
|
class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMixin, AvesMagnifierControllerDelegate, CornerHitDetector {
|
||||||
Offset? _startFocalPoint, _lastViewportFocalPosition;
|
Offset? _startFocalPoint, _lastViewportFocalPosition;
|
||||||
double? _startScale, _quickScaleLastY, _quickScaleLastDistance;
|
double? _startScale, _quickScaleLastY, _quickScaleLastDistance;
|
||||||
late bool _doubleTap, _quickScaleMoved;
|
late bool _dropped, _doubleTap, _quickScaleMoved;
|
||||||
DateTime _lastScaleGestureDate = DateTime.now();
|
DateTime _lastScaleGestureDate = DateTime.now();
|
||||||
|
|
||||||
late AnimationController _scaleAnimationController;
|
late AnimationController _scaleAnimationController;
|
||||||
|
@ -99,9 +105,15 @@ class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateM
|
||||||
}
|
}
|
||||||
|
|
||||||
void onScaleStart(ScaleStartDetails details, bool doubleTap) {
|
void onScaleStart(ScaleStartDetails details, bool doubleTap) {
|
||||||
|
final boundaries = scaleBoundaries;
|
||||||
|
if (boundaries == null) return;
|
||||||
|
|
||||||
|
widget.onScaleStart?.call(details, doubleTap, boundaries);
|
||||||
|
|
||||||
_startScale = scale;
|
_startScale = scale;
|
||||||
_startFocalPoint = details.localFocalPoint;
|
_startFocalPoint = details.localFocalPoint;
|
||||||
_lastViewportFocalPosition = _startFocalPoint;
|
_lastViewportFocalPosition = _startFocalPoint;
|
||||||
|
_dropped = false;
|
||||||
_doubleTap = doubleTap;
|
_doubleTap = doubleTap;
|
||||||
_quickScaleLastDistance = null;
|
_quickScaleLastDistance = null;
|
||||||
_quickScaleLastY = _startFocalPoint!.dy;
|
_quickScaleLastY = _startFocalPoint!.dy;
|
||||||
|
@ -115,6 +127,9 @@ class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateM
|
||||||
final boundaries = scaleBoundaries;
|
final boundaries = scaleBoundaries;
|
||||||
if (boundaries == null) return;
|
if (boundaries == null) return;
|
||||||
|
|
||||||
|
_dropped |= widget.onScaleUpdate?.call(details) ?? false;
|
||||||
|
if (_dropped) return;
|
||||||
|
|
||||||
double newScale;
|
double newScale;
|
||||||
if (_doubleTap) {
|
if (_doubleTap) {
|
||||||
// quick scale, aka one finger zoom
|
// quick scale, aka one finger zoom
|
||||||
|
@ -151,6 +166,8 @@ class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateM
|
||||||
final boundaries = scaleBoundaries;
|
final boundaries = scaleBoundaries;
|
||||||
if (boundaries == null) return;
|
if (boundaries == null) return;
|
||||||
|
|
||||||
|
widget.onScaleEnd?.call(details);
|
||||||
|
|
||||||
final _position = controller.position;
|
final _position = controller.position;
|
||||||
final _scale = controller.scale!;
|
final _scale = controller.scale!;
|
||||||
final maxScale = boundaries.maxScale;
|
final maxScale = boundaries.maxScale;
|
||||||
|
@ -228,7 +245,7 @@ class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateM
|
||||||
if (onDoubleTap != null) {
|
if (onDoubleTap != null) {
|
||||||
final viewportSize = boundaries.viewportSize;
|
final viewportSize = boundaries.viewportSize;
|
||||||
final alignment = Alignment(viewportTapPosition.dx / viewportSize.width, viewportTapPosition.dy / viewportSize.height);
|
final alignment = Alignment(viewportTapPosition.dx / viewportSize.width, viewportTapPosition.dy / viewportSize.height);
|
||||||
if (onDoubleTap.call(alignment) == true) return;
|
if (onDoubleTap(alignment) == true) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final childTapPosition = boundaries.viewportToChildPosition(controller, viewportTapPosition);
|
final childTapPosition = boundaries.viewportToChildPosition(controller, viewportTapPosition);
|
||||||
|
@ -307,12 +324,12 @@ class _MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateM
|
||||||
);
|
);
|
||||||
|
|
||||||
return MagnifierGestureDetector(
|
return MagnifierGestureDetector(
|
||||||
onDoubleTap: onDoubleTap,
|
hitDetector: this,
|
||||||
onScaleStart: onScaleStart,
|
onScaleStart: onScaleStart,
|
||||||
onScaleUpdate: onScaleUpdate,
|
onScaleUpdate: onScaleUpdate,
|
||||||
onScaleEnd: onScaleEnd,
|
onScaleEnd: onScaleEnd,
|
||||||
hitDetector: this,
|
|
||||||
onTapUp: widget.onTap == null ? null : onTap,
|
onTapUp: widget.onTap == null ? null : onTap,
|
||||||
|
onDoubleTap: onDoubleTap,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -60,8 +60,7 @@ class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
||||||
() => MagnifierGestureRecognizer(
|
() => MagnifierGestureRecognizer(
|
||||||
debugOwner: this,
|
debugOwner: this,
|
||||||
hitDetector: widget.hitDetector,
|
hitDetector: widget.hitDetector,
|
||||||
validateAxis: scope.axis,
|
scope: scope,
|
||||||
touchSlopFactor: scope.touchSlopFactor,
|
|
||||||
doubleTapDetails: doubleTapDetails,
|
doubleTapDetails: doubleTapDetails,
|
||||||
),
|
),
|
||||||
(instance) {
|
(instance) {
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:aves_magnifier/aves_magnifier.dart';
|
||||||
import 'package:aves_magnifier/src/pan/corner_hit_detector.dart';
|
import 'package:aves_magnifier/src/pan/corner_hit_detector.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
final CornerHitDetector hitDetector;
|
final CornerHitDetector hitDetector;
|
||||||
final List<Axis> validateAxis;
|
final MagnifierGestureDetectorScope scope;
|
||||||
final double touchSlopFactor;
|
|
||||||
final ValueNotifier<TapDownDetails?> doubleTapDetails;
|
final ValueNotifier<TapDownDetails?> doubleTapDetails;
|
||||||
|
|
||||||
MagnifierGestureRecognizer({
|
MagnifierGestureRecognizer({
|
||||||
super.debugOwner,
|
super.debugOwner,
|
||||||
required this.hitDetector,
|
required this.hitDetector,
|
||||||
required this.validateAxis,
|
required this.scope,
|
||||||
this.touchSlopFactor = 2,
|
|
||||||
required this.doubleTapDetails,
|
required this.doubleTapDetails,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleEvent(PointerEvent event) {
|
void handleEvent(PointerEvent event) {
|
||||||
if (validateAxis.isNotEmpty) {
|
if (scope.axis.isNotEmpty) {
|
||||||
var didChangeConfiguration = false;
|
var didChangeConfiguration = false;
|
||||||
if (event is PointerMoveEvent) {
|
if (event is PointerMoveEvent) {
|
||||||
if (!event.synthesized) {
|
if (!event.synthesized) {
|
||||||
|
@ -104,26 +103,27 @@ class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final validateAxis = scope.axis;
|
||||||
final move = _initialFocalPoint! - _currentFocalPoint!;
|
final move = _initialFocalPoint! - _currentFocalPoint!;
|
||||||
var shouldMove = false;
|
bool shouldMove = scope.acceptPointerEvent?.call(move) ?? false;
|
||||||
if (validateAxis.length == 2) {
|
|
||||||
// the image is the descendant of gesture detector(s) handling drag in both directions
|
if (!shouldMove) {
|
||||||
final shouldMoveX = validateAxis.contains(Axis.horizontal) && hitDetector.shouldMoveX(move);
|
if (validateAxis.length == 2) {
|
||||||
final shouldMoveY = validateAxis.contains(Axis.vertical) && hitDetector.shouldMoveY(move);
|
// the image is the descendant of gesture detector(s) handling drag in both directions
|
||||||
if (shouldMoveX == shouldMoveY) {
|
final shouldMoveX = validateAxis.contains(Axis.horizontal) && hitDetector.shouldMoveX(move);
|
||||||
// consistently can/cannot pan the image in both direction the same way
|
final shouldMoveY = validateAxis.contains(Axis.vertical) && hitDetector.shouldMoveY(move);
|
||||||
shouldMove = shouldMoveX;
|
if (shouldMoveX == shouldMoveY) {
|
||||||
|
// consistently can/cannot pan the image in both direction the same way
|
||||||
|
shouldMove = shouldMoveX;
|
||||||
|
} else {
|
||||||
|
// can pan the image in one direction, but should yield to an ascendant gesture detector in the other one
|
||||||
|
// the gesture direction angle is in ]-pi, pi], cf `Offset` doc for details
|
||||||
|
shouldMove = (isXPan(move) && shouldMoveX) || (isYPan(move) && shouldMoveY);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// can pan the image in one direction, but should yield to an ascendant gesture detector in the other one
|
// the image is the descendant of a gesture detector handling drag in one direction
|
||||||
final d = move.direction;
|
shouldMove = validateAxis.contains(Axis.vertical) ? hitDetector.shouldMoveY(move) : hitDetector.shouldMoveX(move);
|
||||||
// the gesture direction angle is in ]-pi, pi], cf `Offset` doc for details
|
|
||||||
final xPan = (-pi / 4 < d && d < pi / 4) || (3 / 4 * pi < d && d <= pi) || (-pi < d && d < -3 / 4 * pi);
|
|
||||||
final yPan = (pi / 4 < d && d < 3 / 4 * pi) || (-3 / 4 * pi < d && d < -pi / 4);
|
|
||||||
shouldMove = (xPan && shouldMoveX) || (yPan && shouldMoveY);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// the image is the descendant of a gesture detector handling drag in one direction
|
|
||||||
shouldMove = validateAxis.contains(Axis.vertical) ? hitDetector.shouldMoveY(move) : hitDetector.shouldMoveX(move);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final doubleTap = doubleTapDetails.value != null;
|
final doubleTap = doubleTapDetails.value != null;
|
||||||
|
@ -137,9 +137,19 @@ class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
// and the magnifier recognizer may compete with the `HorizontalDragGestureRecognizer` from a containing `PageView`
|
// and the magnifier recognizer may compete with the `HorizontalDragGestureRecognizer` from a containing `PageView`
|
||||||
// setting `touchSlopFactor` to 2 restores default `ScaleGestureRecognizer` behaviour as `kPanSlop = kTouchSlop * 2.0`
|
// setting `touchSlopFactor` to 2 restores default `ScaleGestureRecognizer` behaviour as `kPanSlop = kTouchSlop * 2.0`
|
||||||
// setting `touchSlopFactor` in [0, 1] will allow this recognizer to accept the gesture before the one from `PageView`
|
// setting `touchSlopFactor` in [0, 1] will allow this recognizer to accept the gesture before the one from `PageView`
|
||||||
if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computeHitSlop(pointerDeviceKind, gestureSettings) * touchSlopFactor) {
|
if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computeHitSlop(pointerDeviceKind, gestureSettings) * scope.touchSlopFactor) {
|
||||||
acceptGesture(event.pointer);
|
acceptGesture(event.pointer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isXPan(Offset move) {
|
||||||
|
final d = move.direction;
|
||||||
|
return (-pi / 4 < d && d < pi / 4) || (3 / 4 * pi < d && d <= pi) || (-pi < d && d < -3 / 4 * pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isYPan(Offset move) {
|
||||||
|
final d = move.direction;
|
||||||
|
return (pi / 4 < d && d < 3 / 4 * pi) || (-3 / 4 * pi < d && d < -pi / 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,9 @@ class AvesMagnifier extends StatelessWidget {
|
||||||
this.initialScale = const ScaleLevel(ref: ScaleReference.contained),
|
this.initialScale = const ScaleLevel(ref: ScaleReference.contained),
|
||||||
this.scaleStateCycle = defaultScaleStateCycle,
|
this.scaleStateCycle = defaultScaleStateCycle,
|
||||||
this.applyScale = true,
|
this.applyScale = true,
|
||||||
|
this.onScaleStart,
|
||||||
|
this.onScaleUpdate,
|
||||||
|
this.onScaleEnd,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onDoubleTap,
|
this.onDoubleTap,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
@ -52,6 +55,9 @@ class AvesMagnifier extends StatelessWidget {
|
||||||
|
|
||||||
final ScaleStateCycle scaleStateCycle;
|
final ScaleStateCycle scaleStateCycle;
|
||||||
final bool applyScale;
|
final bool applyScale;
|
||||||
|
final MagnifierGestureScaleStartCallback? onScaleStart;
|
||||||
|
final MagnifierGestureScaleUpdateCallback? onScaleUpdate;
|
||||||
|
final MagnifierGestureScaleEndCallback? onScaleEnd;
|
||||||
final MagnifierTapCallback? onTap;
|
final MagnifierTapCallback? onTap;
|
||||||
final MagnifierDoubleTapCallback? onDoubleTap;
|
final MagnifierDoubleTapCallback? onDoubleTap;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
@ -73,6 +79,9 @@ class AvesMagnifier extends StatelessWidget {
|
||||||
controller: controller,
|
controller: controller,
|
||||||
scaleStateCycle: scaleStateCycle,
|
scaleStateCycle: scaleStateCycle,
|
||||||
applyScale: applyScale,
|
applyScale: applyScale,
|
||||||
|
onScaleStart: onScaleStart,
|
||||||
|
onScaleUpdate: onScaleUpdate,
|
||||||
|
onScaleEnd: onScaleEnd,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onDoubleTap: onDoubleTap,
|
onDoubleTap: onDoubleTap,
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -88,7 +97,7 @@ typedef MagnifierTapCallback = Function(
|
||||||
Alignment alignment,
|
Alignment alignment,
|
||||||
Offset childTapPosition,
|
Offset childTapPosition,
|
||||||
);
|
);
|
||||||
|
typedef MagnifierDoubleTapCallback = bool Function(Alignment alignment);
|
||||||
typedef MagnifierDoubleTapCallback = bool Function(
|
typedef MagnifierGestureScaleStartCallback = void Function(ScaleStartDetails details, bool doubleTap, ScaleBoundaries boundaries);
|
||||||
Alignment alignment,
|
typedef MagnifierGestureScaleUpdateCallback = bool Function(ScaleUpdateDetails details);
|
||||||
);
|
typedef MagnifierGestureScaleEndCallback = void Function(ScaleEndDetails details);
|
||||||
|
|
|
@ -7,18 +7,6 @@ import 'package:flutter/widgets.dart';
|
||||||
/// Useful when placing Magnifier inside a gesture sensitive context,
|
/// Useful when placing Magnifier inside a gesture sensitive context,
|
||||||
/// such as [PageView], [Dismissible], [BottomSheet].
|
/// such as [PageView], [Dismissible], [BottomSheet].
|
||||||
class MagnifierGestureDetectorScope extends InheritedWidget {
|
class MagnifierGestureDetectorScope extends InheritedWidget {
|
||||||
const MagnifierGestureDetectorScope({
|
|
||||||
super.key,
|
|
||||||
required this.axis,
|
|
||||||
this.touchSlopFactor = .8,
|
|
||||||
required Widget child,
|
|
||||||
}) : super(child: child);
|
|
||||||
|
|
||||||
static MagnifierGestureDetectorScope? of(BuildContext context) {
|
|
||||||
final scope = context.dependOnInheritedWidgetOfExactType<MagnifierGestureDetectorScope>();
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Axis> axis;
|
final List<Axis> axis;
|
||||||
|
|
||||||
// in [0, 1[
|
// in [0, 1[
|
||||||
|
@ -26,9 +14,36 @@ class MagnifierGestureDetectorScope extends InheritedWidget {
|
||||||
// <1: less reactive but gives the most leeway to other recognizers
|
// <1: less reactive but gives the most leeway to other recognizers
|
||||||
// 1: will not be able to compete with a `HorizontalDragGestureRecognizer` up the widget tree
|
// 1: will not be able to compete with a `HorizontalDragGestureRecognizer` up the widget tree
|
||||||
final double touchSlopFactor;
|
final double touchSlopFactor;
|
||||||
|
final bool? Function(Offset move)? acceptPointerEvent;
|
||||||
|
|
||||||
|
const MagnifierGestureDetectorScope({
|
||||||
|
super.key,
|
||||||
|
required this.axis,
|
||||||
|
this.touchSlopFactor = .8,
|
||||||
|
this.acceptPointerEvent,
|
||||||
|
required Widget child,
|
||||||
|
}) : super(child: child);
|
||||||
|
|
||||||
|
static MagnifierGestureDetectorScope? of(BuildContext context) {
|
||||||
|
return context.dependOnInheritedWidgetOfExactType<MagnifierGestureDetectorScope>();
|
||||||
|
}
|
||||||
|
|
||||||
|
MagnifierGestureDetectorScope copyWith({
|
||||||
|
List<Axis>? axis,
|
||||||
|
double? touchSlopFactor,
|
||||||
|
bool? Function(Offset move)? acceptPointerEvent,
|
||||||
|
required Widget child,
|
||||||
|
}) {
|
||||||
|
return MagnifierGestureDetectorScope(
|
||||||
|
axis: axis ?? this.axis,
|
||||||
|
touchSlopFactor: touchSlopFactor ?? this.touchSlopFactor,
|
||||||
|
acceptPointerEvent: acceptPointerEvent ?? this.acceptPointerEvent,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(MagnifierGestureDetectorScope oldWidget) {
|
bool updateShouldNotify(MagnifierGestureDetectorScope oldWidget) {
|
||||||
return axis != oldWidget.axis && touchSlopFactor != oldWidget.touchSlopFactor;
|
return axis != oldWidget.axis || touchSlopFactor != oldWidget.touchSlopFactor || acceptPointerEvent != oldWidget.acceptPointerEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1196,6 +1196,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "9.0.0"
|
||||||
|
volume_controller:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: volume_controller
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.6"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -7,7 +7,7 @@ repository: https://github.com/deckerst/aves
|
||||||
# - play changelog: /whatsnew/whatsnew-en-US
|
# - play changelog: /whatsnew/whatsnew-en-US
|
||||||
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XX01.txt
|
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XX01.txt
|
||||||
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XX.txt
|
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XX.txt
|
||||||
version: 1.7.9+89
|
version: 1.7.10+90
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@ -95,6 +95,7 @@ dependencies:
|
||||||
transparent_image:
|
transparent_image:
|
||||||
tuple:
|
tuple:
|
||||||
url_launcher:
|
url_launcher:
|
||||||
|
volume_controller:
|
||||||
xml:
|
xml:
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
@ -475,6 +475,7 @@
|
||||||
"settingsVideoButtonsTile",
|
"settingsVideoButtonsTile",
|
||||||
"settingsVideoGestureDoubleTapTogglePlay",
|
"settingsVideoGestureDoubleTapTogglePlay",
|
||||||
"settingsVideoGestureSideDoubleTapSeek",
|
"settingsVideoGestureSideDoubleTapSeek",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsPrivacySectionTitle",
|
"settingsPrivacySectionTitle",
|
||||||
"settingsAllowInstalledAppAccess",
|
"settingsAllowInstalledAppAccess",
|
||||||
"settingsAllowInstalledAppAccessSubtitle",
|
"settingsAllowInstalledAppAccessSubtitle",
|
||||||
|
@ -576,6 +577,7 @@
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
"settingsModificationWarningDialogMessage",
|
"settingsModificationWarningDialogMessage",
|
||||||
"settingsViewerShowDescription",
|
"settingsViewerShowDescription",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsDisplayUseTvInterface"
|
"settingsDisplayUseTvInterface"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -586,16 +588,14 @@
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
"settingsModificationWarningDialogMessage",
|
"settingsModificationWarningDialogMessage",
|
||||||
"settingsViewerShowDescription",
|
"settingsViewerShowDescription",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
"settingsDisplayUseTvInterface"
|
"settingsDisplayUseTvInterface"
|
||||||
],
|
],
|
||||||
|
|
||||||
"el": [
|
"el": [
|
||||||
"tooManyItemsErrorDialogMessage"
|
"tooManyItemsErrorDialogMessage",
|
||||||
],
|
"settingsVideoGestureVerticalDragBrightnessVolume"
|
||||||
|
|
||||||
"es": [
|
|
||||||
"tooManyItemsErrorDialogMessage"
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
|
@ -933,6 +933,7 @@
|
||||||
"settingsVideoButtonsTile",
|
"settingsVideoButtonsTile",
|
||||||
"settingsVideoGestureDoubleTapTogglePlay",
|
"settingsVideoGestureDoubleTapTogglePlay",
|
||||||
"settingsVideoGestureSideDoubleTapSeek",
|
"settingsVideoGestureSideDoubleTapSeek",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsPrivacySectionTitle",
|
"settingsPrivacySectionTitle",
|
||||||
"settingsAllowInstalledAppAccess",
|
"settingsAllowInstalledAppAccess",
|
||||||
"settingsAllowInstalledAppAccessSubtitle",
|
"settingsAllowInstalledAppAccessSubtitle",
|
||||||
|
@ -1404,6 +1405,7 @@
|
||||||
"settingsVideoButtonsTile",
|
"settingsVideoButtonsTile",
|
||||||
"settingsVideoGestureDoubleTapTogglePlay",
|
"settingsVideoGestureDoubleTapTogglePlay",
|
||||||
"settingsVideoGestureSideDoubleTapSeek",
|
"settingsVideoGestureSideDoubleTapSeek",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsPrivacySectionTitle",
|
"settingsPrivacySectionTitle",
|
||||||
"settingsAllowInstalledAppAccess",
|
"settingsAllowInstalledAppAccess",
|
||||||
"settingsAllowInstalledAppAccessSubtitle",
|
"settingsAllowInstalledAppAccessSubtitle",
|
||||||
|
@ -1512,6 +1514,613 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"he": [
|
||||||
|
"itemCount",
|
||||||
|
"columnCount",
|
||||||
|
"timeSeconds",
|
||||||
|
"timeMinutes",
|
||||||
|
"timeDays",
|
||||||
|
"focalLength",
|
||||||
|
"applyButtonLabel",
|
||||||
|
"deleteButtonLabel",
|
||||||
|
"nextButtonLabel",
|
||||||
|
"showButtonLabel",
|
||||||
|
"hideButtonLabel",
|
||||||
|
"continueButtonLabel",
|
||||||
|
"cancelTooltip",
|
||||||
|
"changeTooltip",
|
||||||
|
"clearTooltip",
|
||||||
|
"previousTooltip",
|
||||||
|
"nextTooltip",
|
||||||
|
"showTooltip",
|
||||||
|
"hideTooltip",
|
||||||
|
"actionRemove",
|
||||||
|
"resetTooltip",
|
||||||
|
"saveTooltip",
|
||||||
|
"pickTooltip",
|
||||||
|
"doubleBackExitMessage",
|
||||||
|
"doNotAskAgain",
|
||||||
|
"sourceStateLoading",
|
||||||
|
"sourceStateCataloguing",
|
||||||
|
"sourceStateLocatingCountries",
|
||||||
|
"sourceStateLocatingPlaces",
|
||||||
|
"chipActionDelete",
|
||||||
|
"chipActionGoToAlbumPage",
|
||||||
|
"chipActionGoToCountryPage",
|
||||||
|
"chipActionGoToTagPage",
|
||||||
|
"chipActionFilterOut",
|
||||||
|
"chipActionFilterIn",
|
||||||
|
"chipActionHide",
|
||||||
|
"chipActionPin",
|
||||||
|
"chipActionUnpin",
|
||||||
|
"chipActionRename",
|
||||||
|
"chipActionSetCover",
|
||||||
|
"chipActionCreateAlbum",
|
||||||
|
"entryActionCopyToClipboard",
|
||||||
|
"entryActionDelete",
|
||||||
|
"entryActionConvert",
|
||||||
|
"entryActionExport",
|
||||||
|
"entryActionInfo",
|
||||||
|
"entryActionRename",
|
||||||
|
"entryActionRestore",
|
||||||
|
"entryActionRotateCCW",
|
||||||
|
"entryActionRotateCW",
|
||||||
|
"entryActionFlip",
|
||||||
|
"entryActionPrint",
|
||||||
|
"entryActionShare",
|
||||||
|
"entryActionShareImageOnly",
|
||||||
|
"entryActionShareVideoOnly",
|
||||||
|
"entryActionViewSource",
|
||||||
|
"entryActionShowGeoTiffOnMap",
|
||||||
|
"entryActionConvertMotionPhotoToStillImage",
|
||||||
|
"entryActionViewMotionPhotoVideo",
|
||||||
|
"entryActionEdit",
|
||||||
|
"entryActionOpen",
|
||||||
|
"entryActionSetAs",
|
||||||
|
"entryActionOpenMap",
|
||||||
|
"entryActionRotateScreen",
|
||||||
|
"entryActionAddFavourite",
|
||||||
|
"entryActionRemoveFavourite",
|
||||||
|
"videoActionCaptureFrame",
|
||||||
|
"videoActionMute",
|
||||||
|
"videoActionUnmute",
|
||||||
|
"videoActionPause",
|
||||||
|
"videoActionPlay",
|
||||||
|
"videoActionReplay10",
|
||||||
|
"videoActionSkip10",
|
||||||
|
"videoActionSelectStreams",
|
||||||
|
"videoActionSetSpeed",
|
||||||
|
"videoActionSettings",
|
||||||
|
"slideshowActionResume",
|
||||||
|
"slideshowActionShowInCollection",
|
||||||
|
"entryInfoActionEditDate",
|
||||||
|
"entryInfoActionEditLocation",
|
||||||
|
"entryInfoActionEditTitleDescription",
|
||||||
|
"entryInfoActionEditRating",
|
||||||
|
"entryInfoActionEditTags",
|
||||||
|
"entryInfoActionRemoveMetadata",
|
||||||
|
"entryInfoActionExportMetadata",
|
||||||
|
"entryInfoActionRemoveLocation",
|
||||||
|
"filterAspectRatioLandscapeLabel",
|
||||||
|
"filterAspectRatioPortraitLabel",
|
||||||
|
"filterBinLabel",
|
||||||
|
"filterFavouriteLabel",
|
||||||
|
"filterNoDateLabel",
|
||||||
|
"filterNoAddressLabel",
|
||||||
|
"filterLocatedLabel",
|
||||||
|
"filterNoLocationLabel",
|
||||||
|
"filterNoRatingLabel",
|
||||||
|
"filterTaggedLabel",
|
||||||
|
"filterNoTagLabel",
|
||||||
|
"filterNoTitleLabel",
|
||||||
|
"filterOnThisDayLabel",
|
||||||
|
"filterRecentlyAddedLabel",
|
||||||
|
"filterRatingRejectedLabel",
|
||||||
|
"filterTypeAnimatedLabel",
|
||||||
|
"filterTypeMotionPhotoLabel",
|
||||||
|
"filterTypePanoramaLabel",
|
||||||
|
"filterTypeRawLabel",
|
||||||
|
"filterTypeSphericalVideoLabel",
|
||||||
|
"filterTypeGeotiffLabel",
|
||||||
|
"filterMimeImageLabel",
|
||||||
|
"filterMimeVideoLabel",
|
||||||
|
"coordinateFormatDms",
|
||||||
|
"coordinateFormatDecimal",
|
||||||
|
"coordinateDms",
|
||||||
|
"coordinateDmsNorth",
|
||||||
|
"coordinateDmsSouth",
|
||||||
|
"coordinateDmsEast",
|
||||||
|
"coordinateDmsWest",
|
||||||
|
"unitSystemMetric",
|
||||||
|
"unitSystemImperial",
|
||||||
|
"videoLoopModeNever",
|
||||||
|
"videoLoopModeShortOnly",
|
||||||
|
"videoLoopModeAlways",
|
||||||
|
"videoControlsPlay",
|
||||||
|
"videoControlsPlaySeek",
|
||||||
|
"videoControlsPlayOutside",
|
||||||
|
"videoControlsNone",
|
||||||
|
"mapStyleGoogleNormal",
|
||||||
|
"mapStyleGoogleHybrid",
|
||||||
|
"mapStyleGoogleTerrain",
|
||||||
|
"mapStyleHuaweiNormal",
|
||||||
|
"mapStyleHuaweiTerrain",
|
||||||
|
"mapStyleOsmHot",
|
||||||
|
"mapStyleStamenToner",
|
||||||
|
"mapStyleStamenWatercolor",
|
||||||
|
"nameConflictStrategyRename",
|
||||||
|
"nameConflictStrategyReplace",
|
||||||
|
"nameConflictStrategySkip",
|
||||||
|
"keepScreenOnNever",
|
||||||
|
"keepScreenOnVideoPlayback",
|
||||||
|
"keepScreenOnViewerOnly",
|
||||||
|
"keepScreenOnAlways",
|
||||||
|
"accessibilityAnimationsRemove",
|
||||||
|
"accessibilityAnimationsKeep",
|
||||||
|
"displayRefreshRatePreferHighest",
|
||||||
|
"displayRefreshRatePreferLowest",
|
||||||
|
"subtitlePositionTop",
|
||||||
|
"subtitlePositionBottom",
|
||||||
|
"videoPlaybackSkip",
|
||||||
|
"videoPlaybackMuted",
|
||||||
|
"videoPlaybackWithSound",
|
||||||
|
"themeBrightnessLight",
|
||||||
|
"themeBrightnessDark",
|
||||||
|
"themeBrightnessBlack",
|
||||||
|
"viewerTransitionSlide",
|
||||||
|
"viewerTransitionParallax",
|
||||||
|
"viewerTransitionFade",
|
||||||
|
"viewerTransitionZoomIn",
|
||||||
|
"viewerTransitionNone",
|
||||||
|
"wallpaperTargetHome",
|
||||||
|
"wallpaperTargetLock",
|
||||||
|
"wallpaperTargetHomeLock",
|
||||||
|
"widgetDisplayedItemRandom",
|
||||||
|
"widgetDisplayedItemMostRecent",
|
||||||
|
"widgetOpenPageHome",
|
||||||
|
"widgetOpenPageCollection",
|
||||||
|
"widgetOpenPageViewer",
|
||||||
|
"albumTierNew",
|
||||||
|
"albumTierPinned",
|
||||||
|
"albumTierSpecial",
|
||||||
|
"albumTierApps",
|
||||||
|
"albumTierRegular",
|
||||||
|
"storageVolumeDescriptionFallbackPrimary",
|
||||||
|
"storageVolumeDescriptionFallbackNonPrimary",
|
||||||
|
"rootDirectoryDescription",
|
||||||
|
"otherDirectoryDescription",
|
||||||
|
"storageAccessDialogMessage",
|
||||||
|
"restrictedAccessDialogMessage",
|
||||||
|
"notEnoughSpaceDialogMessage",
|
||||||
|
"missingSystemFilePickerDialogMessage",
|
||||||
|
"unsupportedTypeDialogMessage",
|
||||||
|
"nameConflictDialogSingleSourceMessage",
|
||||||
|
"nameConflictDialogMultipleSourceMessage",
|
||||||
|
"addShortcutDialogLabel",
|
||||||
|
"addShortcutButtonLabel",
|
||||||
|
"noMatchingAppDialogMessage",
|
||||||
|
"binEntriesConfirmationDialogMessage",
|
||||||
|
"deleteEntriesConfirmationDialogMessage",
|
||||||
|
"moveUndatedConfirmationDialogMessage",
|
||||||
|
"moveUndatedConfirmationDialogSetDate",
|
||||||
|
"videoResumeDialogMessage",
|
||||||
|
"videoStartOverButtonLabel",
|
||||||
|
"videoResumeButtonLabel",
|
||||||
|
"setCoverDialogLatest",
|
||||||
|
"setCoverDialogAuto",
|
||||||
|
"setCoverDialogCustom",
|
||||||
|
"hideFilterConfirmationDialogMessage",
|
||||||
|
"newAlbumDialogTitle",
|
||||||
|
"newAlbumDialogNameLabel",
|
||||||
|
"newAlbumDialogNameLabelAlreadyExistsHelper",
|
||||||
|
"newAlbumDialogStorageLabel",
|
||||||
|
"renameAlbumDialogLabel",
|
||||||
|
"renameAlbumDialogLabelAlreadyExistsHelper",
|
||||||
|
"renameEntrySetPageTitle",
|
||||||
|
"renameEntrySetPagePatternFieldLabel",
|
||||||
|
"renameEntrySetPageInsertTooltip",
|
||||||
|
"renameEntrySetPagePreviewSectionTitle",
|
||||||
|
"renameProcessorCounter",
|
||||||
|
"renameProcessorName",
|
||||||
|
"deleteSingleAlbumConfirmationDialogMessage",
|
||||||
|
"deleteMultiAlbumConfirmationDialogMessage",
|
||||||
|
"exportEntryDialogFormat",
|
||||||
|
"exportEntryDialogWidth",
|
||||||
|
"exportEntryDialogHeight",
|
||||||
|
"renameEntryDialogLabel",
|
||||||
|
"editEntryDialogCopyFromItem",
|
||||||
|
"editEntryDialogTargetFieldsHeader",
|
||||||
|
"editEntryDateDialogTitle",
|
||||||
|
"editEntryDateDialogSetCustom",
|
||||||
|
"editEntryDateDialogCopyField",
|
||||||
|
"editEntryDateDialogExtractFromTitle",
|
||||||
|
"editEntryDateDialogShift",
|
||||||
|
"editEntryDateDialogSourceFileModifiedDate",
|
||||||
|
"durationDialogHours",
|
||||||
|
"durationDialogMinutes",
|
||||||
|
"durationDialogSeconds",
|
||||||
|
"editEntryLocationDialogTitle",
|
||||||
|
"editEntryLocationDialogSetCustom",
|
||||||
|
"editEntryLocationDialogChooseOnMap",
|
||||||
|
"editEntryLocationDialogLatitude",
|
||||||
|
"editEntryLocationDialogLongitude",
|
||||||
|
"locationPickerUseThisLocationButton",
|
||||||
|
"editEntryRatingDialogTitle",
|
||||||
|
"removeEntryMetadataDialogTitle",
|
||||||
|
"removeEntryMetadataDialogMore",
|
||||||
|
"removeEntryMetadataMotionPhotoXmpWarningDialogMessage",
|
||||||
|
"videoSpeedDialogLabel",
|
||||||
|
"videoStreamSelectionDialogVideo",
|
||||||
|
"videoStreamSelectionDialogAudio",
|
||||||
|
"videoStreamSelectionDialogText",
|
||||||
|
"videoStreamSelectionDialogOff",
|
||||||
|
"videoStreamSelectionDialogTrack",
|
||||||
|
"videoStreamSelectionDialogNoSelection",
|
||||||
|
"genericSuccessFeedback",
|
||||||
|
"genericFailureFeedback",
|
||||||
|
"genericDangerWarningDialogMessage",
|
||||||
|
"tooManyItemsErrorDialogMessage",
|
||||||
|
"menuActionConfigureView",
|
||||||
|
"menuActionSelect",
|
||||||
|
"menuActionSelectAll",
|
||||||
|
"menuActionSelectNone",
|
||||||
|
"menuActionMap",
|
||||||
|
"menuActionSlideshow",
|
||||||
|
"menuActionStats",
|
||||||
|
"viewDialogSortSectionTitle",
|
||||||
|
"viewDialogGroupSectionTitle",
|
||||||
|
"viewDialogLayoutSectionTitle",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"tileLayoutMosaic",
|
||||||
|
"tileLayoutGrid",
|
||||||
|
"tileLayoutList",
|
||||||
|
"coverDialogTabCover",
|
||||||
|
"coverDialogTabApp",
|
||||||
|
"coverDialogTabColor",
|
||||||
|
"appPickDialogTitle",
|
||||||
|
"appPickDialogNone",
|
||||||
|
"aboutPageTitle",
|
||||||
|
"aboutLinkLicense",
|
||||||
|
"aboutLinkPolicy",
|
||||||
|
"aboutBugSectionTitle",
|
||||||
|
"aboutBugSaveLogInstruction",
|
||||||
|
"aboutBugCopyInfoInstruction",
|
||||||
|
"aboutBugCopyInfoButton",
|
||||||
|
"aboutBugReportInstruction",
|
||||||
|
"aboutBugReportButton",
|
||||||
|
"aboutCreditsSectionTitle",
|
||||||
|
"aboutCreditsWorldAtlas1",
|
||||||
|
"aboutCreditsWorldAtlas2",
|
||||||
|
"aboutTranslatorsSectionTitle",
|
||||||
|
"aboutLicensesSectionTitle",
|
||||||
|
"aboutLicensesBanner",
|
||||||
|
"aboutLicensesAndroidLibrariesSectionTitle",
|
||||||
|
"aboutLicensesFlutterPluginsSectionTitle",
|
||||||
|
"aboutLicensesFlutterPackagesSectionTitle",
|
||||||
|
"aboutLicensesDartPackagesSectionTitle",
|
||||||
|
"aboutLicensesShowAllButtonLabel",
|
||||||
|
"policyPageTitle",
|
||||||
|
"collectionPageTitle",
|
||||||
|
"collectionPickPageTitle",
|
||||||
|
"collectionSelectPageTitle",
|
||||||
|
"collectionActionShowTitleSearch",
|
||||||
|
"collectionActionHideTitleSearch",
|
||||||
|
"collectionActionAddShortcut",
|
||||||
|
"collectionActionEmptyBin",
|
||||||
|
"collectionActionCopy",
|
||||||
|
"collectionActionMove",
|
||||||
|
"collectionActionRescan",
|
||||||
|
"collectionActionEdit",
|
||||||
|
"collectionSearchTitlesHintText",
|
||||||
|
"collectionGroupAlbum",
|
||||||
|
"collectionGroupMonth",
|
||||||
|
"collectionGroupDay",
|
||||||
|
"collectionGroupNone",
|
||||||
|
"sectionUnknown",
|
||||||
|
"dateToday",
|
||||||
|
"dateYesterday",
|
||||||
|
"dateThisMonth",
|
||||||
|
"collectionDeleteFailureFeedback",
|
||||||
|
"collectionCopyFailureFeedback",
|
||||||
|
"collectionMoveFailureFeedback",
|
||||||
|
"collectionRenameFailureFeedback",
|
||||||
|
"collectionEditFailureFeedback",
|
||||||
|
"collectionExportFailureFeedback",
|
||||||
|
"collectionCopySuccessFeedback",
|
||||||
|
"collectionMoveSuccessFeedback",
|
||||||
|
"collectionRenameSuccessFeedback",
|
||||||
|
"collectionEditSuccessFeedback",
|
||||||
|
"collectionEmptyFavourites",
|
||||||
|
"collectionEmptyVideos",
|
||||||
|
"collectionEmptyImages",
|
||||||
|
"collectionEmptyGrantAccessButtonLabel",
|
||||||
|
"collectionSelectSectionTooltip",
|
||||||
|
"collectionDeselectSectionTooltip",
|
||||||
|
"drawerAboutButton",
|
||||||
|
"drawerSettingsButton",
|
||||||
|
"drawerCollectionAll",
|
||||||
|
"drawerCollectionFavourites",
|
||||||
|
"drawerCollectionImages",
|
||||||
|
"drawerCollectionVideos",
|
||||||
|
"drawerCollectionAnimated",
|
||||||
|
"drawerCollectionMotionPhotos",
|
||||||
|
"drawerCollectionPanoramas",
|
||||||
|
"drawerCollectionRaws",
|
||||||
|
"drawerCollectionSphericalVideos",
|
||||||
|
"drawerAlbumPage",
|
||||||
|
"drawerCountryPage",
|
||||||
|
"drawerTagPage",
|
||||||
|
"sortByDate",
|
||||||
|
"sortByName",
|
||||||
|
"sortByItemCount",
|
||||||
|
"sortBySize",
|
||||||
|
"sortByAlbumFileName",
|
||||||
|
"sortByRating",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
|
"albumGroupTier",
|
||||||
|
"albumGroupType",
|
||||||
|
"albumGroupVolume",
|
||||||
|
"albumGroupNone",
|
||||||
|
"albumMimeTypeMixed",
|
||||||
|
"albumPickPageTitleCopy",
|
||||||
|
"albumPickPageTitleExport",
|
||||||
|
"albumPickPageTitleMove",
|
||||||
|
"albumPickPageTitlePick",
|
||||||
|
"albumCamera",
|
||||||
|
"albumDownload",
|
||||||
|
"albumScreenshots",
|
||||||
|
"albumScreenRecordings",
|
||||||
|
"albumVideoCaptures",
|
||||||
|
"albumPageTitle",
|
||||||
|
"albumEmpty",
|
||||||
|
"createAlbumTooltip",
|
||||||
|
"createAlbumButtonLabel",
|
||||||
|
"newFilterBanner",
|
||||||
|
"countryPageTitle",
|
||||||
|
"countryEmpty",
|
||||||
|
"tagPageTitle",
|
||||||
|
"tagEmpty",
|
||||||
|
"binPageTitle",
|
||||||
|
"searchCollectionFieldHint",
|
||||||
|
"searchRecentSectionTitle",
|
||||||
|
"searchDateSectionTitle",
|
||||||
|
"searchAlbumsSectionTitle",
|
||||||
|
"searchCountriesSectionTitle",
|
||||||
|
"searchPlacesSectionTitle",
|
||||||
|
"searchTagsSectionTitle",
|
||||||
|
"searchRatingSectionTitle",
|
||||||
|
"searchMetadataSectionTitle",
|
||||||
|
"settingsPageTitle",
|
||||||
|
"settingsSystemDefault",
|
||||||
|
"settingsDefault",
|
||||||
|
"settingsDisabled",
|
||||||
|
"settingsModificationWarningDialogMessage",
|
||||||
|
"settingsSearchFieldLabel",
|
||||||
|
"settingsSearchEmpty",
|
||||||
|
"settingsActionExport",
|
||||||
|
"settingsActionExportDialogTitle",
|
||||||
|
"settingsActionImport",
|
||||||
|
"settingsActionImportDialogTitle",
|
||||||
|
"appExportCovers",
|
||||||
|
"appExportFavourites",
|
||||||
|
"appExportSettings",
|
||||||
|
"settingsNavigationSectionTitle",
|
||||||
|
"settingsHomeTile",
|
||||||
|
"settingsHomeDialogTitle",
|
||||||
|
"settingsShowBottomNavigationBar",
|
||||||
|
"settingsKeepScreenOnTile",
|
||||||
|
"settingsKeepScreenOnDialogTitle",
|
||||||
|
"settingsDoubleBackExit",
|
||||||
|
"settingsConfirmationTile",
|
||||||
|
"settingsConfirmationDialogTitle",
|
||||||
|
"settingsConfirmationBeforeDeleteItems",
|
||||||
|
"settingsConfirmationBeforeMoveToBinItems",
|
||||||
|
"settingsConfirmationBeforeMoveUndatedItems",
|
||||||
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
|
"settingsNavigationDrawerTile",
|
||||||
|
"settingsNavigationDrawerEditorPageTitle",
|
||||||
|
"settingsNavigationDrawerBanner",
|
||||||
|
"settingsNavigationDrawerTabTypes",
|
||||||
|
"settingsNavigationDrawerTabAlbums",
|
||||||
|
"settingsNavigationDrawerTabPages",
|
||||||
|
"settingsNavigationDrawerAddAlbum",
|
||||||
|
"settingsThumbnailSectionTitle",
|
||||||
|
"settingsThumbnailOverlayTile",
|
||||||
|
"settingsThumbnailOverlayPageTitle",
|
||||||
|
"settingsThumbnailShowFavouriteIcon",
|
||||||
|
"settingsThumbnailShowTagIcon",
|
||||||
|
"settingsThumbnailShowLocationIcon",
|
||||||
|
"settingsThumbnailShowMotionPhotoIcon",
|
||||||
|
"settingsThumbnailShowRating",
|
||||||
|
"settingsThumbnailShowRawIcon",
|
||||||
|
"settingsThumbnailShowVideoDuration",
|
||||||
|
"settingsCollectionQuickActionsTile",
|
||||||
|
"settingsCollectionQuickActionEditorPageTitle",
|
||||||
|
"settingsCollectionQuickActionTabBrowsing",
|
||||||
|
"settingsCollectionQuickActionTabSelecting",
|
||||||
|
"settingsCollectionBrowsingQuickActionEditorBanner",
|
||||||
|
"settingsCollectionSelectionQuickActionEditorBanner",
|
||||||
|
"settingsViewerSectionTitle",
|
||||||
|
"settingsViewerGestureSideTapNext",
|
||||||
|
"settingsViewerUseCutout",
|
||||||
|
"settingsViewerMaximumBrightness",
|
||||||
|
"settingsMotionPhotoAutoPlay",
|
||||||
|
"settingsImageBackground",
|
||||||
|
"settingsViewerQuickActionsTile",
|
||||||
|
"settingsViewerQuickActionEditorPageTitle",
|
||||||
|
"settingsViewerQuickActionEditorBanner",
|
||||||
|
"settingsViewerQuickActionEditorDisplayedButtonsSectionTitle",
|
||||||
|
"settingsViewerQuickActionEditorAvailableButtonsSectionTitle",
|
||||||
|
"settingsViewerQuickActionEmpty",
|
||||||
|
"settingsViewerOverlayTile",
|
||||||
|
"settingsViewerOverlayPageTitle",
|
||||||
|
"settingsViewerShowOverlayOnOpening",
|
||||||
|
"settingsViewerShowMinimap",
|
||||||
|
"settingsViewerShowInformation",
|
||||||
|
"settingsViewerShowInformationSubtitle",
|
||||||
|
"settingsViewerShowRatingTags",
|
||||||
|
"settingsViewerShowShootingDetails",
|
||||||
|
"settingsViewerShowDescription",
|
||||||
|
"settingsViewerShowOverlayThumbnails",
|
||||||
|
"settingsViewerEnableOverlayBlurEffect",
|
||||||
|
"settingsViewerSlideshowTile",
|
||||||
|
"settingsViewerSlideshowPageTitle",
|
||||||
|
"settingsSlideshowRepeat",
|
||||||
|
"settingsSlideshowShuffle",
|
||||||
|
"settingsSlideshowFillScreen",
|
||||||
|
"settingsSlideshowAnimatedZoomEffect",
|
||||||
|
"settingsSlideshowTransitionTile",
|
||||||
|
"settingsSlideshowIntervalTile",
|
||||||
|
"settingsSlideshowVideoPlaybackTile",
|
||||||
|
"settingsSlideshowVideoPlaybackDialogTitle",
|
||||||
|
"settingsVideoPageTitle",
|
||||||
|
"settingsVideoSectionTitle",
|
||||||
|
"settingsVideoShowVideos",
|
||||||
|
"settingsVideoEnableHardwareAcceleration",
|
||||||
|
"settingsVideoAutoPlay",
|
||||||
|
"settingsVideoLoopModeTile",
|
||||||
|
"settingsVideoLoopModeDialogTitle",
|
||||||
|
"settingsSubtitleThemeTile",
|
||||||
|
"settingsSubtitleThemePageTitle",
|
||||||
|
"settingsSubtitleThemeSample",
|
||||||
|
"settingsSubtitleThemeTextAlignmentTile",
|
||||||
|
"settingsSubtitleThemeTextAlignmentDialogTitle",
|
||||||
|
"settingsSubtitleThemeTextPositionTile",
|
||||||
|
"settingsSubtitleThemeTextPositionDialogTitle",
|
||||||
|
"settingsSubtitleThemeTextSize",
|
||||||
|
"settingsSubtitleThemeShowOutline",
|
||||||
|
"settingsSubtitleThemeTextColor",
|
||||||
|
"settingsSubtitleThemeTextOpacity",
|
||||||
|
"settingsSubtitleThemeBackgroundColor",
|
||||||
|
"settingsSubtitleThemeBackgroundOpacity",
|
||||||
|
"settingsSubtitleThemeTextAlignmentLeft",
|
||||||
|
"settingsSubtitleThemeTextAlignmentCenter",
|
||||||
|
"settingsSubtitleThemeTextAlignmentRight",
|
||||||
|
"settingsVideoControlsTile",
|
||||||
|
"settingsVideoControlsPageTitle",
|
||||||
|
"settingsVideoButtonsTile",
|
||||||
|
"settingsVideoGestureDoubleTapTogglePlay",
|
||||||
|
"settingsVideoGestureSideDoubleTapSeek",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
|
"settingsPrivacySectionTitle",
|
||||||
|
"settingsAllowInstalledAppAccess",
|
||||||
|
"settingsAllowInstalledAppAccessSubtitle",
|
||||||
|
"settingsAllowErrorReporting",
|
||||||
|
"settingsSaveSearchHistory",
|
||||||
|
"settingsEnableBin",
|
||||||
|
"settingsEnableBinSubtitle",
|
||||||
|
"settingsAllowMediaManagement",
|
||||||
|
"settingsHiddenItemsTile",
|
||||||
|
"settingsHiddenItemsPageTitle",
|
||||||
|
"settingsHiddenItemsTabFilters",
|
||||||
|
"settingsHiddenFiltersBanner",
|
||||||
|
"settingsHiddenFiltersEmpty",
|
||||||
|
"settingsHiddenItemsTabPaths",
|
||||||
|
"settingsHiddenPathsBanner",
|
||||||
|
"addPathTooltip",
|
||||||
|
"settingsStorageAccessTile",
|
||||||
|
"settingsStorageAccessPageTitle",
|
||||||
|
"settingsStorageAccessBanner",
|
||||||
|
"settingsStorageAccessEmpty",
|
||||||
|
"settingsStorageAccessRevokeTooltip",
|
||||||
|
"settingsAccessibilitySectionTitle",
|
||||||
|
"settingsRemoveAnimationsTile",
|
||||||
|
"settingsRemoveAnimationsDialogTitle",
|
||||||
|
"settingsTimeToTakeActionTile",
|
||||||
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
|
"settingsDisplaySectionTitle",
|
||||||
|
"settingsThemeBrightnessTile",
|
||||||
|
"settingsThemeBrightnessDialogTitle",
|
||||||
|
"settingsThemeColorHighlights",
|
||||||
|
"settingsThemeEnableDynamicColor",
|
||||||
|
"settingsDisplayRefreshRateModeTile",
|
||||||
|
"settingsDisplayRefreshRateModeDialogTitle",
|
||||||
|
"settingsDisplayUseTvInterface",
|
||||||
|
"settingsLanguageSectionTitle",
|
||||||
|
"settingsLanguageTile",
|
||||||
|
"settingsLanguagePageTitle",
|
||||||
|
"settingsCoordinateFormatTile",
|
||||||
|
"settingsCoordinateFormatDialogTitle",
|
||||||
|
"settingsUnitSystemTile",
|
||||||
|
"settingsUnitSystemDialogTitle",
|
||||||
|
"settingsScreenSaverPageTitle",
|
||||||
|
"settingsWidgetPageTitle",
|
||||||
|
"settingsWidgetShowOutline",
|
||||||
|
"settingsWidgetOpenPage",
|
||||||
|
"settingsWidgetDisplayedItem",
|
||||||
|
"settingsCollectionTile",
|
||||||
|
"statsPageTitle",
|
||||||
|
"statsWithGps",
|
||||||
|
"statsTopCountriesSectionTitle",
|
||||||
|
"statsTopPlacesSectionTitle",
|
||||||
|
"statsTopTagsSectionTitle",
|
||||||
|
"statsTopAlbumsSectionTitle",
|
||||||
|
"viewerOpenPanoramaButtonLabel",
|
||||||
|
"viewerSetWallpaperButtonLabel",
|
||||||
|
"viewerErrorUnknown",
|
||||||
|
"viewerErrorDoesNotExist",
|
||||||
|
"viewerInfoPageTitle",
|
||||||
|
"viewerInfoBackToViewerTooltip",
|
||||||
|
"viewerInfoUnknown",
|
||||||
|
"viewerInfoLabelDescription",
|
||||||
|
"viewerInfoLabelTitle",
|
||||||
|
"viewerInfoLabelDate",
|
||||||
|
"viewerInfoLabelResolution",
|
||||||
|
"viewerInfoLabelSize",
|
||||||
|
"viewerInfoLabelUri",
|
||||||
|
"viewerInfoLabelPath",
|
||||||
|
"viewerInfoLabelDuration",
|
||||||
|
"viewerInfoLabelOwner",
|
||||||
|
"viewerInfoLabelCoordinates",
|
||||||
|
"viewerInfoLabelAddress",
|
||||||
|
"mapStyleDialogTitle",
|
||||||
|
"mapStyleTooltip",
|
||||||
|
"mapZoomInTooltip",
|
||||||
|
"mapZoomOutTooltip",
|
||||||
|
"mapPointNorthUpTooltip",
|
||||||
|
"mapAttributionOsmHot",
|
||||||
|
"mapAttributionStamen",
|
||||||
|
"openMapPageTooltip",
|
||||||
|
"mapEmptyRegion",
|
||||||
|
"viewerInfoOpenEmbeddedFailureFeedback",
|
||||||
|
"viewerInfoOpenLinkText",
|
||||||
|
"viewerInfoViewXmlLinkText",
|
||||||
|
"viewerInfoSearchFieldLabel",
|
||||||
|
"viewerInfoSearchEmpty",
|
||||||
|
"viewerInfoSearchSuggestionDate",
|
||||||
|
"viewerInfoSearchSuggestionDescription",
|
||||||
|
"viewerInfoSearchSuggestionDimensions",
|
||||||
|
"viewerInfoSearchSuggestionResolution",
|
||||||
|
"viewerInfoSearchSuggestionRights",
|
||||||
|
"wallpaperUseScrollEffect",
|
||||||
|
"tagEditorPageTitle",
|
||||||
|
"tagEditorPageNewTagFieldLabel",
|
||||||
|
"tagEditorPageAddTagTooltip",
|
||||||
|
"tagEditorSectionRecent",
|
||||||
|
"tagEditorSectionPlaceholders",
|
||||||
|
"tagPlaceholderCountry",
|
||||||
|
"tagPlaceholderPlace",
|
||||||
|
"panoramaEnableSensorControl",
|
||||||
|
"panoramaDisableSensorControl",
|
||||||
|
"sourceViewerPageTitle",
|
||||||
|
"filePickerShowHiddenFiles",
|
||||||
|
"filePickerDoNotShowHiddenFiles",
|
||||||
|
"filePickerOpenFrom",
|
||||||
|
"filePickerNoItems",
|
||||||
|
"filePickerUseThisFolder"
|
||||||
|
],
|
||||||
|
|
||||||
|
"it": [
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume"
|
||||||
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
"chipActionFilterIn",
|
"chipActionFilterIn",
|
||||||
|
@ -1526,6 +2135,7 @@
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
"settingsModificationWarningDialogMessage",
|
"settingsModificationWarningDialogMessage",
|
||||||
"settingsViewerShowDescription",
|
"settingsViewerShowDescription",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
"settingsDisplayUseTvInterface",
|
"settingsDisplayUseTvInterface",
|
||||||
"settingsWidgetDisplayedItem"
|
"settingsWidgetDisplayedItem"
|
||||||
|
@ -1539,23 +2149,13 @@
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
"settingsModificationWarningDialogMessage",
|
"settingsModificationWarningDialogMessage",
|
||||||
"settingsViewerShowDescription",
|
"settingsViewerShowDescription",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
"settingsDisplayUseTvInterface"
|
"settingsDisplayUseTvInterface"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nb": [
|
"nb": [
|
||||||
"columnCount",
|
"settingsVideoGestureVerticalDragBrightnessVolume"
|
||||||
"entryActionShareImageOnly",
|
|
||||||
"entryActionShareVideoOnly",
|
|
||||||
"entryInfoActionRemoveLocation",
|
|
||||||
"filterLocatedLabel",
|
|
||||||
"filterTaggedLabel",
|
|
||||||
"keepScreenOnVideoPlayback",
|
|
||||||
"tooManyItemsErrorDialogMessage",
|
|
||||||
"settingsModificationWarningDialogMessage",
|
|
||||||
"settingsViewerShowDescription",
|
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
|
||||||
"settingsDisplayUseTvInterface"
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
|
@ -1580,6 +2180,7 @@
|
||||||
"settingsViewerShowDescription",
|
"settingsViewerShowDescription",
|
||||||
"settingsSubtitleThemeTextPositionTile",
|
"settingsSubtitleThemeTextPositionTile",
|
||||||
"settingsSubtitleThemeTextPositionDialogTitle",
|
"settingsSubtitleThemeTextPositionDialogTitle",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
"settingsDisplayUseTvInterface",
|
"settingsDisplayUseTvInterface",
|
||||||
"settingsWidgetDisplayedItem"
|
"settingsWidgetDisplayedItem"
|
||||||
|
@ -1828,6 +2429,7 @@
|
||||||
"settingsVideoButtonsTile",
|
"settingsVideoButtonsTile",
|
||||||
"settingsVideoGestureDoubleTapTogglePlay",
|
"settingsVideoGestureDoubleTapTogglePlay",
|
||||||
"settingsVideoGestureSideDoubleTapSeek",
|
"settingsVideoGestureSideDoubleTapSeek",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsPrivacySectionTitle",
|
"settingsPrivacySectionTitle",
|
||||||
"settingsAllowInstalledAppAccess",
|
"settingsAllowInstalledAppAccess",
|
||||||
"settingsAllowInstalledAppAccessSubtitle",
|
"settingsAllowInstalledAppAccessSubtitle",
|
||||||
|
@ -1872,17 +2474,14 @@
|
||||||
"wallpaperUseScrollEffect"
|
"wallpaperUseScrollEffect"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
|
||||||
"tooManyItemsErrorDialogMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
"tooManyItemsErrorDialogMessage"
|
"tooManyItemsErrorDialogMessage",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ro": [
|
"ro": [
|
||||||
"tooManyItemsErrorDialogMessage"
|
"settingsVideoGestureVerticalDragBrightnessVolume"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
@ -1890,6 +2489,7 @@
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
"settingsModificationWarningDialogMessage",
|
"settingsModificationWarningDialogMessage",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsDisplayUseTvInterface"
|
"settingsDisplayUseTvInterface"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -1901,29 +2501,6 @@
|
||||||
"timeDays",
|
"timeDays",
|
||||||
"focalLength",
|
"focalLength",
|
||||||
"applyButtonLabel",
|
"applyButtonLabel",
|
||||||
"entryActionShareImageOnly",
|
|
||||||
"entryActionShareVideoOnly",
|
|
||||||
"entryActionShowGeoTiffOnMap",
|
|
||||||
"videoActionCaptureFrame",
|
|
||||||
"entryInfoActionRemoveLocation",
|
|
||||||
"filterAspectRatioLandscapeLabel",
|
|
||||||
"filterAspectRatioPortraitLabel",
|
|
||||||
"filterNoAddressLabel",
|
|
||||||
"filterLocatedLabel",
|
|
||||||
"filterTaggedLabel",
|
|
||||||
"coordinateDms",
|
|
||||||
"keepScreenOnVideoPlayback",
|
|
||||||
"keepScreenOnViewerOnly",
|
|
||||||
"accessibilityAnimationsRemove",
|
|
||||||
"accessibilityAnimationsKeep",
|
|
||||||
"widgetOpenPageViewer",
|
|
||||||
"missingSystemFilePickerDialogMessage",
|
|
||||||
"unsupportedTypeDialogMessage",
|
|
||||||
"addShortcutDialogLabel",
|
|
||||||
"binEntriesConfirmationDialogMessage",
|
|
||||||
"deleteEntriesConfirmationDialogMessage",
|
|
||||||
"deleteSingleAlbumConfirmationDialogMessage",
|
|
||||||
"deleteMultiAlbumConfirmationDialogMessage",
|
|
||||||
"editEntryDateDialogExtractFromTitle",
|
"editEntryDateDialogExtractFromTitle",
|
||||||
"editEntryDateDialogShift",
|
"editEntryDateDialogShift",
|
||||||
"removeEntryMetadataDialogTitle",
|
"removeEntryMetadataDialogTitle",
|
||||||
|
@ -2133,6 +2710,7 @@
|
||||||
"settingsVideoButtonsTile",
|
"settingsVideoButtonsTile",
|
||||||
"settingsVideoGestureDoubleTapTogglePlay",
|
"settingsVideoGestureDoubleTapTogglePlay",
|
||||||
"settingsVideoGestureSideDoubleTapSeek",
|
"settingsVideoGestureSideDoubleTapSeek",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsPrivacySectionTitle",
|
"settingsPrivacySectionTitle",
|
||||||
"settingsAllowInstalledAppAccess",
|
"settingsAllowInstalledAppAccess",
|
||||||
"settingsAllowInstalledAppAccessSubtitle",
|
"settingsAllowInstalledAppAccessSubtitle",
|
||||||
|
@ -2241,16 +2819,13 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
"uk": [
|
|
||||||
"tooManyItemsErrorDialogMessage"
|
|
||||||
],
|
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
"filterLocatedLabel",
|
"filterLocatedLabel",
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
"settingsModificationWarningDialogMessage",
|
"settingsModificationWarningDialogMessage",
|
||||||
"settingsViewerShowDescription",
|
"settingsViewerShowDescription",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
"settingsDisplayUseTvInterface"
|
"settingsDisplayUseTvInterface"
|
||||||
],
|
],
|
||||||
|
@ -2262,6 +2837,7 @@
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
"settingsModificationWarningDialogMessage",
|
"settingsModificationWarningDialogMessage",
|
||||||
"settingsViewerShowDescription",
|
"settingsViewerShowDescription",
|
||||||
|
"settingsVideoGestureVerticalDragBrightnessVolume",
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
"settingsDisplayUseTvInterface"
|
"settingsDisplayUseTvInterface"
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
In v1.7.9:
|
In v1.7.10:
|
||||||
- Android TV support (cont'd)
|
- Android TV support (cont'd)
|
||||||
- interact with videos via media session controls
|
- interact with videos via media session controls
|
||||||
- enjoy the app in Czech & Polish
|
- enjoy the app in Czech & Polish
|
||||||
|
|
Loading…
Reference in a new issue