diff --git a/src/haxe/ru/m/data/DataStorage.hx b/src/haxe/ru/m/data/DataStorage.hx index 84e3aae..82573e7 100644 --- a/src/haxe/ru/m/data/DataStorage.hx +++ b/src/haxe/ru/m/data/DataStorage.hx @@ -34,37 +34,41 @@ class DefaultConverter extends Converter { } } -class DataFilter extends DefaultConverter { - private var builder:D -> Filter; +class MetaBuilder extends DefaultConverter { + private var builder:D -> DataMeta; - public function new(builder:D -> Filter) { + public function new(builder:D -> DataMeta) { super(); this.builder = builder; } - public function build(item:D):Filter { + public function build(item:D):DataMeta { return builder(item); } } +typedef DataMeta = Filter; + +typedef IdValue = {id:I, meta:DataMeta}; + class DataStorage implements IDataSource { inline private static var DATA_KEY = "item"; private var name:String; - private var filterConverter:DataFilter; + private var metaBuilder:MetaBuilder; private var idConverter:Converter; private var dataConverter:Converter; private var indexData:SharedObject; public function new( name:String, - filterConverter:DataFilter, + metaBuilder:MetaBuilder, idConverter:Converter = null, dataConverter:Converter = null ) { this.name = name; - this.filterConverter = filterConverter; + this.metaBuilder = metaBuilder; this.idConverter = idConverter != null ? idConverter : new DefaultConverter(); this.dataConverter = dataConverter != null ? dataConverter : new DefaultConverter(); this.indexData = SharedObject.getLocal('${name}/index'); @@ -78,21 +82,38 @@ class DataStorage implements IDataSource { return idConverter.deserialize(data); } - private function buildFilter(item:D):Filter { - return filterConverter.build(item); + private function buildMeta(item:D):DataMeta { + return metaBuilder.build(item); } - private function checkFilter(filter:Null, data:Filter):Bool { + private function checkFilter(filter:Null, meta:DataMeta):Bool { if (filter != null) { for (k => v in filter) { - if (data.get(k) != v) { - return false; - } + if (meta.get(k) != v) { + return false; + } } } return true; } + private function sort(order:Order, values:Array>):Void { + if (order != null) { + values.sort((a:IdValue, b:IdValue) -> { + for (item in order) { + var av = a.meta.get(item.key); + var bv = b.meta.get(item.key); + if (av > bv) { + return item.reverse ? - 1 : 1; + } else if (av < bv) { + return item.reverse ? 1 : -1; + } + } + return 0; + }); + } + } + private function serialize(item:D):Dynamic { return dataConverter.serialize(item); } @@ -103,23 +124,25 @@ class DataStorage implements IDataSource { public function getIndexPage(page:Page):Promise> { var data:DynamicAccess = indexData.data; - var result:Array = []; + var values:Array> = []; for (k => v in data) { - var filter = this.filterConverter.deserialize(v); - if (checkFilter(page.filter, filter)) { - result.push(desirealizeId(k)); + var meta = metaBuilder.deserialize(v); + if (checkFilter(page.filter, meta)) { + values.push({id: desirealizeId(k), meta: meta}); } } + sort(page.order, values); + var result:Array = values.slice(page.index * page.count, page.index * page.count + page.count).map(value -> value.id); return Promise.promise({ page: page, - total: result.length, + total: values.length, data: result, }); } public function getPage(page:Page):Promise> { return getIndexPage(page).pipe((indexPage:DataPage) -> { - Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array) -> { + return Promise.whenAll([for (id in indexPage.data) get(id)]).then((data:Array) -> { return { page: indexPage.page, total: indexPage.total, @@ -144,7 +167,7 @@ class DataStorage implements IDataSource { var itemData = SharedObject.getLocal('${name}/${stringId}'); itemData.setProperty(DATA_KEY, serialize(item)); itemData.flush(); - indexData.setProperty(stringId, filterConverter.serialize(filterConverter.build(item))); + indexData.setProperty(stringId, metaBuilder.serialize(metaBuilder.build(item))); indexData.flush(); return Promise.promise(item); } diff --git a/src/haxe/ru/m/data/IDataSource.hx b/src/haxe/ru/m/data/IDataSource.hx index bfe2980..14fd55b 100644 --- a/src/haxe/ru/m/data/IDataSource.hx +++ b/src/haxe/ru/m/data/IDataSource.hx @@ -4,10 +4,12 @@ import promhx.Promise; typedef Filter = Map; +typedef Order = Array<{key: String, ?reverse:Bool}>; + typedef Page = { var index:Int; var count:Int; - @:optional var order:Array; + @:optional var order:Order; @:optional var filter:Filter; } diff --git a/src/haxe/ru/m/puzzlez/core/Game.hx b/src/haxe/ru/m/puzzlez/core/Game.hx index 2b4577c..00f7c49 100644 --- a/src/haxe/ru/m/puzzlez/core/Game.hx +++ b/src/haxe/ru/m/puzzlez/core/Game.hx @@ -27,9 +27,10 @@ class Game implements IGame { case READY: shuffle(); state.status = STARTED; + signal.emit(START(state)); case _: + signal.emit(RESUME(state)); } - signal.emit(START(state)); } public function shuffle():Void { diff --git a/src/haxe/ru/m/puzzlez/core/GameEvent.hx b/src/haxe/ru/m/puzzlez/core/GameEvent.hx index 6a03050..e458fc1 100644 --- a/src/haxe/ru/m/puzzlez/core/GameEvent.hx +++ b/src/haxe/ru/m/puzzlez/core/GameEvent.hx @@ -14,6 +14,7 @@ enum GameChange { enum GameEvent { START(state:GameState); + RESUME(state:GameState); ACTION(action:GameAction); CHANGE(change:GameChange); COMPLETE; diff --git a/src/haxe/ru/m/puzzlez/render/Render.hx b/src/haxe/ru/m/puzzlez/render/Render.hx index b0c45b8..fd77cd4 100644 --- a/src/haxe/ru/m/puzzlez/render/Render.hx +++ b/src/haxe/ru/m/puzzlez/render/Render.hx @@ -71,7 +71,7 @@ class Render extends SpriteView implements IRender { public function onGameEvent(event:GameEvent):Void { switch event { - case START(state): + case START(state) | RESUME(state): onStart(state); case CHANGE(PART_UPDATE(id, TABLE(point))): var part:PartView = parts[id]; diff --git a/src/haxe/ru/m/puzzlez/storage/GameStorage.hx b/src/haxe/ru/m/puzzlez/storage/GameStorage.hx index 495df5e..f8505ec 100644 --- a/src/haxe/ru/m/puzzlez/storage/GameStorage.hx +++ b/src/haxe/ru/m/puzzlez/storage/GameStorage.hx @@ -6,12 +6,12 @@ import ru.m.data.DataStorage; @:provide class GameStorage extends DataStorage { inline private static var NAME = "game"; - inline private static var VERSION = 3; + inline private static var VERSION = 4; public function new() { super( '${NAME}/${VERSION}', - new DataFilter(item -> ["status" => item.status]), + new MetaBuilder(item -> ["status" => item.status, "date" => Date.now().toString()]), new Converter(id -> id.toString(), data -> ImageId.fromString(data)) ); } diff --git a/src/haxe/ru/m/puzzlez/view/GameFrame.hx b/src/haxe/ru/m/puzzlez/view/GameFrame.hx index 956e481..6d028b5 100644 --- a/src/haxe/ru/m/puzzlez/view/GameFrame.hx +++ b/src/haxe/ru/m/puzzlez/view/GameFrame.hx @@ -40,7 +40,9 @@ import ru.m.puzzlez.view.popup.PreviewPopup; } override public function onHide():Void { - save(); + if (saveTimer != null) { + save(); + } if (game != null) { render.signal.disconnect(game.signal.emit); game.stop(); @@ -50,8 +52,8 @@ import ru.m.puzzlez.view.popup.PreviewPopup; } private function toSave():Void { - if (saveTimer != null) { - saveTimer = Timer.delay(save, 500); + if (saveTimer == null) { + saveTimer = Timer.delay(save, 5000); } } diff --git a/src/haxe/ru/m/puzzlez/view/GameListFrame.hx b/src/haxe/ru/m/puzzlez/view/GameListFrame.hx index a41dbd5..4970cb7 100644 --- a/src/haxe/ru/m/puzzlez/view/GameListFrame.hx +++ b/src/haxe/ru/m/puzzlez/view/GameListFrame.hx @@ -1,6 +1,7 @@ package ru.m.puzzlez.view; import haxework.view.data.DataView; +import haxework.view.form.ToggleButtonView; import haxework.view.frame.FrameSwitcher; import haxework.view.frame.FrameView; import haxework.view.popup.ConfirmView; @@ -14,7 +15,8 @@ import ru.m.puzzlez.view.PuzzleImageView; public static var ID(default, never) = "game_list"; - @:view var images:ActionDataView; + @:view("images") var imagesView:ActionDataView; + @:view("pages") var pagesView:DataView; @:provide var switcher:FrameSwitcher; @:provide var storage:GameStorage; @@ -22,28 +24,48 @@ import ru.m.puzzlez.view.PuzzleImageView; private var status:GameStatus; private var page:Page; + private var data(default, set):DataPage; + + private function set_data(value) { + data = value; + imagesView.data = data.data; + pagesView.data = [for (i in 0...Std.int(data.total / data.page.count) + 1) i]; + return data; + } + public function new() { super(ID); - page = {index: 0, count: 10}; + page = {index: 0, count: 6, order: [{key: "date", reverse: true}]}; + } + + private function pageFactory(index:Int, value:Int):ToggleButtonView { + var result = new ToggleButtonView(); + result.text = '${value}'; + result.on = data.page.index == value; + return result; } override public function onShow(data:GameStatus):Void { status = data; page.filter = ["status" => status]; - storage.getIndexPage(page).then(page -> images.data = page.data); + refresh(); } private function start(id:ImageId):Void { storage.get(id).then(state -> switcher.change(GameFrame.ID, state)); } + private function refresh():Void { + storage.getIndexPage(page).then(data -> this.data = data); + } + private function onAction(imageId:ImageId, action:Action):Void { switch action { case CLEAN: ConfirmView.confirm("Delete state?").then(result -> { if (result) { storage.delete(imageId); - storage.getIndexPage(page).then(page -> images.data = page.data); + refresh(); } }); case _: diff --git a/src/haxe/ru/m/puzzlez/view/GameListFrame.yaml b/src/haxe/ru/m/puzzlez/view/GameListFrame.yaml index e559f78..32d8baf 100644 --- a/src/haxe/ru/m/puzzlez/view/GameListFrame.yaml +++ b/src/haxe/ru/m/puzzlez/view/GameListFrame.yaml @@ -13,6 +13,18 @@ views: +onDataAction: ~onAction geometry.margin: 5 overflow.y: scroll + - id: pages + $type: haxework.view.data.DataView + geometry.width: 100% + layout: + $type: haxework.view.layout.TailLayout + margin: 5 + factory: ~pageFactory + +onDataSelect: | + ~(index) -> { + page.index = index; + refresh(); + } - $type: haxework.view.form.ButtonView text: Back geometry.position: absolute diff --git a/src/haxe/ru/m/puzzlez/view/StartFrame.hx b/src/haxe/ru/m/puzzlez/view/StartFrame.hx index e9914e3..b17ec4a 100644 --- a/src/haxe/ru/m/puzzlez/view/StartFrame.hx +++ b/src/haxe/ru/m/puzzlez/view/StartFrame.hx @@ -1,17 +1,18 @@ package ru.m.puzzlez.view; -import ru.m.update.Updater; import haxework.view.data.DataView; import haxework.view.form.ButtonView; import haxework.view.frame.FrameSwitcher; import haxework.view.frame.FrameView; import haxework.view.popup.ConfirmView; +import ru.m.data.IDataSource; import ru.m.puzzlez.core.GameState.GameStatus; import ru.m.puzzlez.source.AssetSource; import ru.m.puzzlez.source.FileSource; import ru.m.puzzlez.source.PixabaySource; import ru.m.puzzlez.storage.GameStorage; import ru.m.puzzlez.storage.ImageStorage; +import ru.m.update.Updater; @:template class StartFrame extends FrameView { public static var ID = "start"; @@ -38,12 +39,14 @@ import ru.m.puzzlez.storage.ImageStorage; } private function refresh():Void { - gameStorage.getIndexPage({index: 0, count: 0, filter: ["status" => STARTED]}).then(page -> { - var count = page.total; - loadButton.text = 'Resume (${count})'; - loadButton.disabled = count == 0; + var startedRequest:Page = {index: 0, count: 0, filter: ["status" => STARTED]}; + gameStorage.getIndexPage(startedRequest).then(page -> { + var total = page.total; + loadButton.text = 'Resume (${total})'; + loadButton.disabled = total == 0; }); - gameStorage.getIndexPage({index: 0, count: 0, filter: ["status" => COMPLETE]}).then(page -> { + var completeRequest:Page = {index: 0, count: 0, filter: ["status" => COMPLETE]}; + gameStorage.getIndexPage(completeRequest).then(page -> { var total = page.total; completeButton.text = 'Complete (${total})'; completeButton.disabled = total == 0;