From da4f2b8c2f1810528cac4001aa9f97b3a2c30326 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Fri, 30 Mar 2018 15:34:00 -0300 Subject: [PATCH 001/315] Fix zoom in unresponsive in radar mode --- scripts/system/+android/radar.js | 47 +++++++++++++++++--------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 455299dd5f..2b87f8955d 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -21,7 +21,7 @@ function printd(str) { } var radar = false; -var radarHeight = 10; // camera position meters above the avatar +var radarHeight = MyAvatar.position.y + 10; // camera position (absolute y) var tablet; var RADAR_CAMERA_OFFSET = -1; // 1 meter below the avatar @@ -46,11 +46,11 @@ var uniqueColor; function moveTo(position) { if (radar) { MyAvatar.position = position; - Camera.position = Vec3.sum(MyAvatar.position, { - x : 0, + Camera.position = { + x : MyAvatar.position.x, y : radarHeight, - z : 0 - }); + z : MyAvatar.position.z + }; } } @@ -386,12 +386,12 @@ function pinchUpdate(event) { radarHeight -= pinchIncrement; } } - var deltaHeight = avatarY + radarHeight - Camera.position.y; - Camera.position = Vec3.sum(Camera.position, { - x : 0, - y : deltaHeight, - z : 0 - }); + Camera.position = { + x: Camera.position.x, + y:radarHeight, + z:Camera.position.z + }; + if (!draggingCamera) { startedDraggingCamera = true; draggingCamera = true; @@ -401,7 +401,8 @@ function pinchUpdate(event) { } function isInsideSquare(coords0, coords1, halfside) { - return Math.abs(coords0.x - coords1.x) <= halfside + return coords0 != undefined && coords1!= undefined && + Math.abs(coords0.x - coords1.x) <= halfside && Math.abs(coords0.y - coords1.y) <= halfside; } @@ -412,7 +413,7 @@ function dragScrollUpdate(event) { // drag management var pickRay = Camera.computePickRay(event.x, event.y); var dragAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, - radarHeight)); + radarHeight-MyAvatar.position.y)); if (lastDragAt === undefined || lastDragAt === null) { lastDragAt = dragAt; @@ -722,7 +723,7 @@ function Teleporter() { return { dragTeleportBegin : function(event) { printd("[newTeleport] TELEPORT began"); - var overlayDimensions = entityIconModelDimensions(); + var overlayDimensions = entityIconModelDimensions(MyAvatar.position.y); // var destination = computeDestination(event, MyAvatar.position, // Camera.position, radarHeight); // Dimension teleport and cancel overlays (not show them yet) @@ -843,7 +844,7 @@ var avatarIconDimensionsVal = { }; function avatarIconPlaneDimensions() { // given the current height, give a size - var xy = -0.003531 * radarHeight + 0.1; + var xy = -0.003531 * (radarHeight - MyAvatar.position.y) + 0.1; avatarIconDimensionsVal.x = Math.abs(xy); avatarIconDimensionsVal.y = Math.abs(xy); // reuse object @@ -1165,9 +1166,10 @@ var entityIconModelDimensionsVal = { y : 0.00001, z : 0 }; -function entityIconModelDimensions() { +function entityIconModelDimensions(y) { // given the current height, give a size - var xz = -0.002831 * radarHeight + 0.1; + // TODO: receive entity.position.y and substract to radarHeight + var xz = -0.002831 * (radarHeight - y) + 0.1; entityIconModelDimensionsVal.x = xz; entityIconModelDimensionsVal.z = xz; // reuse object @@ -1177,8 +1179,8 @@ function entityIconModelDimensions() { * entityIconPlaneDimensions: similar to entityIconModelDimensions but using xy * plane */ -function entityIconPlaneDimensions() { - var dim = entityIconModelDimensions(); +function entityIconPlaneDimensions(y) { + var dim = entityIconModelDimensions(y); var z = dim.z; dim.z = dim.y; dim.y = z; @@ -1255,15 +1257,15 @@ function hideAllEntitiesIcons() { function renderAllEntitiesIcons() { var entityPos; var entityProps; - var iconDimensions = entityIconModelDimensions(); - var planeDimensions = entityIconPlaneDimensions(); // plane overlays uses - // xy instead of xz + for ( var QUuid in entitiesData) { if (entitiesData.hasOwnProperty(QUuid)) { entityProps = Entities.getEntityProperties(QUuid, [ "position", "visible" ]); if (entityProps != null) { entityPos = entityProps.position; + var planeDimensions = entityIconPlaneDimensions(entityPos.y); // plane overlays uses + // xy instead of xz if (entitiesData[QUuid].icon != undefined && entityPos) { var iconPos = findLineToHeightIntersectionCoords( entityPos.x, @@ -1276,6 +1278,7 @@ function renderAllEntitiesIcons() { printd("entity icon pos bad for " + QUuid); continue; } + var iconDimensions = entityIconModelDimensions(entityPos.y); var dimensions = entitiesData[QUuid].planar ? planeDimensions : iconDimensions; Overlays.editOverlay(entitiesData[QUuid].icon, { From fc86525863df9eb5a2a01c948fa08f30e6cf616d Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 4 Apr 2018 12:00:06 +0200 Subject: [PATCH 002/315] Cleaned up a bit shadow map clear --- libraries/render-utils/src/RenderShadowTask.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 69c5b3c689..faa5889307 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -149,9 +149,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con batch.setStateScissorRect(viewport); batch.setFramebuffer(fbo); - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, true); + batch.clearDepthFramebuffer(1.0, false); glm::mat4 projMat; Transform viewMat; From 36bc5f841627e170cb0d15c35586106d5c176eb5 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 5 Apr 2018 15:35:08 -0300 Subject: [PATCH 003/315] Fix the view snap when user starts zooming --- scripts/system/+android/radar.js | 37 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 2b87f8955d..120bec33c3 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -21,7 +21,8 @@ function printd(str) { } var radar = false; -var radarHeight = MyAvatar.position.y + 10; // camera position (absolute y) +var RADAR_HEIGHT_INIT_DELTA = 10; +var radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; // camera position (absolute y) var tablet; var RADAR_CAMERA_OFFSET = -1; // 1 meter below the avatar @@ -386,6 +387,7 @@ function pinchUpdate(event) { radarHeight -= pinchIncrement; } } + Camera.position = { x: Camera.position.x, y:radarHeight, @@ -655,6 +657,7 @@ function Teleporter() { return; } + Camera.position = Vec3.sum(Camera.position, { x : xDelta, y : 0, @@ -1301,11 +1304,12 @@ function startRadar() { saveAllOthersAvatarsData(); Camera.mode = "independent"; - Camera.position = Vec3.sum(MyAvatar.position, { - x : 0, - y : radarHeight, - z : 0 - }); + Camera.position = { + x : MyAvatar.position.x, + y : radarHeight, + z : MyAvatar.position.z + }; + Camera.orientation = Quat.fromPitchYawRollDegrees(-90, 0, 0); radar = true; @@ -1394,15 +1398,25 @@ function connectRadarModeEvents() { Controller.keyPressEvent.connect(keyPressEvent); Controller.mousePressEvent.connect(mousePress); // single click/touch Controller.touchUpdateEvent.connect(touchUpdate); + Window.domainChanged.connect(domainChanged); MyAvatar.positionGoneTo.connect(positionGoneTo); } -function positionGoneTo() { - Camera.position = Vec3.sum(MyAvatar.position, { - x : 0, +function domainChanged() { + radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; + Camera.position = { + x : MyAvatar.position.x, y : radarHeight, - z : 0 - }); + z : MyAvatar.position.z + }; +} + +function positionGoneTo() { + Camera.position = { + x : MyAvatar.position.x, + y : radarHeight, + z : MyAvatar.position.z + }; } function disconnectRadarModeEvents() { @@ -1411,6 +1425,7 @@ function disconnectRadarModeEvents() { Controller.mousePressEvent.disconnect(mousePress); Controller.touchUpdateEvent.disconnect(touchUpdate); MyAvatar.positionGoneTo.disconnect(positionGoneTo); + Window.domainChanged.disconnect(domainChanged); } function init() { From 573f399023eafd625212284f827ea3207f006f76 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 6 Apr 2018 14:45:16 +0200 Subject: [PATCH 004/315] Fixed incorrect shadow frustum far clip computation due to not taking into account shadow receivers --- .../render-utils/src/RenderShadowTask.cpp | 27 ++++++++++-------- libraries/render-utils/src/RenderShadowTask.h | 2 +- libraries/render/src/render/CullTask.cpp | 28 +++++++++++++------ libraries/render/src/render/CullTask.h | 2 +- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index faa5889307..fbb4bba263 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -230,12 +230,11 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - // Enable models to not cast shadows (otherwise, models will always cast shadows) - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster(); + static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); - const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); + const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); - const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); + const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not @@ -259,21 +258,22 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); const auto cascadeSetupOutput = task.addJob(jobName, i, _cullFunctor, tagBits, tagMask); - const auto shadowFilter = cascadeSetupOutput.getN(0); + const auto shadowRenderFilter = cascadeSetupOutput.getN(0); + const auto shadowBoundsFilter = cascadeSetupOutput.getN(1); auto antiFrustum = render::Varying(ViewFrustumPointer()); - cascadeFrustums[i] = cascadeSetupOutput.getN(1); + cascadeFrustums[i] = cascadeSetupOutput.getN(2); if (i > 1) { antiFrustum = cascadeFrustums[i - 2]; } // CPU jobs: finer grained culling - const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying(); + const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowRenderFilter, shadowBoundsFilter, antiFrustum).asVarying(); const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW); // GPU jobs: Render to shadow map sprintf(jobName, "RenderShadowMap%d", i); task.addJob(jobName, culledShadowItemsAndBounds, shapePlumber, i); - task.addJob("ShadowCascadeTeardown", shadowFilter); + task.addJob("ShadowCascadeTeardown", shadowRenderFilter); } task.addJob("ShadowTeardown", setupOutput); @@ -404,7 +404,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster(); + auto baseFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); + // Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers) + output.edit1() = baseFilter; + // First item filter is to filter items to render in shadow map (so only keep casters) + output.edit0() = baseFilter.withShadowCaster(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); @@ -417,10 +421,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon texelSize *= minTexelCount; _cullFunctor._minSquareSize = texelSize * texelSize; - output.edit1() = cascadeFrustum; + output.edit2() = cascadeFrustum; } else { output.edit0() = ItemFilter::Builder::nothing(); - output.edit1() = ViewFrustumPointer(); + output.edit1() = ItemFilter::Builder::nothing(); + output.edit2() = ViewFrustumPointer(); } } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 98b70c0c9f..19ffcb4234 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -118,7 +118,7 @@ private: class RenderShadowCascadeSetup { public: - using Outputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) : diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index f04427540a..b5819f114f 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -368,17 +368,19 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input RenderArgs* args = renderContext->args; const auto& inShapes = inputs.get0(); - const auto& filter = inputs.get1(); - const auto& antiFrustum = inputs.get2(); + const auto& cullFilter = inputs.get1(); + const auto& boundsFilter = inputs.get2(); + const auto& antiFrustum = inputs.get3(); auto& outShapes = outputs.edit0(); auto& outBounds = outputs.edit1(); outShapes.clear(); outBounds = AABox(); - if (!filter.selectsNothing()) { + if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) { auto& details = args->_details.edit(_detailType); Test test(_cullFunctor, args, details, antiFrustum); + auto scene = args->_scene; for (auto& inItems : inShapes) { auto key = inItems.first; @@ -393,16 +395,26 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input if (antiFrustum == nullptr) { for (auto& item : inItems.second) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { - outItems->second.emplace_back(item); - outBounds += item.bound; + const auto shapeKey = scene->getItem(item.id).getKey(); + if (cullFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + } + if (boundsFilter.test(shapeKey)) { + outBounds += item.bound; + } } } } else { for (auto& item : inItems.second) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { - outItems->second.emplace_back(item); - outBounds += item.bound; - } + const auto shapeKey = scene->getItem(item.id).getKey(); + if (cullFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + } + if (boundsFilter.test(shapeKey)) { + outBounds += item.bound; + } + } } } details._rendered += (int)outItems->second.size(); diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 3c5a30de89..47abe8a960 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -110,7 +110,7 @@ namespace render { class CullShapeBounds { public: - using Inputs = render::VaryingSet3; + using Inputs = render::VaryingSet4; using Outputs = render::VaryingSet2; using JobModel = Job::ModelIO; From d5c252c9ad70cfb2e46c95b75cd3a71d7b8f5196 Mon Sep 17 00:00:00 2001 From: Cristian Duarte Date: Mon, 9 Apr 2018 16:24:03 -0300 Subject: [PATCH 005/315] Use MyAvatar.goToLocation in radar teleport. Use MyAvatar.goToLocation in radar teleport to prevent synchronization issues that make it fail occasionally when directly modifying its world position. It will work just as teleport.js does. --- scripts/system/+android/radar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 120bec33c3..8100cc0887 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -46,11 +46,11 @@ var uniqueColor; function moveTo(position) { if (radar) { - MyAvatar.position = position; + MyAvatar.goToLocation(position, false); Camera.position = { - x : MyAvatar.position.x, + x : position.x, y : radarHeight, - z : MyAvatar.position.z + z : position.z }; } } From 63e17e87b0fa4e00db8b871e731c34684a1f4f08 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 10 Apr 2018 19:21:23 -0300 Subject: [PATCH 006/315] Fix radar initial position. Clean radar.js --- scripts/system/+android/radar.js | 232 ++----------------------------- 1 file changed, 8 insertions(+), 224 deletions(-) diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 8100cc0887..67ba896207 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -90,46 +90,6 @@ function keyPressEvent(event) { } } -function actionOnObjectFromEvent(event) { - var rayIntersection = findRayIntersection(Camera.computePickRay(event.x, - event.y)); - if (rayIntersection && rayIntersection.intersects - && rayIntersection.overlayID) { - printd("found overlayID touched " + rayIntersection.overlayID); - if (entitiesByOverlayID[rayIntersection.overlayID]) { - var entity = Entities.getEntityProperties( - entitiesByOverlayID[rayIntersection.overlayID], - [ "sourceUrl" ]); - App.openUrl(entity.sourceUrl); - return true; - } - } - if (rayIntersection && rayIntersection.intersects - && rayIntersection.entityID && rayIntersection.properties) { - printd("found " + rayIntersection.entityID + " of type " - + rayIntersection.properties.type); - if (rayIntersection.properties.type == "Web") { - printd("found web element to " - + rayIntersection.properties.sourceUrl); - App.openUrl(rayIntersection.properties.sourceUrl); - return true; - } - } - return false; -} - -function mousePress(event) { - mousePressOrTouchEnd(event); -} - -function mousePressOrTouchEnd(event) { - if (radar) { - if (actionOnObjectFromEvent(event)) { - return; - } - } -} - function toggleRadarMode() { if (radar) { endRadar(); @@ -230,9 +190,6 @@ function touchEnd(event) { if (analyzeDoubleTap(event)) return; // double tap detected, finish - if (radar) { - mousePressOrTouchEnd(event); - } } /** @@ -726,7 +683,7 @@ function Teleporter() { return { dragTeleportBegin : function(event) { printd("[newTeleport] TELEPORT began"); - var overlayDimensions = entityIconModelDimensions(MyAvatar.position.y); + var overlayDimensions = teleportIconModelDimensions(MyAvatar.position.y); // var destination = computeDestination(event, MyAvatar.position, // Camera.position, radarHeight); // Dimension teleport and cancel overlays (not show them yet) @@ -1125,39 +1082,10 @@ function renderAllOthersAvatarIcons() { } } -function entityAdded(entityID) { - printd("Entity added " + entityID); - var props = Entities.getEntityProperties(entityID, [ "type" ]); - printd("Entity added " + entityID + " PROPS " + JSON.stringify(props)); - if (props && props.type == "Web") { - printd("Entity Web added " + entityID); - saveEntityData(entityID, true); - } -} - -function entityRemoved(entityID) { - printd("Entity removed " + entityID); - var props = Entities.getEntityProperties(entityID, [ "type" ]); - if (props && props.type == "Web") { - print("Entity Web removed " + entityID); - removeEntityData(entityID); - } -} - /******************************************************************************* * Entities (to remark) cache structure for showing entities markers ******************************************************************************/ -var entitiesData = {}; // by entityID -var entitiesByOverlayID = {}; // by overlayID -var entitiesIcons = []; // a parallel list of icons (overlays) to easily run - // through - -var ICON_ENTITY_WEB_MODEL_URL = Script.resolvePath("../assets/images/web.svg"); -var ICON_ENTITY_IMG_MODEL_URL = Script - .resolvePath("../assets/models/teleport-cancel.fbx"); // FIXME - use - // correct - // model&texture var ICON_ENTITY_DEFAULT_DIMENSIONS = { x : 0.10, y : 0.00001, @@ -1169,7 +1097,7 @@ var entityIconModelDimensionsVal = { y : 0.00001, z : 0 }; -function entityIconModelDimensions(y) { +function teleportIconModelDimensions(y) { // given the current height, give a size // TODO: receive entity.position.y and substract to radarHeight var xz = -0.002831 * (radarHeight - y) + 0.1; @@ -1178,122 +1106,6 @@ function entityIconModelDimensions(y) { // reuse object return entityIconModelDimensionsVal; } -/* - * entityIconPlaneDimensions: similar to entityIconModelDimensions but using xy - * plane - */ -function entityIconPlaneDimensions(y) { - var dim = entityIconModelDimensions(y); - var z = dim.z; - dim.z = dim.y; - dim.y = z; - return dim; -} - -function currentOverlayForEntity(QUuid) { - if (entitiesData[QUuid] != undefined) { - return entitiesData[QUuid].icon; - } else { - return null; - } -} - -function saveEntityData(QUuid, planar) { - if (QUuid == null) - return; - var entity = Entities.getEntityProperties(QUuid, [ "position" ]); - printd("entity added save entity " + QUuid); - if (entitiesData[QUuid] != undefined) { - entitiesData[QUuid].position = entity.position; - } else { - var entityIcon = Overlays.addOverlay("image3d", { - subImage : { - x : 0, - y : 0, - width : 150, - height : 150 - }, - url : ICON_ENTITY_WEB_MODEL_URL, - dimensions : ICON_ENTITY_DEFAULT_DIMENSIONS, - visible : false, - ignoreRayIntersection : false, - orientation : Quat.fromPitchYawRollDegrees(-90, 0, 0) - }); - - entitiesIcons.push(entityIcon); - entitiesData[QUuid] = { - position : entity.position, - icon : entityIcon - }; - entitiesByOverlayID[entityIcon] = QUuid; - } -} - -function removeEntityData(QUuid) { - if (QUuid == null) - return; - - var itsOverlay = currentOverlayForEntity(QUuid); - if (itsOverlay != null) { - Overlays.deleteOverlay(itsOverlay); - delete entitiesByOverlayID[itsOverlay]; - } - var idx = entitiesIcons.indexOf(itsOverlay); - entitiesIcons.splice(idx, 1); - - delete entitiesData[QUuid]; -} - -/******************************************************************************* - * Entities to remark Icon/Markers rendering - ******************************************************************************/ - -function hideAllEntitiesIcons() { - var len = entitiesIcons.length; - for (var i = 0; i < len; i++) { - Overlays.editOverlay(entitiesIcons[i], { - visible : false - }); - } -} - -function renderAllEntitiesIcons() { - var entityPos; - var entityProps; - - for ( var QUuid in entitiesData) { - if (entitiesData.hasOwnProperty(QUuid)) { - entityProps = Entities.getEntityProperties(QUuid, [ "position", - "visible" ]); - if (entityProps != null) { - entityPos = entityProps.position; - var planeDimensions = entityIconPlaneDimensions(entityPos.y); // plane overlays uses - // xy instead of xz - if (entitiesData[QUuid].icon != undefined && entityPos) { - var iconPos = findLineToHeightIntersectionCoords( - entityPos.x, - entityPos.y - + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, - entityPos.z, Camera.position.x, Camera.position.y, - Camera.position.z, Camera.position.y - - RADAR_CAMERA_DISTANCE_TO_ICONS); - if (!iconPos) { - printd("entity icon pos bad for " + QUuid); - continue; - } - var iconDimensions = entityIconModelDimensions(entityPos.y); - var dimensions = entitiesData[QUuid].planar ? planeDimensions - : iconDimensions; - Overlays.editOverlay(entitiesData[QUuid].icon, { - visible : entityProps.visible, - dimensions : dimensions, - position : iconPos - }); - } - } - } - } -} /******************************************************************************* * @@ -1304,11 +1116,7 @@ function startRadar() { saveAllOthersAvatarsData(); Camera.mode = "independent"; - Camera.position = { - x : MyAvatar.position.x, - y : radarHeight, - z : MyAvatar.position.z - }; + initCameraOverMyAvatar(); Camera.orientation = Quat.fromPitchYawRollDegrees(-90, 0, 0); radar = true; @@ -1326,7 +1134,6 @@ function endRadar() { Controller.setVPadEnabled(true); disconnectRadarModeEvents(); - hideAllEntitiesIcons(); hideAllAvatarIcons(); } @@ -1360,12 +1167,10 @@ function updateRadar() { // Update avatar icons if (startedDraggingCamera) { hideAllAvatarIcons(); - hideAllEntitiesIcons(); startedDraggingCamera = false; } else if (!draggingCamera) { renderMyAvatarIcon(); renderAllOthersAvatarIcons(); - renderAllEntitiesIcons(); } } @@ -1373,36 +1178,15 @@ function valueIfDefined(value) { return value !== undefined ? value : ""; } -function entitiesAnalysis() { - var ids = Entities.findEntitiesInFrustum(Camera.frustum); - var entities = []; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - var properties = Entities.getEntityProperties(id); - entities.push({ - id : id, - name : properties.name, - type : properties.type, - url : properties.type == "Model" ? properties.modelURL : "", - sourceUrl : properties.sourceUrl, - locked : properties.locked, - visible : properties.visible, - drawCalls : valueIfDefined(properties.renderInfo.drawCalls), - hasScript : properties.script !== "" - }); - } -} - function connectRadarModeEvents() { Script.update.connect(updateRadar); // 60Hz loop Controller.keyPressEvent.connect(keyPressEvent); - Controller.mousePressEvent.connect(mousePress); // single click/touch Controller.touchUpdateEvent.connect(touchUpdate); Window.domainChanged.connect(domainChanged); MyAvatar.positionGoneTo.connect(positionGoneTo); } -function domainChanged() { +function initCameraOverMyAvatar() { radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; Camera.position = { x : MyAvatar.position.x, @@ -1411,6 +1195,10 @@ function domainChanged() { }; } +function domainChanged() { + initCameraOverMyAvatar(); +} + function positionGoneTo() { Camera.position = { x : MyAvatar.position.x, @@ -1422,7 +1210,6 @@ function positionGoneTo() { function disconnectRadarModeEvents() { Script.update.disconnect(updateRadar); Controller.keyPressEvent.disconnect(keyPressEvent); - Controller.mousePressEvent.disconnect(mousePress); Controller.touchUpdateEvent.disconnect(touchUpdate); MyAvatar.positionGoneTo.disconnect(positionGoneTo); Window.domainChanged.disconnect(domainChanged); @@ -1436,7 +1223,4 @@ function init() { AvatarList.avatarAddedEvent.connect(avatarAdded); AvatarList.avatarRemovedEvent.connect(avatarRemoved); - - Entities.addingEntity.connect(entityAdded); - Entities.deletingEntity.connect(entityRemoved); } From 120de92c9e284bf5b792bc8d85845536583204f8 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 18 Apr 2018 15:52:02 -0300 Subject: [PATCH 007/315] Add login activity --- android/app/src/main/AndroidManifest.xml | 9 +- android/app/src/main/cpp/native.cpp | 76 ++++- .../hifiinterface/GotoActivity.java | 14 +- .../hifiinterface/HomeActivity.java | 105 +++--- .../hifiinterface/InterfaceActivity.java | 8 +- .../hifiinterface/LoginActivity.java | 106 ++++++ .../hifiinterface/PermissionChecker.java | 2 +- .../QtPreloader/QtPreloader.java | 315 ------------------ .../hifiinterface/SplashActivity.java | 32 ++ .../qt5/android/bindings/QtActivity.java | 8 +- .../app/src/main/res/drawable/hifi_header.xml | 50 +++ .../src/main/res/drawable/rounded_button.xml | 24 ++ .../src/main/res/drawable/rounded_edit.xml | 7 + .../app/src/main/res/layout/activity_goto.xml | 4 +- .../app/src/main/res/layout/activity_home.xml | 25 +- .../src/main/res/layout/activity_login.xml | 106 ++++++ .../src/main/res/layout/activity_splash.xml | 12 + android/app/src/main/res/values/colors.xml | 5 + android/app/src/main/res/values/strings.xml | 7 + android/app/src/main/res/values/styles.xml | 9 +- interface/src/AndroidHelper.cpp | 29 ++ interface/src/AndroidHelper.h | 16 +- interface/src/Application.cpp | 4 + 23 files changed, 577 insertions(+), 396 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java delete mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java create mode 100644 android/app/src/main/res/drawable/hifi_header.xml create mode 100644 android/app/src/main/res/drawable/rounded_button.xml create mode 100644 android/app/src/main/res/drawable/rounded_edit.xml create mode 100644 android/app/src/main/res/layout/activity_login.xml create mode 100644 android/app/src/main/res/layout/activity_splash.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ed9caee58b..8828335cd1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -47,8 +47,9 @@ - + android:theme="@style/AppTheme" /> + + + diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index c71be76b3e..3fad01db9b 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -22,7 +22,9 @@ #include #include "AndroidHelper.h" -QAndroidJniObject __activity; +QAndroidJniObject __interfaceActivity; +QAndroidJniObject __loginActivity; +QAndroidJniObject __loadCompleteListener; void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (!message.isEmpty()) { @@ -142,16 +144,16 @@ void unpackAndroidAssets() { extern "C" { JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) { - qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId(); g_assetManager = AAssetManager_fromJava(env, asset_mgr); - __activity = QAndroidJniObject(instance); + qRegisterMetaType("QAndroidJniObject"); + __interfaceActivity = QAndroidJniObject(instance); auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler); unpackAndroidAssets(); qInstallMessageHandler(oldMessageHandler); QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a) { QAndroidJniObject string = QAndroidJniObject::fromString(a); - __activity.callMethod("openGotoActivity", "(Ljava/lang/String;)V", string.object()); + __interfaceActivity.callMethod("openGotoActivity", "(Ljava/lang/String;)V", string.object()); }); } @@ -167,15 +169,12 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) { - qDebug() << "nativeOnPause"; } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) { - qDebug() << "nativeOnResume"; } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) { - qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId(); } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) { @@ -204,4 +203,67 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurren return env->NewStringUTF(str.toLatin1().data()); } +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobject instance, + jstring username_, jstring password_) { + const char *c_username = env->GetStringUTFChars(username_, 0); + const char *c_password = env->GetStringUTFChars(password_, 0); + QString username = QString(c_username); + QString password = QString(c_password); + env->ReleaseStringUTFChars(username_, c_username); + env->ReleaseStringUTFChars(password_, c_password); + + QSharedPointer accountManager = AndroidHelper::instance().getAccountManager(); + + __loginActivity = QAndroidJniObject(instance); + + QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) { + AndroidHelper::instance().notifyLoginComplete(true); + }); + + QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() { + AndroidHelper::instance().notifyLoginComplete(false); + }); + + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::loginComplete, [](bool success) { + jboolean jSuccess = (jboolean) success; + __loginActivity.callMethod("handleLoginCompleted", "(Z)V", jSuccess); + }); + + QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken", Q_ARG(const QString&, username), Q_ARG(const QString&, password)); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env, + jobject instance) { + + __loadCompleteListener = QAndroidJniObject(instance); + + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() { + + __loadCompleteListener.callMethod("onAppLoadedComplete", "()V"); + __interfaceActivity.callMethod("onAppLoadedComplete", "()V"); + + QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr, + nullptr); + }); + +} +JNIEXPORT jboolean JNICALL +Java_io_highfidelity_hifiinterface_HomeActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) { + return AndroidHelper::instance().getAccountManager()->isLoggedIn(); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_HomeActivity_nativeLogout(JNIEnv *env, jobject instance) { + AndroidHelper::instance().getAccountManager()->logout(); +} + +JNIEXPORT jstring JNICALL +Java_io_highfidelity_hifiinterface_HomeActivity_nativeGetDisplayName(JNIEnv *env, + jobject instance) { + QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername(); + return env->NewStringUTF(username.toLatin1().data()); +} + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index 65221bc21c..f1ecc21d84 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -9,7 +9,6 @@ import android.support.v7.widget.Toolbar; import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; -import android.view.inputmethod.EditorInfo; import android.widget.EditText; import java.net.URI; @@ -17,6 +16,8 @@ import java.net.URISyntaxException; public class GotoActivity extends AppCompatActivity { + public static final String PARAM_DOMAIN_URL = "domain_url"; + private EditText mUrlEditText; private AppCompatButton mGoBtn; @@ -69,15 +70,10 @@ public class GotoActivity extends AppCompatActivity { urlString = "hifi://" + urlString; } - Intent intent = new Intent(this, InterfaceActivity.class); - intent.putExtra(InterfaceActivity.DOMAIN_URL, urlString); + Intent intent = new Intent(); + intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString); + setResult(RESULT_OK, intent); finish(); - if (getIntent() != null && - getIntent().hasExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY) && - getIntent().getBooleanExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - startActivity(intent); } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index ed442e2d8d..117e1f2cc4 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -1,9 +1,7 @@ package io.highfidelity.hifiinterface; -import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Color; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; @@ -22,18 +20,23 @@ import android.widget.TabHost; import android.widget.TabWidget; import android.widget.TextView; -import io.highfidelity.hifiinterface.QtPreloader.QtPreloader; import io.highfidelity.hifiinterface.view.DomainAdapter; public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { + public native boolean nativeIsLoggedIn(); + public native void nativeLogout(); + public native String nativeGetDisplayName(); + /** * Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected" */ - public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity"; + //public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity"; + + public static final int ENTER_DOMAIN_URL = 1; + private DomainAdapter domainAdapter; private DrawerLayout mDrawerLayout; - private ProgressDialog mDialog; private NavigationView mNavigationView; @Override @@ -89,15 +92,7 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On @Override public void onItemClick(View view, int position, DomainAdapter.Domain domain) { - Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class); - intent.putExtra(InterfaceActivity.DOMAIN_URL, domain.url); - HomeActivity.this.finish(); - if (getIntent() != null && - getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) && - getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - startActivity(intent); + gotoDomain(domain.url); } }); domainsView.setAdapter(domainAdapter); @@ -112,49 +107,28 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On searchTextView.setTextAppearance(R.style.SearchText); } - if (getIntent() == null || - !getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) || - !getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - preloadQt(); - showActivityIndicator(); - } + updateLoginMenu(); } - private void showActivityIndicator() { - if (mDialog == null) { - mDialog = new ProgressDialog(this); - } - mDialog.setMessage("Please wait..."); - mDialog.setCancelable(false); - mDialog.show(); - } - - private void cancelActivityIndicator() { - if (mDialog != null) { - mDialog.cancel(); + private void updateLoginMenu() { + TextView loginOption = findViewById(R.id.login); + TextView logoutOption = findViewById(R.id.logout); + if (nativeIsLoggedIn()) { + loginOption.setVisibility(View.GONE); + logoutOption.setVisibility(View.VISIBLE); + } else { + loginOption.setVisibility(View.VISIBLE); + logoutOption.setVisibility(View.GONE); } } - private AsyncTask preloadTask; - - private void preloadQt() { - if (preloadTask == null) { - preloadTask = new AsyncTask() { - @Override - protected Object doInBackground(Object[] objects) { - new QtPreloader(HomeActivity.this).initQt(); - runOnUiThread(new Runnable() { - @Override - public void run() { - cancelActivityIndicator(); - } - }); - return null; - } - }; - preloadTask.execute(); - } + private void gotoDomain(String domainUrl) { + Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class); + intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl); + HomeActivity.this.finish(); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(intent); } @Override @@ -181,7 +155,6 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On @Override protected void onDestroy() { - cancelActivityIndicator(); super.onDestroy(); } @@ -190,11 +163,33 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On switch(item.getItemId()) { case R.id.action_goto: Intent i = new Intent(this, GotoActivity.class); - startActivity(i); - return true; - case R.id.action_settings: + startActivityForResult(i, ENTER_DOMAIN_URL); return true; } return false; } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == ENTER_DOMAIN_URL && resultCode == RESULT_OK) { + gotoDomain(data.getStringExtra(GotoActivity.PARAM_DOMAIN_URL)); + } + } + + @Override + protected void onStart() { + super.onStart(); + updateLoginMenu(); + } + + public void onLoginClicked(View view) { + Intent intent = new Intent(this, LoginActivity.class); + startActivity(intent); + } + + public void onLogoutClicked(View view) { + nativeLogout(); + updateLoginMenu(); + } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 678f7e8aac..2b86abc9da 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -63,6 +63,7 @@ public class InterfaceActivity extends QtActivity { @Override public void onCreate(Bundle savedInstanceState) { + super.isLoading = true; Intent intent = getIntent(); if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) { intent.putExtra("applicationArguments", "--url "+intent.getStringExtra(DOMAIN_URL)); @@ -112,6 +113,8 @@ public class InterfaceActivity extends QtActivity { } } }); + startActivity(new Intent(this, SplashActivity.class)); + } @Override @@ -201,7 +204,6 @@ public class InterfaceActivity extends QtActivity { switch (activityName) { case "Goto": { Intent intent = new Intent(this, HomeActivity.class); - intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true); startActivity(intent); break; } @@ -212,4 +214,8 @@ public class InterfaceActivity extends QtActivity { } } + public void onAppLoadedComplete() { + super.isLoading = false; + } + } \ No newline at end of file diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java new file mode 100644 index 0000000000..9fd256aa82 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java @@ -0,0 +1,106 @@ +package io.highfidelity.hifiinterface; + +import android.app.ProgressDialog; +import android.support.annotation.MainThread; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +public class LoginActivity extends AppCompatActivity { + + public native void nativeLogin(String username, String password); + + private EditText mUsername; + private EditText mPassword; + private TextView mError; + private Button mLoginButton; + + private ProgressDialog mDialog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + mUsername = findViewById(R.id.username); + mPassword = findViewById(R.id.password); + mError = findViewById(R.id.error); + mLoginButton = findViewById(R.id.loginButton); + + mPassword.setOnEditorActionListener( + new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + mLoginButton.performClick(); + return true; + } + return false; + } + }); + + } + + @Override + protected void onStop() { + super.onStop(); + cancelActivityIndicator(); + } + + public void login(View view) { + String username = mUsername.getText().toString(); + String password = mPassword.getText().toString(); + if (username.isEmpty() || password.isEmpty()) { + showError(getString(R.string.login_username_or_password_incorrect)); + } else { + mLoginButton.setEnabled(false); + hideError(); + showActivityIndicator(); + nativeLogin(username, password); + } + } + + private void showActivityIndicator() { + if (mDialog == null) { + mDialog = new ProgressDialog(this); + } + mDialog.setMessage(getString(R.string.logging_in)); + mDialog.setCancelable(false); + mDialog.show(); + } + + private void cancelActivityIndicator() { + if (mDialog != null) { + mDialog.cancel(); + } + } + private void showError(String error) { + mError.setText(error); + mError.setVisibility(View.VISIBLE); + } + + private void hideError() { + mError.setText(""); + mError.setVisibility(View.INVISIBLE); + } + + public void handleLoginCompleted(boolean success) { + runOnUiThread(() -> { + mLoginButton.setEnabled(true); + cancelActivityIndicator(); + if (success) { + finish(); + } else { + showError(getString(R.string.login_username_or_password_incorrect)); + } + }); + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java index b1c5f570c8..2b48d85a48 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java @@ -64,7 +64,7 @@ public class PermissionChecker extends Activity { private void launchActivityWithPermissions(){ finish(); - Intent i = new Intent(this, HomeActivity.class); + Intent i = new Intent(this, InterfaceActivity.class); startActivity(i); finish(); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java b/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java deleted file mode 100644 index d9ecdb9710..0000000000 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java +++ /dev/null @@ -1,315 +0,0 @@ -package io.highfidelity.hifiinterface.QtPreloader; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ComponentInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.os.Bundle; -import android.util.Log; - -import org.qtproject.qt5.android.bindings.QtApplication; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Method; -import java.util.ArrayList; - -import dalvik.system.DexClassLoader; - -/** - * Created by Gabriel Calero & Cristian Duarte on 3/22/18. - */ - -public class QtPreloader { - - public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1"; - private ComponentInfo m_contextInfo; - private String[] m_qtLibs = null; // required qt libs - private Context m_context; - - private static final String DEX_PATH_KEY = "dex.path"; - private static final String LIB_PATH_KEY = "lib.path"; - private static final String NATIVE_LIBRARIES_KEY = "native.libraries"; - private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; - private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries"; - private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id"; - private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id"; - private static final String MAIN_LIBRARY_KEY = "main.library"; - - private static final int BUFFER_SIZE = 1024; - - public QtPreloader(Context context) { - m_context = context; - } - - public void initQt() { - - try { - m_contextInfo = m_context.getPackageManager().getActivityInfo(new ComponentName("io.highfidelity.hifiinterface", "io.highfidelity.hifiinterface.InterfaceActivity"), - PackageManager.GET_META_DATA); - - if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) { - int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id"); - m_qtLibs = m_context.getResources().getStringArray(resourceId); - } - ArrayList libraryList = new ArrayList<>(); - String localPrefix = m_context.getApplicationInfo().dataDir + "/"; - String pluginsPrefix = localPrefix + "qt-reserved-files/"; - cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); - extractBundledPluginsAndImports(pluginsPrefix); - - for (String lib : m_qtLibs) { - libraryList.add(localPrefix + "lib/lib" + lib + ".so"); - } - - if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) { - String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":"); - for (String lib : extraLibs) { - if (lib.length() > 0) { - if (lib.startsWith("lib/")) { - libraryList.add(localPrefix + lib); - } else { - libraryList.add(pluginsPrefix + lib); - } - } - } - } - - Bundle loaderParams = new Bundle(); - loaderParams.putString(DEX_PATH_KEY, new String()); - - loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); - - loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES - + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" - + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" - + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); - - - // add all bundled Qt libs to loader params - ArrayList libs = new ArrayList<>(); - - String libName = m_contextInfo.metaData.getString("android.app.lib_name"); - loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function - loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); - - // load and start QtLoader class - DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files - m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. - loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) - m_context.getClassLoader()); // parent loader - - Class loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class - Object qtLoader = loaderClass.newInstance(); // create an instance - Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", - contextClassName(), - ClassLoader.class, - Bundle.class); - prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams); - - // now load the application library so it's accessible from this class loader - if (libName != null) { - System.loadLibrary(libName); - } - } catch (Exception e) { - Log.e(QtApplication.QtTAG, "Error pre-loading HiFi Qt app", e); - } - } - - protected String loaderClassName() { - return "org.qtproject.qt5.android.QtActivityDelegate"; - } - - protected Class contextClassName() { - return android.app.Activity.class; - } - - - private void deleteRecursively(File directory) { - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - deleteRecursively(file); - } else { - file.delete(); - } - } - - directory.delete(); - } - } - - private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) { - File newCache = new File(localPrefix); - if (!newCache.exists()) { - { - File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); - if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) { - deleteRecursively(oldPluginsCache); - } - } - - { - File oldImportsCache = new File(oldLocalPrefix + "imports/"); - if (oldImportsCache.exists() && oldImportsCache.isDirectory()) { - deleteRecursively(oldImportsCache); - } - } - - { - File oldQmlCache = new File(oldLocalPrefix + "qml/"); - if (oldQmlCache.exists() && oldQmlCache.isDirectory()) { - deleteRecursively(oldQmlCache); - } - } - } - } - - static private void copyFile(InputStream inputStream, OutputStream outputStream) - throws IOException { - byte[] buffer = new byte[BUFFER_SIZE]; - - int count; - while ((count = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, count); - } - } - - private void copyAsset(String source, String destination) - throws IOException { - // Already exists, we don't have to do anything - File destinationFile = new File(destination); - if (destinationFile.exists()) { - return; - } - - File parentDirectory = destinationFile.getParentFile(); - if (!parentDirectory.exists()) { - parentDirectory.mkdirs(); - } - - destinationFile.createNewFile(); - - AssetManager assetsManager = m_context.getAssets(); - InputStream inputStream = assetsManager.open(source); - OutputStream outputStream = new FileOutputStream(destinationFile); - copyFile(inputStream, outputStream); - - inputStream.close(); - outputStream.close(); - } - - private static void createBundledBinary(String source, String destination) - throws IOException { - // Already exists, we don't have to do anything - File destinationFile = new File(destination); - if (destinationFile.exists()) { - return; - } - - File parentDirectory = destinationFile.getParentFile(); - if (!parentDirectory.exists()) { - parentDirectory.mkdirs(); - } - - destinationFile.createNewFile(); - - InputStream inputStream = new FileInputStream(source); - OutputStream outputStream = new FileOutputStream(destinationFile); - copyFile(inputStream, outputStream); - - inputStream.close(); - outputStream.close(); - } - - private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) { - File versionFile = new File(pluginsPrefix + "cache.version"); - - long cacheVersion = 0; - if (versionFile.exists() && versionFile.canRead()) { - try { - DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); - cacheVersion = inputStream.readLong(); - inputStream.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (cacheVersion != packageVersion) { - deleteRecursively(new File(pluginsPrefix)); - return true; - } else { - return false; - } - } - - private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException { - String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/"; - long packageVersion = -1; - try { - PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0); - packageVersion = packageInfo.lastUpdateTime; - } catch (Exception e) { - e.printStackTrace(); - } - - - if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) { - return; - } - - { - File versionFile = new File(pluginsPrefix + "cache.version"); - - File parentDirectory = versionFile.getParentFile(); - if (!parentDirectory.exists()) { - parentDirectory.mkdirs(); - } - - versionFile.createNewFile(); - - DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); - outputStream.writeLong(packageVersion); - outputStream.close(); - } - - { - String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; - if (m_contextInfo.metaData.containsKey(key)) { - String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); - - for (String bundledImportBinary : list) { - String[] split = bundledImportBinary.split(":"); - String sourceFileName = libsDir + split[0]; - String destinationFileName = pluginsPrefix + split[1]; - createBundledBinary(sourceFileName, destinationFileName); - } - } - } - - { - String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; - if (m_contextInfo.metaData.containsKey(key)) { - String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); - - for (String fileName : list) { - String[] split = fileName.split(":"); - String sourceFileName = split[0]; - String destinationFileName = pluginsPrefix + split[1]; - copyAsset(sourceFileName, destinationFileName); - } - } - - } - } -} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java new file mode 100644 index 0000000000..b663a3e396 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java @@ -0,0 +1,32 @@ +package io.highfidelity.hifiinterface; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +public class SplashActivity extends Activity { + + private native void registerLoadCompleteListener(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_splash); + registerLoadCompleteListener(); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + public void onAppLoadedComplete() { + startActivity(new Intent(this, HomeActivity.class)); + finish(); + } +} diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java index ed55c16cde..887d27dba4 100644 --- a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java @@ -68,6 +68,8 @@ public class QtActivity extends Activity { public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme. private QtActivityLoader m_loader = new QtActivityLoader(this); + public boolean isLoading; + public QtActivity() { } @@ -499,7 +501,11 @@ public class QtActivity extends Activity { @Override protected void onPause() { super.onPause(); - QtApplication.invokeDelegate(); + // GC: this trick allow us to show a splash activity until Qt app finishes + // loading + if (!isLoading) { + QtApplication.invokeDelegate(); + } } //--------------------------------------------------------------------------- diff --git a/android/app/src/main/res/drawable/hifi_header.xml b/android/app/src/main/res/drawable/hifi_header.xml new file mode 100644 index 0000000000..9f7c85297a --- /dev/null +++ b/android/app/src/main/res/drawable/hifi_header.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rounded_button.xml b/android/app/src/main/res/drawable/rounded_button.xml new file mode 100644 index 0000000000..11a9f90c8b --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_button.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rounded_edit.xml b/android/app/src/main/res/drawable/rounded_edit.xml new file mode 100644 index 0000000000..3c1cac4d1d --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_edit.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_goto.xml index 7f4e7d5fcf..2d5943e15d 100644 --- a/android/app/src/main/res/layout/activity_goto.xml +++ b/android/app/src/main/res/layout/activity_goto.xml @@ -3,8 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root_activity_goto" - android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" tools:context="io.highfidelity.hifiinterface.GotoActivity"> + > + + + + + diff --git a/android/app/src/main/res/layout/activity_login.xml b/android/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000000..ecf72b94bf --- /dev/null +++ b/android/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + +