fix: Smart Pickup-fane — layout som AD's egen side (ScrollingLayout + slider, rækkefarver), udskudt genopbygning, fokus på boxLayout (v1.9.1.0)

This commit is contained in:
2026-09-22 23:49:58 +02:00
parent 856b949749
commit 90d86b22e9
4 changed files with 165 additions and 97 deletions
+108 -28
View File
@@ -21,6 +21,10 @@ function SmartPickupFrame.new(i18n, getAdEnv)
self.clonedRows = {} self.clonedRows = {}
self.refreshTimer = 0 self.refreshTimer = 0
self.statusOverride = nil self.statusOverride = nil
self.statusRow = nil -- Text-elementet i den klonede statusrække
self.pendingRebuild = false
self.frameOpen = false
self.focusLost = false
self.backButtonInfo = {inputAction = InputAction.MENU_BACK} self.backButtonInfo = {inputAction = InputAction.MENU_BACK}
self.startButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_button_start"), callback = function() self:onClickStartStop() end} self.startButtonInfo = {inputAction = InputAction.MENU_ACTIVATE, text = i18n:getText("spu_button_start"), callback = function() self:onClickStartStop() end}
self.newButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_button_new"), callback = function() self:onClickNew() end} self.newButtonInfo = {inputAction = InputAction.MENU_EXTRA_1, text = i18n:getText("spu_button_new"), callback = function() self:onClickNew() end}
@@ -43,7 +47,7 @@ end
-- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker. -- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker.
function SmartPickupFrame:initialize() function SmartPickupFrame:initialize()
for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "poolPrefab"}) do for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab"}) do
local prefab = self[name] local prefab = self[name]
if prefab ~= nil then if prefab ~= nil then
prefab:unlinkElement() prefab:unlinkElement()
@@ -58,29 +62,69 @@ function SmartPickupFrame:initialize()
self.loopsOption:setTexts(loopTexts) self.loopsOption:setTexts(loopTexts)
end end
-- FocusManager har skiftet API mellem FS-versioner; her hentes det fokuserede element
-- så blødt som muligt, fordi det kun bruges til at opdage at fokus lige er slettet.
function SmartPickupFrame.getFocusedElement()
if FocusManager == nil then
return nil
end
if FocusManager.getFocusedElement ~= nil then
local ok, element = pcall(FocusManager.getFocusedElement, FocusManager)
if ok then
return element
end
end
local data = FocusManager.currentFocusData
if type(data) == "table" then
return data.focusElement
end
return nil
end
function SmartPickupFrame.isDescendantOf(element, ancestor)
while element ~= nil do
if element == ancestor then
return true
end
element = element.parent
end
return false
end
-- rebuild sletter de rækker der kan have fokus (fx vare-rækkerne når Varer skifter til -- rebuild sletter de rækker der kan have fokus (fx vare-rækkerne når Varer skifter til
-- Auto); uden dette står fokus på et slettet element og tastatur/controller er død. -- Auto); uden dette står fokus på et slettet element og tastatur/controller er død.
function SmartPickupFrame:focusRunOption() function SmartPickupFrame:restoreFocusIfLost()
self:safe(function(frame) if self.focusLost and self.frameOpen and self.boxLayout ~= nil then
if frame.runOption ~= nil then FocusManager:setFocus(self.boxLayout)
FocusManager:setFocus(frame.runOption)
end end
end) self.focusLost = false
end end
function SmartPickupFrame:onFrameOpen() function SmartPickupFrame:onFrameOpen()
SmartPickupFrame:superClass().onFrameOpen(self) SmartPickupFrame:superClass().onFrameOpen(self)
self.statusOverride = nil self.statusOverride = nil
self.frameOpen = true
self.pendingRebuild = false
self:safe(self.rebuild) self:safe(self.rebuild)
-- Som AutoDrive's egne sider: fokus på selve listen, ikke på en enkelt række.
self:safe(function(frame)
FocusManager:setFocus(frame.boxLayout)
end)
end end
function SmartPickupFrame:onFrameClose() function SmartPickupFrame:onFrameClose()
self.frameOpen = false
SmartPickupFrame:superClass().onFrameClose(self) SmartPickupFrame:superClass().onFrameClose(self)
end end
-- Live-opdatering af status/traktor-rækker mens siden er åben (uden at genopbygge rækkerne). -- Genopbygning sker her og aldrig inde i et klik-callback: sletter man elementet midt i
-- dets egen klik-håndtering, hopper værdien frem og tilbage og fokus ryger.
function SmartPickupFrame:update(dt) function SmartPickupFrame:update(dt)
SmartPickupFrame:superClass().update(self, dt) SmartPickupFrame:superClass().update(self, dt)
if self.pendingRebuild then
self.pendingRebuild = false
self:safe(self.rebuild)
end
self.refreshTimer = self.refreshTimer + dt self.refreshTimer = self.refreshTimer + dt
if self.refreshTimer >= SmartPickupFrame.REFRESH_INTERVAL_MS then if self.refreshTimer >= SmartPickupFrame.REFRESH_INTERVAL_MS then
self.refreshTimer = 0 self.refreshTimer = 0
@@ -93,10 +137,14 @@ function SmartPickupFrame:safe(fn, ...)
if not ok then if not ok then
Logging.warning(SmartPickupFrame.LOG_PREFIX .. "GUI-fejl: %s", tostring(err)) Logging.warning(SmartPickupFrame.LOG_PREFIX .. "GUI-fejl: %s", tostring(err))
self.statusOverride = string.format(self.i18n:getText("spu_status_error"), tostring(err)) self.statusOverride = string.format(self.i18n:getText("spu_status_error"), tostring(err))
if self.statusText ~= nil then self:setStatusText(self.statusOverride)
self.statusText:setText(self.statusOverride)
end end
end end
function SmartPickupFrame:setStatusText(text)
if self.statusRow ~= nil then
self.statusRow:setText(text)
end
end end
function SmartPickupFrame:getRun() function SmartPickupFrame:getRun()
@@ -108,15 +156,20 @@ end
-- addRow melder hver klonet række ind i FocusManager; uden afmelding her ville -- addRow melder hver klonet række ind i FocusManager; uden afmelding her ville
-- FocusManager beholde slettede rækker og fokus-navigationen gå i stå. -- FocusManager beholde slettede rækker og fokus-navigationen gå i stå.
function SmartPickupFrame:clearRows() function SmartPickupFrame:clearRows()
local focused = SmartPickupFrame.getFocusedElement()
for _, row in ipairs(self.clonedRows) do for _, row in ipairs(self.clonedRows) do
if focused ~= nil and SmartPickupFrame.isDescendantOf(focused, row) then
self.focusLost = true
end
FocusManager:removeElement(row) FocusManager:removeElement(row)
row:delete() row:delete()
end end
self.clonedRows, self.rowData = {}, {} self.clonedRows, self.rowData = {}, {}
self.statusRow = nil
end end
function SmartPickupFrame:addRow(prefab, parent, data) function SmartPickupFrame:addRow(prefab, data)
local row = prefab:clone(parent) local row = prefab:clone(self.boxLayout)
FocusManager:loadElementFromCustomValues(row) FocusManager:loadElementFromCustomValues(row)
table.insert(self.clonedRows, row) table.insert(self.clonedRows, row)
local option = row:getDescendantByName("option") local option = row:getDescendantByName("option")
@@ -127,6 +180,30 @@ function SmartPickupFrame:addRow(prefab, parent, data)
return row return row
end end
-- Tekstrække uden valgmuligheder: sektionstitler, Wait-pulje-linjer og statuslinjen.
function SmartPickupFrame:addTextRow(text)
local title = self:addRow(self.textPrefab, {kind = "text"}):getDescendantByName("title")
title:setText(text)
return title
end
-- AutoDrive farver hver række i onCreate; her sker det efter hver genopbygning,
-- fordi rækkerne først findes på det tidspunkt.
function SmartPickupFrame:colorRows()
local colors = InGameMenuSettingsFrame ~= nil and InGameMenuSettingsFrame.COLOR_ALTERNATING or nil
if colors == nil or self.boxLayout == nil then
return
end
local unpackFn = table.unpack or unpack
local isEvenRow = false
for _, element in ipairs(self.boxLayout.elements) do
if element.setImageColor ~= nil and colors[isEvenRow] ~= nil then
element:setImageColor(nil, unpackFn(colors[isEvenRow]))
end
isEvenRow = not isEvenRow
end
end
function SmartPickupFrame:rebuild() function SmartPickupFrame:rebuild()
local adEnv = self.getAdEnv() local adEnv = self.getAdEnv()
local store = ADRunsController.store local store = ADRunsController.store
@@ -146,14 +223,18 @@ function SmartPickupFrame:rebuild()
self:fillIngredientRows(adEnv, run) self:fillIngredientRows(adEnv, run)
self:fillWaitPoolOption(adEnv, run) self:fillWaitPoolOption(adEnv, run)
self.loopsOption:setState(run.loops + 1, false) self.loopsOption:setState(run.loops + 1, false)
self:addTextRow(self.i18n:getText("spu_vehicles"))
self:fillVehicleRows(adEnv, run) self:fillVehicleRows(adEnv, run)
end end
if adEnv ~= nil then
self:addTextRow(self.i18n:getText("spu_waitPools"))
self:fillPoolRows(adEnv) self:fillPoolRows(adEnv)
for _, box in ipairs({self.ingredientBox, self.vehicleBox, self.poolBox, self.boxLayout}) do
box:invalidateLayout()
end end
self.statusRow = self:addTextRow("")
self:colorRows()
self.boxLayout:invalidateLayout()
self:refreshLive() self:refreshLive()
self:focusRunOption() self:restoreFocusIfLost()
end end
function SmartPickupFrame:fillRunOption(adEnv) function SmartPickupFrame:fillRunOption(adEnv)
@@ -198,7 +279,7 @@ function SmartPickupFrame:fillIngredientRows(adEnv, run)
chosen[name] = true chosen[name] = true
end end
for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do
local row = self:addRow(self.ingredientPrefab, self.ingredientBox, {kind = "ingredient", name = fillType.name}) local row = self:addRow(self.ingredientPrefab, {kind = "ingredient", name = fillType.name})
row:getDescendantByName("title"):setText(tostring(fillType.title)) row:getDescendantByName("title"):setText(tostring(fillType.title))
row:getDescendantByName("option"):setState(chosen[fillType.name] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) row:getDescendantByName("option"):setState(chosen[fillType.name] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end end
@@ -225,7 +306,7 @@ function SmartPickupFrame:fillVehicleRows(adEnv, run)
end end
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
local id = ADRunsController.getVehicleId(vehicle) local id = ADRunsController.getVehicleId(vehicle)
local row = self:addRow(self.vehiclePrefab, self.vehicleBox, {kind = "vehicle", vehicle = vehicle, row = nil}) local row = self:addRow(self.vehiclePrefab, {kind = "vehicle", vehicle = vehicle, row = nil})
self.rowData[row:getDescendantByName("option")].row = row self.rowData[row:getDescendantByName("option")].row = row
row:getDescendantByName("option"):setState(inRun[id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false) row:getDescendantByName("option"):setState(inRun[id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
row:getDescendantByName("title"):setText(self:describeVehicle(adEnv, run, vehicle, inRun[id])) row:getDescendantByName("title"):setText(self:describeVehicle(adEnv, run, vehicle, inRun[id]))
@@ -264,8 +345,7 @@ function SmartPickupFrame:fillPoolRows(adEnv)
for _, pool in ipairs(ADRunsController.getWaitPools(adEnv)) do for _, pool in ipairs(ADRunsController.getWaitPools(adEnv)) do
local names = occupied[pool.group] or {} local names = occupied[pool.group] or {}
local suffix = #names > 0 and (" (" .. table.concat(names, ", ") .. ")") or "" local suffix = #names > 0 and (" (" .. table.concat(names, ", ") .. ")") or ""
local row = self:addRow(self.poolPrefab, self.poolBox, {kind = "pool"}) self:addTextRow(string.format(self.i18n:getText("spu_waitPoolRow"), pool.group, pool.count, #names, suffix))
row:getDescendantByName("title"):setText(string.format(self.i18n:getText("spu_waitPoolRow"), pool.group, pool.count, #names, suffix))
end end
end end
@@ -289,7 +369,7 @@ function SmartPickupFrame:refreshLive()
local adEnv = self.getAdEnv() local adEnv = self.getAdEnv()
local run = self:getRun() local run = self:getRun()
if adEnv == nil then if adEnv == nil then
self.statusText:setText(self.i18n:getText("spu_status_noAd")) self:setStatusText(self.i18n:getText("spu_status_noAd"))
return return
end end
local isRunning = false local isRunning = false
@@ -298,14 +378,14 @@ function SmartPickupFrame:refreshLive()
isRunning = status.running > 0 isRunning = status.running > 0
local text = isRunning and string.format(self.i18n:getText("spu_status_running"), status.running, status.total) local text = isRunning and string.format(self.i18n:getText("spu_status_running"), status.running, status.total)
or string.format(self.i18n:getText("spu_status_stopped"), status.total) or string.format(self.i18n:getText("spu_status_stopped"), status.total)
self.statusText:setText(self.statusOverride or text) self:setStatusText(self.statusOverride or text)
for option, data in pairs(self.rowData) do for option, data in pairs(self.rowData) do
if data.kind == "vehicle" and data.row ~= nil then if data.kind == "vehicle" and data.row ~= nil then
data.row:getDescendantByName("title"):setText(self:describeVehicle(adEnv, run, data.vehicle, SmartPickupFrame.getOptionState(option) == SmartPickupFrame.STATE_ON)) data.row:getDescendantByName("title"):setText(self:describeVehicle(adEnv, run, data.vehicle, SmartPickupFrame.getOptionState(option) == SmartPickupFrame.STATE_ON))
end end
end end
else else
self.statusText:setText(self.statusOverride or self.i18n:getText("spu_noRuns")) self:setStatusText(self.statusOverride or self.i18n:getText("spu_noRuns"))
end end
-- Engangsbesked ("Startet 2, sprunget over 1", "flyttet fra ..."): vist nu, ryddet her, -- Engangsbesked ("Startet 2, sprunget over 1", "flyttet fra ..."): vist nu, ryddet her,
-- så næste opdatering om et sekund viser den levende status igen. -- så næste opdatering om et sekund viser den levende status igen.
@@ -322,7 +402,7 @@ end
function SmartPickupFrame:onClickRun(state) function SmartPickupFrame:onClickRun(state)
self.selectedRunId = self.runIds[state] self.selectedRunId = self.runIds[state]
self.statusOverride = nil self.statusOverride = nil
self:safe(self.rebuild) self.pendingRebuild = true
end end
function SmartPickupFrame:onClickTarget(state) function SmartPickupFrame:onClickTarget(state)
@@ -331,7 +411,7 @@ function SmartPickupFrame:onClickTarget(state)
if run == nil then return end if run == nil then return end
-- targetIds[1] er false = "(vælg mål)"; modellen tolker false som "ryd målet" -- targetIds[1] er false = "(vælg mål)"; modellen tolker false som "ryd målet"
ADRunsController.updateRun(run.id, {targetWayPointId = frame.targetIds[state] or false}) ADRunsController.updateRun(run.id, {targetWayPointId = frame.targetIds[state] or false})
frame:rebuild() frame.pendingRebuild = true
end) end)
end end
@@ -340,7 +420,7 @@ function SmartPickupFrame:onClickIngredientMode(state)
local run = frame:getRun() local run = frame:getRun()
if run == nil then return end if run == nil then return end
ADRunsController.updateRun(run.id, {ingredientMode = state == SmartPickupFrame.STATE_ON and "manual" or "auto"}) ADRunsController.updateRun(run.id, {ingredientMode = state == SmartPickupFrame.STATE_ON and "manual" or "auto"})
frame:rebuild() frame.pendingRebuild = true
end) end)
end end
@@ -409,7 +489,7 @@ function SmartPickupFrame:onClickStartStop()
frame.statusOverride = string.format(frame.i18n:getText("spu_status_started"), started, skipped) frame.statusOverride = string.format(frame.i18n:getText("spu_status_started"), started, skipped)
end end
ADRunsController.save() ADRunsController.save()
frame:rebuild() frame.pendingRebuild = true
end) end)
end end
@@ -419,7 +499,7 @@ function SmartPickupFrame:onClickNew()
local run = ADRunsController.createRun(string.format(frame.i18n:getText("spu_newRunName"), ADRunsController.store.nextId)) local run = ADRunsController.createRun(string.format(frame.i18n:getText("spu_newRunName"), ADRunsController.store.nextId))
frame.selectedRunId = run.id frame.selectedRunId = run.id
frame.statusOverride = nil frame.statusOverride = nil
frame:rebuild() frame.pendingRebuild = true
end) end)
end end
@@ -438,7 +518,7 @@ function SmartPickupFrame:onRenameResult(value, clickOk)
local run = frame:getRun() local run = frame:getRun()
if clickOk and run ~= nil and value ~= nil and value:match("%S") then if clickOk and run ~= nil and value ~= nil and value:match("%S") then
ADRunsController.updateRun(run.id, {name = value}) ADRunsController.updateRun(run.id, {name = value})
frame:rebuild() frame.pendingRebuild = true
end end
end) end)
end end
@@ -458,7 +538,7 @@ function SmartPickupFrame:onDeleteResult(yes)
if yes and run ~= nil then if yes and run ~= nil then
ADRunsController.deleteRun(run.id) ADRunsController.deleteRun(run.id)
frame.selectedRunId = nil frame.selectedRunId = nil
frame:rebuild() frame.pendingRebuild = true
end end
end) end)
end end
+10 -16
View File
@@ -8,7 +8,8 @@
<Text profile="fs25_menuHeaderTitle" text="$l10n_spu_title"/> <Text profile="fs25_menuHeaderTitle" text="$l10n_spu_title"/>
</GuiElement> </GuiElement>
<GuiElement profile="spuTabContainer"> <!-- Samme opbygning som AutoDrive's egne ESC-sider: ScrollingLayout direkte i
menuContainer, alle rækker som direkte børn (ingen indlejrede layouts). -->
<ScrollingLayout profile="fs25_settingsLayout" id="boxLayout"> <ScrollingLayout profile="fs25_settingsLayout" id="boxLayout">
<Bitmap profile="fs25_multiTextOptionContainer"> <Bitmap profile="fs25_multiTextOptionContainer">
<MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickRun" id="runOption"> <MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickRun" id="runOption">
@@ -28,7 +29,6 @@
</BinaryOption> </BinaryOption>
<Text profile="fs25_settingsMultiTextOptionTitle" text="$l10n_spu_ingredientMode"/> <Text profile="fs25_settingsMultiTextOptionTitle" text="$l10n_spu_ingredientMode"/>
</Bitmap> </Bitmap>
<BoxLayout profile="fs25_settingsLayout" id="ingredientBox"/>
<Bitmap profile="fs25_multiTextOptionContainer"> <Bitmap profile="fs25_multiTextOptionContainer">
<MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickWaitPool" id="waitPoolOption"> <MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickWaitPool" id="waitPoolOption">
<Text profile="fs25_multiTextOptionTooltip" name="ignore" text="$l10n_spu_waitPool_tooltip"/> <Text profile="fs25_multiTextOptionTooltip" name="ignore" text="$l10n_spu_waitPool_tooltip"/>
@@ -41,20 +41,9 @@
</MultiTextOption> </MultiTextOption>
<Text profile="fs25_settingsMultiTextOptionTitle" text="$l10n_spu_loops"/> <Text profile="fs25_settingsMultiTextOptionTitle" text="$l10n_spu_loops"/>
</Bitmap> </Bitmap>
<Bitmap profile="fs25_multiTextOptionContainer">
<Text profile="spuSectionTitle" text="$l10n_spu_vehicles"/>
</Bitmap>
<BoxLayout profile="fs25_settingsLayout" id="vehicleBox"/>
<Bitmap profile="fs25_multiTextOptionContainer">
<Text profile="spuSectionTitle" text="$l10n_spu_waitPools"/>
</Bitmap>
<BoxLayout profile="fs25_settingsLayout" id="poolBox"/>
<Bitmap profile="fs25_multiTextOptionContainer">
<Text profile="spuInfoText" id="statusText"/>
</Bitmap>
</ScrollingLayout> </ScrollingLayout>
<!-- prefabs: klones pr. vare / traktor / pulje og fjernes fra layoutet ved initialize --> <!-- prefabs: klones pr. vare / traktor / tekstlinje og fjernes fra layoutet ved initialize -->
<Bitmap profile="fs25_multiTextOptionContainer" id="ingredientPrefab"> <Bitmap profile="fs25_multiTextOptionContainer" id="ingredientPrefab">
<BinaryOption profile="fs25_settingsBinaryOption" onClick="onClickIngredient" name="option"/> <BinaryOption profile="fs25_settingsBinaryOption" onClick="onClickIngredient" name="option"/>
<Text profile="fs25_settingsMultiTextOptionTitle" name="title"/> <Text profile="fs25_settingsMultiTextOptionTitle" name="title"/>
@@ -65,9 +54,14 @@
</BinaryOption> </BinaryOption>
<Text profile="fs25_settingsMultiTextOptionTitle" name="title"/> <Text profile="fs25_settingsMultiTextOptionTitle" name="title"/>
</Bitmap> </Bitmap>
<Bitmap profile="fs25_multiTextOptionContainer" id="poolPrefab"> <Bitmap profile="fs25_multiTextOptionContainer" id="textPrefab">
<Text profile="spuInfoText" name="title"/> <Text profile="spuInfoText" name="title"/>
</Bitmap> </Bitmap>
</GuiElement> </GuiElement>
</GuiElement>
<ThreePartBitmap profile="fs25_sliderDockedBg">
<ThreePartBitmap profile="fs25_sliderDockedBox">
<Slider profile="fs25_helpLineSlider" dataElementId="boxLayout"/>
</ThreePartBitmap>
</ThreePartBitmap>
</GUI> </GUI>
+2 -8
View File
@@ -5,14 +5,8 @@
<imageSliceId value="gui.icon_ingameMenu_vehicles"/> <imageSliceId value="gui.icon_ingameMenu_vehicles"/>
<imageColor value="$preset_fs25_colorMainDark"/> <imageColor value="$preset_fs25_colorMainDark"/>
</Profile> </Profile>
<Profile name="spuTabContainer" extends="emptyPanel" with="anchorStretchingYStretchingX pivotTopLeft"> <!-- Tekst-rækker (sektionstitler, Wait-pulje-linjer og statuslinjen) deler profil,
<position value="0px -90px"/> fordi de alle klones fra den samme textPrefab. -->
<absoluteSizeOffset value="0px 90px"/>
</Profile>
<Profile name="spuSectionTitle" extends="fs25_settingsMultiTextOptionTitle">
<textBold value="true"/>
<textUpperCase value="true"/>
</Profile>
<Profile name="spuInfoText" extends="fs25_settingsMultiTextOptionTitle"> <Profile name="spuInfoText" extends="fs25_settingsMultiTextOptionTitle">
<textBold value="false"/> <textBold value="false"/>
<size value="1400px 40px"/> <size value="1400px 40px"/>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107"> <modDesc descVersion="107">
<author>masterdraco</author> <author>masterdraco</author>
<version>1.9.0.0</version> <version>1.9.1.0</version>
<title> <title>
<en>AD Smart Pickup</en> <en>AD Smart Pickup</en>
</title> </title>