mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 04:23:33 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into project-freeloco
This commit is contained in:
commit
ebbf48a11b
1002 changed files with 32800 additions and 23615 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -14,11 +14,13 @@ Makefile
|
||||||
|
|
||||||
# Android Studio
|
# Android Studio
|
||||||
*.iml
|
*.iml
|
||||||
|
*.class
|
||||||
local.properties
|
local.properties
|
||||||
android/gradle*
|
android/gradle*
|
||||||
android/.gradle
|
android/.gradle
|
||||||
android/**/src/main/jniLibs
|
android/**/src/main/jniLibs
|
||||||
android/**/libs
|
android/**/libs
|
||||||
|
android/**/bin
|
||||||
android/**/src/main/res/values/libs.xml
|
android/**/src/main/res/values/libs.xml
|
||||||
android/**/src/main/assets
|
android/**/src/main/assets
|
||||||
android/**/gradle*
|
android/**/gradle*
|
||||||
|
@ -102,3 +104,6 @@ tools/unity-avatar-exporter/Logs
|
||||||
tools/unity-avatar-exporter/Packages
|
tools/unity-avatar-exporter/Packages
|
||||||
tools/unity-avatar-exporter/ProjectSettings
|
tools/unity-avatar-exporter/ProjectSettings
|
||||||
tools/unity-avatar-exporter/Temp
|
tools/unity-avatar-exporter/Temp
|
||||||
|
server-console/package-lock.json
|
||||||
|
vcpkg/
|
||||||
|
/tools/nitpick/compiledResources
|
||||||
|
|
|
@ -12,7 +12,7 @@ target_python()
|
||||||
|
|
||||||
if (HIFI_ANDROID )
|
if (HIFI_ANDROID )
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android --build-root ${CMAKE_BINARY_DIR}
|
COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android ${HIFI_ANDROID_APP} --build-root ${CMAKE_BINARY_DIR}
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
|
@ -80,8 +80,28 @@ endif()
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
set(GLES_OPTION ON)
|
set(GLES_OPTION ON)
|
||||||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||||
|
add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\")
|
||||||
|
if (
|
||||||
|
(${HIFI_ANDROID_APP} STREQUAL "questInterface") OR
|
||||||
|
(${HIFI_ANDROID_APP} STREQUAL "questFramePlayer") OR
|
||||||
|
(${HIFI_ANDROID_APP} STREQUAL "framePlayer")
|
||||||
|
)
|
||||||
|
# We know the quest hardware has this extension, so we can force the use of instanced stereo
|
||||||
|
add_definitions(-DHAVE_EXT_clip_cull_distance)
|
||||||
|
# We can also use multiview stereo techniques
|
||||||
|
add_definitions(-DHAVE_OVR_multiview2)
|
||||||
|
add_definitions(-DHAVE_OVR_multiview)
|
||||||
|
# We can also use our own foveated textures
|
||||||
|
add_definitions(-DHAVE_QCOM_texture_foveated)
|
||||||
|
|
||||||
|
# if set, the application itself or some library it depends on MUST implement
|
||||||
|
# `DisplayPluginList getDisplayPlugins()` and `InputPluginList getInputPlugins()`
|
||||||
|
add_definitions(-DCUSTOM_INPUT_PLUGINS)
|
||||||
|
add_definitions(-DCUSTOM_DISPLAY_PLUGINS)
|
||||||
|
set(PLATFORM_PLUGIN_LIBRARIES oculusMobile oculusMobilePlugin)
|
||||||
|
endif()
|
||||||
else ()
|
else ()
|
||||||
set(PLATFORM_QT_COMPONENTS WebEngine)
|
set(PLATFORM_QT_COMPONENTS WebEngine Xml)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (USE_GLES AND (NOT ANDROID))
|
if (USE_GLES AND (NOT ANDROID))
|
||||||
|
@ -108,8 +128,10 @@ set(PLATFORM_QT_GL OpenGL)
|
||||||
|
|
||||||
if (USE_GLES)
|
if (USE_GLES)
|
||||||
add_definitions(-DUSE_GLES)
|
add_definitions(-DUSE_GLES)
|
||||||
|
add_definitions(-DGPU_POINTER_STORAGE_SHARED)
|
||||||
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles)
|
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles)
|
||||||
else()
|
else()
|
||||||
|
add_definitions(-DGPU_POINTER_STORAGE_RAW)
|
||||||
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl)
|
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -174,7 +196,7 @@ set_packaging_parameters()
|
||||||
|
|
||||||
# FIXME hack to work on the proper Android toolchain
|
# FIXME hack to work on the proper Android toolchain
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
add_subdirectory(android/app)
|
add_subdirectory(android/apps/${HIFI_ANDROID_APP})
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
75
INSTALL.md
75
INSTALL.md
|
@ -4,7 +4,8 @@ During generation, CMake should produce an `install` target and a `package` targ
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
|
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
|
||||||
|
This variable is set by the `project(hifi)` command in `CMakeLists.txt` to `C:/Program Files/hifi` and stored in `build/CMakeCache.txt`
|
||||||
|
|
||||||
### Packaging
|
### Packaging
|
||||||
|
|
||||||
|
@ -14,17 +15,67 @@ To produce an installer, run the `package` target.
|
||||||
|
|
||||||
To produce an executable installer on Windows, the following are required:
|
To produce an executable installer on Windows, the following are required:
|
||||||
|
|
||||||
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
|
1. [7-zip](<https://www.7-zip.org/download.html>)
|
||||||
- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
|
|
||||||
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
|
|
||||||
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
|
|
||||||
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
|
|
||||||
- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7
|
|
||||||
- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
|
|
||||||
- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0
|
|
||||||
|
|
||||||
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
|
1. [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.04
|
||||||
|
Install using defaults (will install to `C:\Program Files (x86)\NSIS`)
|
||||||
|
1. [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
|
||||||
|
1. Extract Zip
|
||||||
|
1. Copy `UAC.nsh` to `C:\Program Files (x86)\NSIS\Include\`
|
||||||
|
1. Copy `Plugins\x86-ansi\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
|
||||||
|
1. Copy `Plugins\x86-unicode\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
|
||||||
|
1. [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 (use the link marked **nsProcess_1_6.7z**)
|
||||||
|
1. Extract Zip
|
||||||
|
1. Copy `Include\nsProcess.nsh` to `C:\Program Files (x86)\NSIS\Include\`
|
||||||
|
1. Copy `Plugins\nsProcess.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
|
||||||
|
1. Copy `Plugins\nsProcessW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
|
||||||
|
|
||||||
|
1. [InetC Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
|
||||||
|
1. Extract Zip
|
||||||
|
1. Copy `Plugin\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
|
||||||
|
1. Copy `Plugin\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
|
||||||
|
|
||||||
|
1. [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
|
||||||
|
1. Extract Zip
|
||||||
|
1. Copy `NSISpre.nsh` to `C:\Program Files (x86)\NSIS\Include\`
|
||||||
|
1. Copy `NSISpre.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
|
||||||
|
|
||||||
|
1. [nsisSlideshow Plug-in for Nullsoft](<http://wiz0u.free.fr/prog/nsisSlideshow/>) - 1.7
|
||||||
|
1. Extract Zip
|
||||||
|
1. Copy `bin\nsisSlideshow.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
|
||||||
|
1. Copy `bin\nsisSlideshowW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
|
||||||
|
|
||||||
|
1. [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
|
||||||
|
1. Download both Zips and unzip
|
||||||
|
1. Copy `nsisunz\Release\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
|
||||||
|
1. Copy `NSISunzU\Plugin unicode\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
|
||||||
|
|
||||||
|
1. [ApplicationID plug-in for Nullsoft]() - 1.0
|
||||||
|
1. Download [`Pre-built DLLs`](<https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip>)
|
||||||
|
1. Extract Zip
|
||||||
|
1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
|
||||||
|
1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
|
||||||
|
|
||||||
|
1. [npm](<https://www.npmjs.com/get-npm>)
|
||||||
|
1. Install version 10.15.0 LTS
|
||||||
|
|
||||||
|
1. Perform a clean cmake from a new terminal.
|
||||||
|
1. Open the `hifi.sln` Solution and select the Release configuration.
|
||||||
|
1. Build the Solution.
|
||||||
|
1. Build `packaged-server-console` (found under **Server Console**)
|
||||||
|
This will add 2 folders to `build\server-console\` -
|
||||||
|
`server-console-win32-x64` and `x64`
|
||||||
|
1. Build CMakeTargets->PACKAGE
|
||||||
|
Installer is now available in `build\_CPack_Packages\win64\NSIS`
|
||||||
|
|
||||||
#### OS X
|
#### OS X
|
||||||
|
1. [npm](<https://www.npmjs.com/get-npm>)
|
||||||
Run the `package` target to create an Apple Disk Image (.dmg).
|
Install version 10.15.0 LTS
|
||||||
|
|
||||||
|
1. Perform a clean cmake.
|
||||||
|
1. Perform a Release build of ALL_BUILD
|
||||||
|
1. Perform a Release build of `packaged-server-console`
|
||||||
|
This will add a folder to `build\server-console\` -
|
||||||
|
Sandbox-darwin-x64
|
||||||
|
1. Perform a Release build of `package`
|
||||||
|
Installer is now available in `build/_CPack_Packages/Darwin/DragNDrop
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
//
|
|
||||||
// InterfaceActivity.java
|
|
||||||
// gvr-interface/java
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/26/15.
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
package io.highfidelity.gvrinterface;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.util.Log;
|
|
||||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
|
||||||
|
|
||||||
public class InterfaceActivity extends QtActivity {
|
|
||||||
|
|
||||||
public static native void handleHifiURL(String hifiURLString);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
|
|
||||||
// Get the intent that started this activity in case we have a hifi:// URL to parse
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent.getAction() == Intent.ACTION_VIEW) {
|
|
||||||
Uri data = intent.getData();
|
|
||||||
|
|
||||||
if (data.getScheme().equals("hifi")) {
|
|
||||||
handleHifiURL(data.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
5
android/apps/framePlayer/CMakeLists.txt
Normal file
5
android/apps/framePlayer/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
set(TARGET_NAME framePlayer)
|
||||||
|
setup_hifi_library(AndroidExtras)
|
||||||
|
link_hifi_libraries(shared ktx shaders qml gpu gl ${PLATFORM_GL_BACKEND})
|
||||||
|
target_link_libraries(${TARGET_NAME} android log m)
|
||||||
|
target_opengl()
|
41
android/apps/framePlayer/build.gradle
Normal file
41
android/apps/framePlayer/build.gradle
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import com.android.builder.core.BuilderConstants
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "io.highfidelity.frameplayer"
|
||||||
|
minSdkVersion 25
|
||||||
|
targetSdkVersion 28
|
||||||
|
ndk { abiFilters 'arm64-v8a' }
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments '-DHIFI_ANDROID=1',
|
||||||
|
'-DHIFI_ANDROID_APP=framePlayer',
|
||||||
|
'-DANDROID_TOOLCHAIN=clang',
|
||||||
|
'-DANDROID_STL=c++_shared',
|
||||||
|
'-DCMAKE_VERBOSE_MAKEFILE=ON'
|
||||||
|
targets = ['framePlayer']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild.cmake.path '../../../CMakeLists.txt'
|
||||||
|
|
||||||
|
variantFilter { variant ->
|
||||||
|
def build = variant.buildType.name
|
||||||
|
if (build == BuilderConstants.RELEASE) {
|
||||||
|
variant.setIgnore(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
|
||||||
|
implementation project(':qt')
|
||||||
|
}
|
38
android/apps/framePlayer/src/main/AndroidManifest.xml
Normal file
38
android/apps/framePlayer/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="io.highfidelity.frameplayer"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0"
|
||||||
|
android:installLocation="auto">
|
||||||
|
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<application android:label="Frame Viewer"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon">
|
||||||
|
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
|
||||||
|
<activity
|
||||||
|
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||||
|
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:screenOrientation="landscape"
|
||||||
|
android:excludeFromRecents="false"
|
||||||
|
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode">
|
||||||
|
<!-- JNI nonsense -->
|
||||||
|
<meta-data android:name="android.app.lib_name" android:value="framePlayer"/>
|
||||||
|
<!-- Qt nonsense -->
|
||||||
|
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||||
|
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||||
|
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||||
|
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so"/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
6
android/apps/framePlayer/src/main/cpp/FramePlayer.qrc
Normal file
6
android/apps/framePlayer/src/main/cpp/FramePlayer.qrc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE RCC>
|
||||||
|
<RCC version="1.0">
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>qml/main.qml</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
91
android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp
Normal file
91
android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "PlayerWindow.h"
|
||||||
|
|
||||||
|
#include <QtCore/QFileInfo>
|
||||||
|
#include <QtGui/QImageReader>
|
||||||
|
#include <QtQml/QQmlContext>
|
||||||
|
#include <QtQuick/QQuickItem>
|
||||||
|
|
||||||
|
#include <gpu/Frame.h>
|
||||||
|
#include <gpu/FrameIO.h>
|
||||||
|
|
||||||
|
PlayerWindow::PlayerWindow() {
|
||||||
|
setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
|
||||||
|
setSurfaceType(QSurface::OpenGLSurface);
|
||||||
|
create();
|
||||||
|
showFullScreen();
|
||||||
|
|
||||||
|
// Make sure the window has been created by processing events
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
// Start the rendering thread
|
||||||
|
_renderThread.initialize(this, &_surface);
|
||||||
|
|
||||||
|
// Start the UI
|
||||||
|
_surface.resize(size());
|
||||||
|
connect(&_surface, &hifi::qml::OffscreenSurface::rootContextCreated, this, [](QQmlContext* context){
|
||||||
|
context->setContextProperty("FRAMES_FOLDER", "file:assets:/frames");
|
||||||
|
});
|
||||||
|
_surface.load("qrc:///qml/main.qml");
|
||||||
|
|
||||||
|
// Connect the UI handler
|
||||||
|
QObject::connect(_surface.getRootItem(), SIGNAL(loadFile(QString)),
|
||||||
|
this, SLOT(loadFile(QString))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Turn on UI input events
|
||||||
|
installEventFilter(&_surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerWindow::~PlayerWindow() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// static const char* FRAME_FILE = "assets:/frames/20190110_1635.json";
|
||||||
|
|
||||||
|
static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) {
|
||||||
|
QImage image;
|
||||||
|
QImageReader(filename.c_str()).read(&image);
|
||||||
|
if (layer > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
texture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerWindow::loadFile(QString filename) {
|
||||||
|
QString realFilename = QUrl(filename).toLocalFile();
|
||||||
|
if (QFileInfo(realFilename).exists()) {
|
||||||
|
auto frame = gpu::readFrame(realFilename.toStdString(), _renderThread._externalTexture, &textureLoader);
|
||||||
|
_surface.pause();
|
||||||
|
_renderThread.submitFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerWindow::touchEvent(QTouchEvent* event) {
|
||||||
|
// Super basic input handling when the 3D scene is active.... tap with two finders to return to the
|
||||||
|
// QML UI
|
||||||
|
static size_t touches = 0;
|
||||||
|
switch (event->type()) {
|
||||||
|
case QEvent::TouchBegin:
|
||||||
|
case QEvent::TouchUpdate:
|
||||||
|
touches = std::max<size_t>(touches, event->touchPoints().size());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QEvent::TouchEnd:
|
||||||
|
if (touches >= 2) {
|
||||||
|
_renderThread.submitFrame(nullptr);
|
||||||
|
_surface.resume();
|
||||||
|
}
|
||||||
|
touches = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
35
android/apps/framePlayer/src/main/cpp/PlayerWindow.h
Normal file
35
android/apps/framePlayer/src/main/cpp/PlayerWindow.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QtGui/QWindow>
|
||||||
|
#include <QtCore/QSettings>
|
||||||
|
|
||||||
|
#include <qml/OffscreenSurface.h>
|
||||||
|
#include <gpu/Forward.h>
|
||||||
|
#include "RenderThread.h"
|
||||||
|
|
||||||
|
// Create a simple OpenGL window that renders text in various ways
|
||||||
|
class PlayerWindow : public QWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlayerWindow();
|
||||||
|
virtual ~PlayerWindow();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void touchEvent(QTouchEvent *ev) override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void loadFile(QString filename);
|
||||||
|
|
||||||
|
private:
|
||||||
|
hifi::qml::OffscreenSurface _surface;
|
||||||
|
QSettings _settings;
|
||||||
|
RenderThread _renderThread;
|
||||||
|
};
|
162
android/apps/framePlayer/src/main/cpp/RenderThread.cpp
Normal file
162
android/apps/framePlayer/src/main/cpp/RenderThread.cpp
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RenderThread.h"
|
||||||
|
|
||||||
|
#include <QtGui/QWindow>
|
||||||
|
|
||||||
|
void RenderThread::submitFrame(const gpu::FramePointer& frame) {
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
_pendingFrames.push(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::move(const glm::vec3& v) {
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
_correction = glm::inverse(glm::translate(mat4(), v)) * _correction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::setup() {
|
||||||
|
// Wait until the context has been moved to this thread
|
||||||
|
{ std::unique_lock<std::mutex> lock(_frameLock); }
|
||||||
|
|
||||||
|
makeCurrent();
|
||||||
|
// Disable vsync for profiling
|
||||||
|
::gl::setSwapInterval(0);
|
||||||
|
|
||||||
|
glClearColor(1, 1, 0, 1);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
_glContext.swapBuffers();
|
||||||
|
|
||||||
|
// GPU library init
|
||||||
|
gpu::Context::init<gpu::gl::GLBackend>();
|
||||||
|
_gpuContext = std::make_shared<gpu::Context>();
|
||||||
|
_backend = _gpuContext->getBackend();
|
||||||
|
_gpuContext->beginFrame();
|
||||||
|
_gpuContext->endFrame();
|
||||||
|
makeCurrent();
|
||||||
|
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &_uiFbo);
|
||||||
|
glGenTextures(1, &_externalTexture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, _externalTexture);
|
||||||
|
static const glm::u8vec4 color{ 0 };
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color);
|
||||||
|
|
||||||
|
glClearColor(0, 1, 1, 1);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
_glContext.swapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen) {
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
setObjectName("RenderThread");
|
||||||
|
Parent::initialize();
|
||||||
|
|
||||||
|
_offscreen = offscreen;
|
||||||
|
_window = window;
|
||||||
|
_glContext.setWindow(_window);
|
||||||
|
_glContext.create();
|
||||||
|
_glContext.makeCurrent();
|
||||||
|
|
||||||
|
hifi::qml::OffscreenSurface::setSharedContext(_glContext.qglContext());
|
||||||
|
glClearColor(1, 0, 0, 1);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
_glContext.swapBuffers();
|
||||||
|
_glContext.doneCurrent();
|
||||||
|
_glContext.moveToThread(_thread);
|
||||||
|
_thread->setObjectName("RenderThread");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::shutdown() {
|
||||||
|
_activeFrame.reset();
|
||||||
|
while (!_pendingFrames.empty()) {
|
||||||
|
_gpuContext->consumeFrameUpdates(_pendingFrames.front());
|
||||||
|
_pendingFrames.pop();
|
||||||
|
}
|
||||||
|
_gpuContext->shutdown();
|
||||||
|
_gpuContext.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::renderFrame() {
|
||||||
|
auto windowSize = _window->geometry().size();
|
||||||
|
uvec2 readFboSize;
|
||||||
|
uint32_t readFbo{ 0 };
|
||||||
|
|
||||||
|
if (_activeFrame) {
|
||||||
|
const auto &frame = _activeFrame;
|
||||||
|
_backend->recycle();
|
||||||
|
_backend->syncCache();
|
||||||
|
_gpuContext->enableStereo(frame->stereoState._enable);
|
||||||
|
if (frame && !frame->batches.empty()) {
|
||||||
|
_gpuContext->executeFrame(frame);
|
||||||
|
}
|
||||||
|
auto &glBackend = static_cast<gpu::gl::GLBackend&>(*_backend);
|
||||||
|
readFbo = glBackend.getFramebufferID(frame->framebuffer);
|
||||||
|
readFboSize = frame->framebuffer->getSize();
|
||||||
|
CHECK_GL_ERROR();
|
||||||
|
} else {
|
||||||
|
hifi::qml::OffscreenSurface::TextureAndFence newTextureAndFence;
|
||||||
|
if (_offscreen->fetchTexture(newTextureAndFence)) {
|
||||||
|
if (_uiTexture != 0) {
|
||||||
|
auto readFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
glFlush();
|
||||||
|
_offscreen->getDiscardLambda()(_uiTexture, readFence);
|
||||||
|
_uiTexture = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
|
||||||
|
glDeleteSync((GLsync)newTextureAndFence.second);
|
||||||
|
_uiTexture = newTextureAndFence.first;
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, _uiFbo);
|
||||||
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _uiTexture, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uiTexture != 0) {
|
||||||
|
readFbo = _uiFbo;
|
||||||
|
readFboSize = { windowSize.width(), windowSize.height() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readFbo) {
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
|
||||||
|
glBlitFramebuffer(
|
||||||
|
0, 0, readFboSize.x, readFboSize.y,
|
||||||
|
0, 0, windowSize.width(), windowSize.height(),
|
||||||
|
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||||
|
} else {
|
||||||
|
glClearColor(1, 0, 0, 1);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
_glContext.swapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::updateFrame() {
|
||||||
|
std::queue<gpu::FramePointer> pendingFrames;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
pendingFrames.swap(_pendingFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!pendingFrames.empty()) {
|
||||||
|
_activeFrame = pendingFrames.front();
|
||||||
|
pendingFrames.pop();
|
||||||
|
if (_activeFrame) {
|
||||||
|
_gpuContext->consumeFrameUpdates(_activeFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderThread::process() {
|
||||||
|
updateFrame();
|
||||||
|
makeCurrent();
|
||||||
|
renderFrame();
|
||||||
|
return true;
|
||||||
|
}
|
54
android/apps/framePlayer/src/main/cpp/RenderThread.h
Normal file
54
android/apps/framePlayer/src/main/cpp/RenderThread.h
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <GenericThread.h>
|
||||||
|
|
||||||
|
#include <gl/Context.h>
|
||||||
|
#include <gpu/gl/GLBackend.h>
|
||||||
|
#include <qml/OffscreenSurface.h>
|
||||||
|
|
||||||
|
class RenderThread : public GenericThread {
|
||||||
|
using Parent = GenericThread;
|
||||||
|
public:
|
||||||
|
QWindow* _window{ nullptr };
|
||||||
|
std::mutex _mutex;
|
||||||
|
gpu::ContextPointer _gpuContext; // initialized during window creation
|
||||||
|
std::shared_ptr<gpu::Backend> _backend;
|
||||||
|
std::atomic<size_t> _presentCount{ 0 };
|
||||||
|
std::mutex _frameLock;
|
||||||
|
std::queue<gpu::FramePointer> _pendingFrames;
|
||||||
|
gpu::FramePointer _activeFrame;
|
||||||
|
uint32_t _externalTexture{ 0 };
|
||||||
|
glm::mat4 _correction;
|
||||||
|
hifi::qml::OffscreenSurface* _offscreen{ nullptr };
|
||||||
|
|
||||||
|
gl::Context _glContext;
|
||||||
|
uint32_t _uiTexture{ 0 };
|
||||||
|
uint32_t _uiFbo{ 0 };
|
||||||
|
|
||||||
|
void move(const glm::vec3& v);
|
||||||
|
void setup() override;
|
||||||
|
bool process() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen);
|
||||||
|
|
||||||
|
void submitFrame(const gpu::FramePointer& frame);
|
||||||
|
void updateFrame();
|
||||||
|
void renderFrame();
|
||||||
|
|
||||||
|
bool makeCurrent() {
|
||||||
|
return _glContext.makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void doneCurrent() {
|
||||||
|
_glContext.doneCurrent();
|
||||||
|
}
|
||||||
|
};
|
54
android/apps/framePlayer/src/main/cpp/main.cpp
Normal file
54
android/apps/framePlayer/src/main/cpp/main.cpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/11/22
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtCore/QFileInfo>
|
||||||
|
|
||||||
|
#include <Trace.h>
|
||||||
|
|
||||||
|
#include "PlayerWindow.h"
|
||||||
|
|
||||||
|
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
const char * local=message.toStdString().c_str();
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_DEBUG,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtInfoMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_INFO,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtWarningMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_WARN,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtCriticalMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_ERROR,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
default:
|
||||||
|
__android_log_write(ANDROID_LOG_FATAL,"Interface",local);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
setupHifiApplication("gpuFramePlayer");
|
||||||
|
QGuiApplication app(argc, argv);
|
||||||
|
auto oldMessageHandler = qInstallMessageHandler(messageHandler);
|
||||||
|
DependencyManager::set<tracing::Tracer>();
|
||||||
|
PlayerWindow window;
|
||||||
|
app.exec();
|
||||||
|
qInstallMessageHandler(oldMessageHandler);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
36
android/apps/framePlayer/src/main/cpp/qml/main.qml
Normal file
36
android/apps/framePlayer/src/main/cpp/qml/main.qml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Dialogs 1.1
|
||||||
|
import Qt.labs.folderlistmodel 2.11
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
width: 640
|
||||||
|
height: 480
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
FolderListModel {
|
||||||
|
id: folderModel
|
||||||
|
folder: FRAMES_FOLDER
|
||||||
|
nameFilters: ["*.json"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: fileDelegate
|
||||||
|
Text {
|
||||||
|
text: fileName
|
||||||
|
font.pointSize: 36
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.loadFile(folderModel.folder + "/" + fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model: folderModel
|
||||||
|
delegate: fileDelegate
|
||||||
|
}
|
||||||
|
|
||||||
|
signal loadFile(string filename);
|
||||||
|
}
|
3
android/apps/framePlayer/src/main/res/values/strings.xml
Normal file
3
android/apps/framePlayer/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name" translatable="false">GPU Frame Player</string>
|
||||||
|
</resources>
|
|
@ -4,10 +4,10 @@ link_hifi_libraries(shared task networking gl gpu qml image fbx hfm render-utils
|
||||||
target_opengl()
|
target_opengl()
|
||||||
target_bullet()
|
target_bullet()
|
||||||
|
|
||||||
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface")
|
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../interface")
|
||||||
add_subdirectory("${INTERFACE_DIR}" "libraries/interface")
|
add_subdirectory("${INTERFACE_DIR}" "libraries/interface")
|
||||||
include_directories("${INTERFACE_DIR}/src")
|
include_directories("${INTERFACE_DIR}/src")
|
||||||
set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../plugins/hifiCodec")
|
set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/hifiCodec")
|
||||||
add_subdirectory("${HIFI_CODEC_PLUGIN_DIR}" "libraries/hifiCodecPlugin")
|
add_subdirectory("${HIFI_CODEC_PLUGIN_DIR}" "libraries/hifiCodecPlugin")
|
||||||
|
|
||||||
target_link_libraries(native-lib android log m interface)
|
target_link_libraries(native-lib android log m interface)
|
||||||
|
@ -15,16 +15,3 @@ target_link_libraries(native-lib android log m interface)
|
||||||
set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/")
|
set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/")
|
||||||
target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src")
|
target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src")
|
||||||
target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so" ui)
|
target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so" ui)
|
||||||
|
|
||||||
# finished libraries
|
|
||||||
# core -> qt
|
|
||||||
# networking -> openssl, tbb
|
|
||||||
# fbx -> draco
|
|
||||||
# physics -> bullet
|
|
||||||
# entities-renderer -> polyvox
|
|
||||||
|
|
||||||
# unfinished libraries
|
|
||||||
# image -> nvtt (doesn't look good, but can be made optional)
|
|
||||||
# script-engine -> quazip (probably not required for the android client)
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,37 @@
|
||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task renameHifiACTaskDebug() {
|
||||||
|
doLast {
|
||||||
|
def sourceFile = new File("${appDir}/build/intermediates/cmake/debug/obj/arm64-v8a/","libhifiCodec.so")
|
||||||
|
def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so")
|
||||||
|
copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task renameHifiACTaskRelease(type: Copy) {
|
||||||
|
doLast {
|
||||||
|
def sourceFile = new File("${appDir}/build/intermediates/cmake/release/obj/arm64-v8a/","libhifiCodec.so")
|
||||||
|
def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so")
|
||||||
|
copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -19,17 +51,17 @@ android {
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
arguments '-DHIFI_ANDROID=1',
|
arguments '-DHIFI_ANDROID=1',
|
||||||
|
'-DHIFI_ANDROID_APP=interface',
|
||||||
'-DANDROID_PLATFORM=android-24',
|
'-DANDROID_PLATFORM=android-24',
|
||||||
'-DANDROID_TOOLCHAIN=clang',
|
'-DANDROID_TOOLCHAIN=clang',
|
||||||
'-DANDROID_STL=c++_shared',
|
'-DANDROID_STL=c++_shared',
|
||||||
'-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake',
|
|
||||||
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
|
|
||||||
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
|
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
|
||||||
'-DRELEASE_TYPE=' + RELEASE_TYPE,
|
'-DRELEASE_TYPE=' + RELEASE_TYPE,
|
||||||
'-DSTABLE_BUILD=' + STABLE_BUILD,
|
'-DSTABLE_BUILD=' + STABLE_BUILD,
|
||||||
'-DDISABLE_QML=OFF',
|
'-DDISABLE_QML=OFF',
|
||||||
'-DDISABLE_KTX_CACHE=OFF',
|
'-DDISABLE_KTX_CACHE=OFF',
|
||||||
'-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF');
|
'-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF');
|
||||||
|
targets = ['native-lib']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
@ -72,7 +104,7 @@ android {
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path '../../CMakeLists.txt'
|
path '../../../CMakeLists.txt'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +114,7 @@ android {
|
||||||
variant.externalNativeBuildTasks.each { task ->
|
variant.externalNativeBuildTasks.each { task ->
|
||||||
variant.mergeResources.dependsOn(task)
|
variant.mergeResources.dependsOn(task)
|
||||||
if (Os.isFamily(Os.FAMILY_UNIX)) {
|
if (Os.isFamily(Os.FAMILY_UNIX)) {
|
||||||
|
// FIXME
|
||||||
def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first()
|
def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first()
|
||||||
def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first()
|
def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first()
|
||||||
def renameHifiACTask = rootProject.getTasksByName("renameHifiACTask${variant.name.capitalize()}", false).first()
|
def renameHifiACTask = rootProject.getTasksByName("renameHifiACTask${variant.name.capitalize()}", false).first()
|
||||||
|
@ -97,7 +130,7 @@ android {
|
||||||
|
|
||||||
// Copy the compiled resources generated by the external native build
|
// Copy the compiled resources generated by the external native build
|
||||||
copy {
|
copy {
|
||||||
from new File(projectDir, "../../interface/compiledResources")
|
from new File(projectDir, "../../../interface/compiledResources")
|
||||||
into outputDir
|
into outputDir
|
||||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||||
eachFile { details ->
|
eachFile { details ->
|
||||||
|
@ -108,7 +141,7 @@ android {
|
||||||
|
|
||||||
// Copy the scripts directory
|
// Copy the scripts directory
|
||||||
copy {
|
copy {
|
||||||
from new File(projectDir, "../../scripts")
|
from new File(projectDir, "../../../scripts")
|
||||||
into new File(outputDir, "scripts")
|
into new File(outputDir, "scripts")
|
||||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||||
eachFile { details->
|
eachFile { details->
|
||||||
|
@ -123,12 +156,6 @@ android {
|
||||||
assetList.each { file -> out.println(file) }
|
assetList.each { file -> out.println(file) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variant.outputs.all {
|
|
||||||
if (RELEASE_NUMBER != '0') {
|
|
||||||
outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,5 +184,6 @@ dependencies {
|
||||||
|
|
||||||
api 'com.sothree.slidinguppanel:library:3.4.0'
|
api 'com.sothree.slidinguppanel:library:3.4.0'
|
||||||
|
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
|
||||||
|
implementation project(':qt')
|
||||||
}
|
}
|
25
android/apps/interface/proguard-rules.pro
vendored
Normal file
25
android/apps/interface/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in C:\Android\SDK/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
44
android/apps/interface/src/main/assets/avatars.json
Normal file
44
android/apps/interface/src/main/assets/avatars.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"avatars": [
|
||||||
|
{
|
||||||
|
"name": "Wooden Mannequin",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/large/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg",
|
||||||
|
"url": "qrc:////meshes/defaultAvatar_full.fst"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anime-Styled Boy",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg",
|
||||||
|
"url": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anime-Styled Girl",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e7e43f6-1757-44d3-baa4-756827d96311/large/hifi-mp-1e7e43f6-1757-44d3-baa4-756827d96311.jpg",
|
||||||
|
"url": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Last Legends: Male Avatar",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/28569047-6f1a-4100-af67-8054ec397cc3/thumbnail/hifi-mp-28569047-6f1a-4100-af67-8054ec397cc3.jpg",
|
||||||
|
"url": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Last Legends: Female Avatar",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8d823be5-6197-4418-b984-eb94160ed956/thumbnail/hifi-mp-8d823be5-6197-4418-b984-eb94160ed956.jpg",
|
||||||
|
"url": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matthew: Photo-real avatar",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/b652081b-a199-425e-ae5c-7815721bdc09/thumbnail/hifi-mp-b652081b-a199-425e-ae5c-7815721bdc09.jpg",
|
||||||
|
"url": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Priscilla: Photo real avatar",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339/thumbnail/hifi-mp-e7565f93-8bc5-47c2-b6eb-b3b31d4a1339.jpg",
|
||||||
|
"url": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "H1-F1 Optical Interpreter bot",
|
||||||
|
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/469c8b66-e3c2-47fb-9820-e306b1dd15c4/large/hifi-mp-469c8b66-e3c2-47fb-9820-e306b1dd15c4.jpg",
|
||||||
|
"url": "http://mpassets.highfidelity.com/469c8b66-e3c2-47fb-9820-e306b1dd15c4-v1/optical_interpreter[1].fst"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -149,7 +149,7 @@ void unpackAndroidAssets() {
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
|
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject instance, jobject asset_mgr) {
|
||||||
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
|
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
|
||||||
qRegisterMetaType<QAndroidJniObject>("QAndroidJniObject");
|
qRegisterMetaType<QAndroidJniObject>("QAndroidJniObject");
|
||||||
__interfaceActivity = QAndroidJniObject(instance);
|
__interfaceActivity = QAndroidJniObject(instance);
|
||||||
|
@ -493,6 +493,34 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_getDisplayName(JNIEnv *env,
|
||||||
|
jobject instance) {
|
||||||
|
|
||||||
|
QString displayName = AndroidHelper::instance().getDisplayName();
|
||||||
|
return env->NewStringUTF(displayName.toLatin1().data());
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setDisplayName(JNIEnv *env,
|
||||||
|
jobject instance,
|
||||||
|
jstring name_) {
|
||||||
|
const char *c_name = env->GetStringUTFChars(name_, 0);
|
||||||
|
const QString name = QString::fromUtf8(c_name);
|
||||||
|
env->ReleaseStringUTFChars(name_, c_name);
|
||||||
|
AndroidHelper::instance().setDisplayName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setAvatarUrl(JNIEnv *env,
|
||||||
|
jobject instance,
|
||||||
|
jstring url_) {
|
||||||
|
const char *url = env->GetStringUTFChars(url_, 0);
|
||||||
|
QString avatarUrl = QString::fromUtf8(url);
|
||||||
|
AndroidHelper::instance().setMyAvatarUrl(avatarUrl);
|
||||||
|
env->ReleaseStringUTFChars(url_, url);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) {
|
Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) {
|
||||||
DependencyManager::get<AccountManager>()->logout();
|
DependencyManager::get<AccountManager>()->logout();
|
|
@ -24,6 +24,7 @@ import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -31,6 +32,7 @@ import android.view.WindowManager;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.SlidingDrawer;
|
import android.widget.SlidingDrawer;
|
||||||
|
|
||||||
|
import org.qtproject.qt5.android.QtNative;
|
||||||
import org.qtproject.qt5.android.QtLayout;
|
import org.qtproject.qt5.android.QtLayout;
|
||||||
import org.qtproject.qt5.android.QtSurface;
|
import org.qtproject.qt5.android.QtSurface;
|
||||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||||
|
@ -53,6 +55,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
public static final String DOMAIN_URL = "url";
|
public static final String DOMAIN_URL = "url";
|
||||||
public static final String EXTRA_GOTO_USERNAME = "gotousername";
|
public static final String EXTRA_GOTO_USERNAME = "gotousername";
|
||||||
private static final String TAG = "Interface";
|
private static final String TAG = "Interface";
|
||||||
|
public static final String EXTRA_ARGS = "args";
|
||||||
private static final int WEB_DRAWER_RIGHT_MARGIN = 262;
|
private static final int WEB_DRAWER_RIGHT_MARGIN = 262;
|
||||||
private static final int WEB_DRAWER_BOTTOM_MARGIN = 150;
|
private static final int WEB_DRAWER_BOTTOM_MARGIN = 150;
|
||||||
private static final int NORMAL_DPI = 160;
|
private static final int NORMAL_DPI = 160;
|
||||||
|
@ -61,7 +64,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
private HeadsetStateReceiver headsetStateReceiver;
|
private HeadsetStateReceiver headsetStateReceiver;
|
||||||
|
|
||||||
//public static native void handleHifiURL(String hifiURLString);
|
//public static native void handleHifiURL(String hifiURLString);
|
||||||
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
|
private native void nativeOnCreate(AssetManager assetManager);
|
||||||
private native void nativeOnDestroy();
|
private native void nativeOnDestroy();
|
||||||
private native void nativeGotoUrl(String url);
|
private native void nativeGotoUrl(String url);
|
||||||
private native void nativeGoToUser(String username);
|
private native void nativeGoToUser(String username);
|
||||||
|
@ -77,6 +80,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
|
|
||||||
private boolean nativeEnterBackgroundCallEnqueued = false;
|
private boolean nativeEnterBackgroundCallEnqueued = false;
|
||||||
private SlidingDrawer mWebSlidingDrawer;
|
private SlidingDrawer mWebSlidingDrawer;
|
||||||
|
private boolean mStartInDomain;
|
||||||
// private GvrApi gvrApi;
|
// private GvrApi gvrApi;
|
||||||
// Opaque native pointer to the Application C++ object.
|
// Opaque native pointer to the Application C++ object.
|
||||||
// This object is owned by the InterfaceActivity instance and passed to the native methods.
|
// This object is owned by the InterfaceActivity instance and passed to the native methods.
|
||||||
|
@ -92,8 +96,14 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.isLoading = true;
|
super.isLoading = true;
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) {
|
if (intent.hasExtra(DOMAIN_URL) && !TextUtils.isEmpty(intent.getStringExtra(DOMAIN_URL))) {
|
||||||
intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
|
intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
|
||||||
|
} else if (intent.hasExtra(EXTRA_ARGS)) {
|
||||||
|
String args = intent.getStringExtra(EXTRA_ARGS);
|
||||||
|
if (!TextUtils.isEmpty(args)) {
|
||||||
|
mStartInDomain = true;
|
||||||
|
intent.putExtra("applicationArguments", args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
@ -114,7 +124,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
assetManager = getResources().getAssets();
|
assetManager = getResources().getAssets();
|
||||||
|
|
||||||
//nativeGvrApi =
|
//nativeGvrApi =
|
||||||
nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/);
|
nativeOnCreate(assetManager /*, gvrApi.getNativeGvrContext()*/);
|
||||||
|
|
||||||
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
|
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||||
|
|
||||||
|
@ -124,7 +134,10 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
getActionBar().hide();
|
getActionBar().hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
startActivity(new Intent(this, SplashActivity.class));
|
Intent splashIntent = new Intent(this, SplashActivity.class);
|
||||||
|
splashIntent.putExtra(SplashActivity.EXTRA_START_IN_DOMAIN, mStartInDomain);
|
||||||
|
startActivity(splashIntent);
|
||||||
|
|
||||||
mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
|
mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
|
||||||
headsetStateReceiver = new HeadsetStateReceiver();
|
headsetStateReceiver = new HeadsetStateReceiver();
|
||||||
}
|
}
|
||||||
|
@ -166,8 +179,27 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
|
||||||
nativeOnDestroy();
|
nativeOnDestroy();
|
||||||
|
/*
|
||||||
|
cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
|
||||||
|
After Qt upgrade to 5.11 we had a black screen crash after closing the application with
|
||||||
|
the hardware button "Back" and trying to start the app again. It could only be fixed after
|
||||||
|
totally closing the app swiping it in the list of running apps.
|
||||||
|
This problem did not happen with the previous Qt version.
|
||||||
|
After analysing changes we came up with this case and change:
|
||||||
|
https://codereview.qt-project.org/#/c/218882/
|
||||||
|
In summary they've moved libs loading to the same thread as main() and as a matter of correctness
|
||||||
|
in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
|
||||||
|
That exit call is the main reason of this problem.
|
||||||
|
|
||||||
|
In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
|
||||||
|
entire onDestroy method including that thread exit line for other three lines that purposely
|
||||||
|
terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
|
||||||
|
*/
|
||||||
|
QtNative.terminateQt();
|
||||||
|
QtNative.setActivity(null, null);
|
||||||
|
System.exit(0);
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -33,13 +33,15 @@ import com.squareup.picasso.Picasso;
|
||||||
import io.highfidelity.hifiinterface.fragment.FriendsFragment;
|
import io.highfidelity.hifiinterface.fragment.FriendsFragment;
|
||||||
import io.highfidelity.hifiinterface.fragment.HomeFragment;
|
import io.highfidelity.hifiinterface.fragment.HomeFragment;
|
||||||
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
|
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
|
||||||
|
import io.highfidelity.hifiinterface.fragment.ProfileFragment;
|
||||||
import io.highfidelity.hifiinterface.fragment.SettingsFragment;
|
import io.highfidelity.hifiinterface.fragment.SettingsFragment;
|
||||||
import io.highfidelity.hifiinterface.fragment.SignupFragment;
|
import io.highfidelity.hifiinterface.fragment.SignupFragment;
|
||||||
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
|
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
|
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
|
||||||
HomeFragment.OnHomeInteractionListener,
|
HomeFragment.OnHomeInteractionListener,
|
||||||
FriendsFragment.OnHomeInteractionListener {
|
FriendsFragment.OnHomeInteractionListener,
|
||||||
|
ProfileFragment.OnProfileInteractionListener {
|
||||||
|
|
||||||
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
|
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
|
||||||
public static final String DEFAULT_FRAGMENT = "Home";
|
public static final String DEFAULT_FRAGMENT = "Home";
|
||||||
|
@ -61,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
||||||
private View mProfilePanel;
|
private View mProfilePanel;
|
||||||
private TextView mLogoutOption;
|
private TextView mLogoutOption;
|
||||||
private MenuItem mPeopleMenuItem;
|
private MenuItem mPeopleMenuItem;
|
||||||
|
private MenuItem mProfileMenuItem;
|
||||||
|
|
||||||
private boolean backToScene;
|
private boolean backToScene;
|
||||||
private String backToUrl;
|
private String backToUrl;
|
||||||
|
@ -83,6 +86,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
||||||
|
|
||||||
mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
|
mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
|
||||||
|
|
||||||
|
mProfileMenuItem = mNavigationView.getMenu().findItem(R.id.action_profile);
|
||||||
|
|
||||||
updateDebugMenu(mNavigationView.getMenu());
|
updateDebugMenu(mNavigationView.getMenu());
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
@ -162,6 +167,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
||||||
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true);
|
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadProfileFragment() {
|
||||||
|
Fragment fragment = ProfileFragment.newInstance();
|
||||||
|
|
||||||
|
loadFragment(fragment, getString(R.string.profile), getString(R.string.tagFragmentProfile), true, true);
|
||||||
|
}
|
||||||
|
|
||||||
private void loadSettingsFragment() {
|
private void loadSettingsFragment() {
|
||||||
SettingsFragment fragment = SettingsFragment.newInstance();
|
SettingsFragment fragment = SettingsFragment.newInstance();
|
||||||
|
|
||||||
|
@ -261,6 +272,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
||||||
case R.id.action_people:
|
case R.id.action_people:
|
||||||
loadPeopleFragment();
|
loadPeopleFragment();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_profile:
|
||||||
|
loadProfileFragment();
|
||||||
|
break;
|
||||||
case R.id.action_debug_settings:
|
case R.id.action_debug_settings:
|
||||||
loadSettingsFragment();
|
loadSettingsFragment();
|
||||||
return true;
|
return true;
|
||||||
|
@ -351,6 +365,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
||||||
goToUser(username);
|
goToUser(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelProfileEdit() {
|
||||||
|
loadHomeFragment(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCompleteProfileEdit() {
|
||||||
|
loadHomeFragment(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAvatarChosen() {
|
||||||
|
loadHomeFragment(false);
|
||||||
|
}
|
||||||
|
|
||||||
private class RoundProfilePictureCallback implements Callback {
|
private class RoundProfilePictureCallback implements Callback {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
|
@ -9,6 +9,7 @@ import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
@ -27,9 +28,14 @@ public class PermissionChecker extends Activity {
|
||||||
private static final boolean CHOOSE_AVATAR_ON_STARTUP = false;
|
private static final boolean CHOOSE_AVATAR_ON_STARTUP = false;
|
||||||
private static final String TAG = "Interface";
|
private static final String TAG = "Interface";
|
||||||
|
|
||||||
|
private static final String EXTRA_ARGS = "args";
|
||||||
|
private String mArgs;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
mArgs =(getIntent().getStringExtra(EXTRA_ARGS));
|
||||||
|
|
||||||
Intent myIntent = new Intent(this, BreakpadUploaderService.class);
|
Intent myIntent = new Intent(this, BreakpadUploaderService.class);
|
||||||
startService(myIntent);
|
startService(myIntent);
|
||||||
if (CHOOSE_AVATAR_ON_STARTUP) {
|
if (CHOOSE_AVATAR_ON_STARTUP) {
|
||||||
|
@ -76,6 +82,11 @@ public class PermissionChecker extends Activity {
|
||||||
|
|
||||||
private void launchActivityWithPermissions(){
|
private void launchActivityWithPermissions(){
|
||||||
Intent i = new Intent(this, InterfaceActivity.class);
|
Intent i = new Intent(this, InterfaceActivity.class);
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(mArgs)) {
|
||||||
|
i.putExtra(EXTRA_ARGS, mArgs);
|
||||||
|
}
|
||||||
|
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
|
@ -7,6 +7,9 @@ import android.view.View;
|
||||||
|
|
||||||
public class SplashActivity extends Activity {
|
public class SplashActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String EXTRA_START_IN_DOMAIN = "start-in-domain";
|
||||||
|
private boolean mStartInDomain;
|
||||||
|
|
||||||
private native void registerLoadCompleteListener();
|
private native void registerLoadCompleteListener();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -36,13 +39,27 @@ public class SplashActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAppLoadedComplete() {
|
public void onAppLoadedComplete() {
|
||||||
if (HifiUtils.getInstance().isUserLoggedIn()) {
|
if (!mStartInDomain) {
|
||||||
startActivity(new Intent(this, MainActivity.class));
|
if (HifiUtils.getInstance().isUserLoggedIn()) {
|
||||||
} else {
|
startActivity(new Intent(this, MainActivity.class));
|
||||||
Intent menuIntent = new Intent(this, LoginMenuActivity.class);
|
} else {
|
||||||
menuIntent.putExtra(LoginMenuActivity.EXTRA_FINISH_ON_BACK, true);
|
Intent menuIntent = new Intent(this, LoginMenuActivity.class);
|
||||||
startActivity(menuIntent);
|
menuIntent.putExtra(LoginMenuActivity.EXTRA_FINISH_ON_BACK, true);
|
||||||
|
startActivity(menuIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SplashActivity.this.finish();
|
SplashActivity.this.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean(EXTRA_START_IN_DOMAIN, mStartInDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
|
mStartInDomain = savedInstanceState.getBoolean(EXTRA_START_IN_DOMAIN, false);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package io.highfidelity.hifiinterface.fragment;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import io.highfidelity.hifiinterface.R;
|
||||||
|
import io.highfidelity.hifiinterface.provider.AvatarProvider;
|
||||||
|
import io.highfidelity.hifiinterface.view.AvatarAdapter;
|
||||||
|
|
||||||
|
public class ProfileFragment extends Fragment {
|
||||||
|
|
||||||
|
private TextView mDisplayName;
|
||||||
|
|
||||||
|
private Button mOkButton;
|
||||||
|
private OnProfileInteractionListener mListener;
|
||||||
|
private AvatarProvider mAvatarsProvider;
|
||||||
|
|
||||||
|
private native String getDisplayName();
|
||||||
|
private native void setDisplayName(String name);
|
||||||
|
private native void setAvatarUrl(String url);
|
||||||
|
|
||||||
|
public ProfileFragment() {
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProfileFragment newInstance() {
|
||||||
|
ProfileFragment fragment = new ProfileFragment();
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_profile, container, false);
|
||||||
|
|
||||||
|
mDisplayName = rootView.findViewById(R.id.displayName);
|
||||||
|
mDisplayName.setText(getDisplayName());
|
||||||
|
mDisplayName.setOnEditorActionListener((textView, actionId, keyEvent) -> onDisplayNameEditorAction(textView, actionId, keyEvent));
|
||||||
|
|
||||||
|
mOkButton = rootView.findViewById(R.id.okButton);
|
||||||
|
mOkButton.setOnClickListener(view -> onOkButtonClicked());
|
||||||
|
|
||||||
|
rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelProfileEdit());
|
||||||
|
|
||||||
|
RecyclerView avatarsView = rootView.findViewById(R.id.gridview);
|
||||||
|
int numberOfColumns = 1;
|
||||||
|
mAvatarsProvider = new AvatarProvider(getContext());
|
||||||
|
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
|
||||||
|
avatarsView.setLayoutManager(gridLayoutMgr);
|
||||||
|
AvatarAdapter avatarAdapter = new AvatarAdapter(getContext(), mAvatarsProvider);
|
||||||
|
avatarsView.setAdapter(avatarAdapter);
|
||||||
|
avatarAdapter.loadAvatars();
|
||||||
|
|
||||||
|
avatarAdapter.setClickListener((view, position, avatar) -> {
|
||||||
|
setAvatarUrl(avatar.avatarUrl);
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onAvatarChosen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onOkButtonClicked() {
|
||||||
|
setDisplayName(mDisplayName.getText().toString());
|
||||||
|
View view = getActivity().getCurrentFocus();
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onCompleteProfileEdit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onDisplayNameEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
mOkButton.performClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCancelProfileEdit() {
|
||||||
|
View view = getActivity().getCurrentFocus();
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
|
if (mListener != null) {
|
||||||
|
mListener.onCancelProfileEdit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the back pressed event and returns true if it was managed by this Fragment
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean onBackPressed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (context instanceof OnProfileInteractionListener) {
|
||||||
|
mListener = (OnProfileInteractionListener) context;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(context.toString()
|
||||||
|
+ " must implement OnProfileInteractionListener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnProfileInteractionListener {
|
||||||
|
void onCancelProfileEdit();
|
||||||
|
void onCompleteProfileEdit();
|
||||||
|
void onAvatarChosen();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package io.highfidelity.hifiinterface.provider;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.highfidelity.hifiinterface.view.AvatarAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by gcalero on 1/21/19
|
||||||
|
*/
|
||||||
|
public class AvatarProvider {
|
||||||
|
|
||||||
|
private static final String AVATARS_JSON = "avatars.json";
|
||||||
|
private static final String JSON_FIELD_NAME = "name";
|
||||||
|
private static final String JSON_FIELD_URL = "url";
|
||||||
|
private static final String JSON_FIELD_IMAGE = "preview_image";
|
||||||
|
private static final String JSON_FIELD_AVATARS_ARRAY = "avatars";
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public interface AvatarsCallback {
|
||||||
|
void retrieveOk(List<AvatarAdapter.Avatar> avatars);
|
||||||
|
void retrieveError(Exception e, String message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvatarProvider(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void retrieve(AvatarsCallback avatarsCallback)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
JSONObject obj = new JSONObject(loadJSONFromAssets());
|
||||||
|
JSONArray m_jArry = obj.getJSONArray(JSON_FIELD_AVATARS_ARRAY);
|
||||||
|
ArrayList<AvatarAdapter.Avatar> avatars = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < m_jArry.length(); i++) {
|
||||||
|
JSONObject jo_inside = m_jArry.getJSONObject(i);
|
||||||
|
AvatarAdapter.Avatar anAvatar = new AvatarAdapter.Avatar();
|
||||||
|
anAvatar.avatarName = jo_inside.getString(JSON_FIELD_NAME);
|
||||||
|
anAvatar.avatarPreviewUrl = jo_inside.getString(JSON_FIELD_IMAGE);
|
||||||
|
anAvatar.avatarUrl = jo_inside.getString(JSON_FIELD_URL);
|
||||||
|
avatars.add(anAvatar);
|
||||||
|
}
|
||||||
|
avatarsCallback.retrieveOk(avatars);
|
||||||
|
} catch (IOException e) {
|
||||||
|
avatarsCallback.retrieveError(e, "Failed retrieving avatar JSON");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
avatarsCallback.retrieveError(e, "Failed parsing avatar JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String loadJSONFromAssets() throws IOException {
|
||||||
|
String json = null;
|
||||||
|
InputStream is = mContext.getAssets().open(AVATARS_JSON);
|
||||||
|
int size = is.available();
|
||||||
|
byte[] buffer = new byte[size];
|
||||||
|
is.read(buffer);
|
||||||
|
is.close();
|
||||||
|
json = new String(buffer, "UTF-8");
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package io.highfidelity.hifiinterface.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.highfidelity.hifiinterface.R;
|
||||||
|
import io.highfidelity.hifiinterface.provider.AvatarProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by gcalero on 1/21/19
|
||||||
|
*/
|
||||||
|
public class AvatarAdapter extends RecyclerView.Adapter<AvatarAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private static final String TAG = "Interface";
|
||||||
|
private final Context mContext;
|
||||||
|
private final LayoutInflater mInflater;
|
||||||
|
private final AvatarProvider mProvider;
|
||||||
|
private List<Avatar> mAvatars = new ArrayList<>();
|
||||||
|
private ItemClickListener mClickListener;
|
||||||
|
|
||||||
|
public AvatarAdapter(Context context, AvatarProvider provider) {
|
||||||
|
mContext = context;
|
||||||
|
mInflater = LayoutInflater.from(mContext);
|
||||||
|
mProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View view = mInflater.inflate(R.layout.avatar_item, parent, false);
|
||||||
|
return new AvatarAdapter.ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
|
AvatarAdapter.Avatar anAvatar = mAvatars.get(position);
|
||||||
|
assert(holder.mName != null);
|
||||||
|
holder.mName.setText(anAvatar.avatarName);
|
||||||
|
Uri uri = Uri.parse(anAvatar.avatarPreviewUrl);
|
||||||
|
Picasso.get().load(uri).into(holder.mPreviewImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mAvatars.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadAvatars() {
|
||||||
|
mProvider.retrieve(new AvatarProvider.AvatarsCallback() {
|
||||||
|
@Override
|
||||||
|
public void retrieveOk(List<AvatarAdapter.Avatar> avatars) {
|
||||||
|
mAvatars = new ArrayList<>(avatars);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void retrieveError(Exception e, String message) {
|
||||||
|
Log.e(TAG, message, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClickListener(ItemClickListener clickListener) {
|
||||||
|
mClickListener = clickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ItemClickListener {
|
||||||
|
void onItemClick(View view, int position, Avatar avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||||
|
|
||||||
|
TextView mName;
|
||||||
|
ImageView mPreviewImage;
|
||||||
|
|
||||||
|
public ViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
mName = itemView.findViewById(R.id.avatarName);
|
||||||
|
assert (mName != null);
|
||||||
|
mPreviewImage = itemView.findViewById(R.id.avatarPreview);
|
||||||
|
itemView.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
int position= getAdapterPosition();
|
||||||
|
if (mClickListener != null) {
|
||||||
|
mClickListener.onItemClick(view, position, mAvatars.get(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Avatar {
|
||||||
|
public String avatarName;
|
||||||
|
public String avatarUrl;
|
||||||
|
public String avatarPreviewUrl;
|
||||||
|
|
||||||
|
public Avatar() { }
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
17
android/apps/interface/src/main/res/drawable/ic_launcher.xml
Normal file
17
android/apps/interface/src/main/res/drawable/ic_launcher.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--suppress AndroidUnknownAttribute -->
|
||||||
|
<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:viewportWidth="192"
|
||||||
|
android:viewportHeight="192"
|
||||||
|
android:width="192dp"
|
||||||
|
android:height="192dp">
|
||||||
|
<path
|
||||||
|
android:pathData="M189.5 96.5A93.5 93.5 0 0 1 96 190 93.5 93.5 0 0 1 2.5 96.5 93.5 93.5 0 0 1 96 3 93.5 93.5 0 0 1 189.5 96.5Z"
|
||||||
|
android:fillColor="#333333" />
|
||||||
|
<path
|
||||||
|
android:pathData="M96.2 173.1c-10.3 0 -20.4 -2.1 -29.8 -6 -9.2 -3.8 -17.3 -9.4 -24.3 -16.4 -7 -7 -12.6 -15.2 -16.4 -24.3 -4.1 -9.6 -6.2 -19.6 -6.2 -30 0 -10.3 2.1 -20.4 6 -29.8 3.8 -9.2 9.4 -17.3 16.4 -24.3 7 -7 15.2 -12.6 24.3 -16.4 9.5 -4 19.5 -6 29.8 -6 10.3 0 20.4 2.1 29.8 6 9.2 3.8 17.3 9.4 24.3 16.4 7 7 12.6 15.2 16.4 24.3 4 9.5 6 19.5 6 29.8 0 10.3 -2.1 20.4 -6 29.8 -3.8 9.2 -9.4 17.3 -16.4 24.3 -7 7 -15.2 12.6 -24.3 16.4 -9.2 4.1 -19.3 6.2 -29.6 6.2zm0 -145.3c-37.8 0 -68.6 30.8 -68.6 68.6 0 37.8 30.8 68.6 68.6 68.6 37.8 0 68.6 -30.8 68.6 -68.6 0 -37.8 -30.8 -68.6 -68.6 -68.6z"
|
||||||
|
android:fillColor="#00b4f0" />
|
||||||
|
<path
|
||||||
|
android:pathData="M119.6 129l0 -53.8c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 25L79 83.8 79 64c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 54.1c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.7 -2.4 -6.9 -5.8 -8l0 -27.3 35 16.3 0 22.2c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.8 -2.4 -6.9 -5.8 -8z"
|
||||||
|
android:fillColor="#00b4f0" />
|
||||||
|
</vector>
|
23
android/apps/interface/src/main/res/layout/avatar_item.xml
Normal file
23
android/apps/interface/src/main/res/layout/avatar_item.xml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:paddingBottom="5dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/avatarName"
|
||||||
|
android:fontFamily="@font/raleway_bold"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/colorButton1" />
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatarPreview"
|
||||||
|
android:background="@color/backgroundDark"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="190dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/avatarName"/>
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue