mirror of
https://github.com/lubosz/overte.git
synced 2025-04-26 17:55:29 +02:00
Merge pull request #3 from highfidelity/master
Master Update PR 20190226
This commit is contained in:
commit
2bd8155e6a
2424 changed files with 103919 additions and 59695 deletions
.eslintrc.js.gitignoreBUILD.mdBUILD_ANDROID.mdBUILD_LINUX.mdBUILD_OSX.mdBUILD_QUEST.mdBUILD_WIN.mdCMakeLists.txtINSTALL.mdLICENSEVCPKG.md
android
app/src/main
java/io/highfidelity
res
apps
framePlayer
interface
CMakeLists.txtbuild.gradleproguard-rules.pro
src/main
AndroidManifest.xml
assets
cpp
java/io/highfidelity/hifiinterface
BreakpadUploaderService.javaHifiUtils.javaInterfaceActivity.javaLoginMenuActivity.javaMainActivity.javaPermissionChecker.javaSplashActivity.javaWebViewActivity.java
fragment
FriendsFragment.javaHomeFragment.javaLoginFragment.javaOnBackPressedListener.javaPolicyFragment.javaProfileFragment.javaSettingsFragment.javaSignupFragment.javaStartMenuFragment.javaWebViewFragment.java
provider
AvatarProvider.javaCallback.javaDomainProvider.javaEndpointUsersProvider.javaUserStoryDomainProvider.javaUsersProvider.java
view
receiver
task
view
res
drawable
default_profile_avatar.xmldomain_placeholder.pngencourage_login_background.jpghifi_header.xmlhifi_logo_header.xmlhifi_logo_splash.xmlic_bookmark.xmlic_clear.xmlic_close.xmlic_close_black_24dp.xmlic_delete_black_24dp.xmlic_expand.xmlic_eye_noshow.xmlic_eye_show.xmlic_launcher.xmlic_menu.xmlic_person.xmlic_right_arrow.xmlic_search.xmlic_share.xmlic_star.xmlic_steam.xmlic_teleporticon.xmllaunch_screen.xmlrounded_button_color1.xmlrounded_button_color3.xmlrounded_button_color4.xmlrounded_secondary_button.xmlsearch_bg.xmlselector_show_password.xml
font
|
@ -36,6 +36,7 @@ module.exports = {
|
|||
"GlobalServices": false,
|
||||
"GooglePoly": false,
|
||||
"Graphics": false,
|
||||
"HifiAbout": false,
|
||||
"HMD": false,
|
||||
"LaserPointers": false,
|
||||
"location": true,
|
||||
|
|
28
.gitignore
vendored
28
.gitignore
vendored
|
@ -14,13 +14,17 @@ Makefile
|
|||
|
||||
# Android Studio
|
||||
*.iml
|
||||
*.class
|
||||
local.properties
|
||||
android/gradle*
|
||||
android/.gradle
|
||||
android/app/src/main/jniLibs
|
||||
android/app/libs
|
||||
android/app/src/main/res/values/libs.xml
|
||||
android/app/src/main/assets/bundled
|
||||
android/**/src/main/jniLibs
|
||||
android/**/libs
|
||||
android/**/bin
|
||||
android/**/src/main/res/values/libs.xml
|
||||
android/**/src/main/assets
|
||||
android/**/gradle*
|
||||
*.class
|
||||
|
||||
# VSCode
|
||||
# List taken from Github Global Ignores master@435c4d92
|
||||
|
@ -83,9 +87,6 @@ npm-debug.log
|
|||
# Android studio files
|
||||
*___jb_old___
|
||||
|
||||
# Generated assets for Android
|
||||
android/app/src/main/assets
|
||||
|
||||
# Resource binary file
|
||||
interface/compiledResources
|
||||
|
||||
|
@ -94,3 +95,16 @@ interface/resources/GPUCache/*
|
|||
|
||||
# package lock file for JSDoc tool
|
||||
tools/jsdoc/package-lock.json
|
||||
|
||||
# Python compile artifacts
|
||||
**/__pycache__
|
||||
|
||||
# ignore unneeded unity project files for avatar exporter
|
||||
tools/unity-avatar-exporter/Library
|
||||
tools/unity-avatar-exporter/Logs
|
||||
tools/unity-avatar-exporter/Packages
|
||||
tools/unity-avatar-exporter/ProjectSettings
|
||||
tools/unity-avatar-exporter/Temp
|
||||
server-console/package-lock.json
|
||||
vcpkg/
|
||||
/tools/nitpick/compiledResources
|
||||
|
|
40
BUILD.md
40
BUILD.md
|
@ -9,14 +9,12 @@
|
|||
|
||||
- [cmake](https://cmake.org/download/): 3.9
|
||||
- [Qt](https://www.qt.io/download-open-source): 5.10.1
|
||||
- [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities.
|
||||
- [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
- [Python](https://www.python.org/downloads/): 3.6 or higher
|
||||
|
||||
### CMake External Project Dependencies
|
||||
|
||||
These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required.
|
||||
These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required.
|
||||
- [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83
|
||||
- [GLEW](http://glew.sourceforge.net/): 1.13
|
||||
- [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8
|
||||
- [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Win32) / 0.5 (Mac)
|
||||
- [OpenVR](https://github.com/ValveSoftware/openvr): 1.0.6 (Win32 only)
|
||||
|
@ -24,16 +22,15 @@ These dependencies need not be installed manually. They are automatically downlo
|
|||
- [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3
|
||||
- [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3
|
||||
- [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/): 4.3
|
||||
- [Sixense](http://sixense.com/): 071615
|
||||
- [vcpkg](https://github.com/highfidelity/vcpkg):
|
||||
- [VHACD](https://github.com/virneo/v-hacd)
|
||||
- [zlib](http://www.zlib.net/): 1.28 (Win32 only)
|
||||
- nVidia Texture Tools: 2.1
|
||||
- [nvtt](https://github.com/highfidelity/nvidia-texture-tools): 2.1.1 (customized)
|
||||
|
||||
The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build/ext` folder in each of the subfolders for each external project.
|
||||
|
||||
These are not placed in your normal build tree when doing an out of source build so that they do not need to be re-downloaded and re-compiled every time the CMake build folder is cleared. Should you want to force a re-download and re-compile of a specific external, you can simply remove that directory from the appropriate subfolder in `build/ext`. Should you want to force a re-download and re-compile of all externals, just remove the `build/ext` folder.
|
||||
|
||||
If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE\_LOCAL\_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
|
||||
|
||||
#### CMake
|
||||
|
||||
Hifi uses CMake to generate build files and project files for your platform.
|
||||
|
@ -51,6 +48,18 @@ The path it needs to be set to will depend on where and how Qt5 was installed. e
|
|||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
|
||||
|
||||
#### Vcpkg
|
||||
|
||||
Hifi uses vcpkg to download and build dependencies.
|
||||
You do not need to install vcpkg.
|
||||
|
||||
Building the dependencies can be lengthy and the resulting files will be stored in your OS temp directory.
|
||||
However, those files can potentially get cleaned up by the OS, so in order to avoid this and having to redo the lengthy build step, you can set the following environment variable:
|
||||
|
||||
export HIFI_VCPKG_BASE=/path/to/directory
|
||||
|
||||
Where /path/to/directory is the path to a directory where you wish the build files to get stored.
|
||||
|
||||
#### Generating build files
|
||||
|
||||
Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean.
|
||||
|
@ -81,9 +90,22 @@ In the examples below the variable $NAME would be replaced by the name of the de
|
|||
* $NAME_ROOT_DIR - set this variable in your ENV
|
||||
* HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name'
|
||||
|
||||
|
||||
### Optional Components
|
||||
|
||||
#### Build Options
|
||||
|
||||
The following build options can be used when running CMake
|
||||
|
||||
* BUILD_CLIENT
|
||||
* BUILD_SERVER
|
||||
* BUILD_TESTS
|
||||
* BUILD_TOOLS
|
||||
|
||||
#### Developer Build Options
|
||||
|
||||
* USE_GLES
|
||||
* DISABLE_UI
|
||||
|
||||
#### Devices
|
||||
|
||||
You can support external input/output devices such as Leap Motion, MIDI, and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device.
|
||||
|
|
|
@ -6,13 +6,8 @@ Building is currently supported on OSX, Windows and Linux platforms, but develop
|
|||
|
||||
You will need the following tools to build Android targets.
|
||||
|
||||
* [Gradle](https://gradle.org/install/)
|
||||
* [Android Studio](https://developer.android.com/studio/index.html)
|
||||
|
||||
### Gradle
|
||||
|
||||
Install gradle version 4.1 or higher. Following the instructions to install via [SDKMAN!](http://sdkman.io/install.html) is recommended.
|
||||
|
||||
### Android Studio
|
||||
|
||||
Download the Android Studio installer and run it. Once installed, at the welcome screen, click configure in the lower right corner and select SDK manager
|
||||
|
@ -29,6 +24,8 @@ From the SDK Tools tab select the following
|
|||
* Android SDK Tools
|
||||
* NDK (even if you have the NDK installed separately)
|
||||
|
||||
Make sure the NDK installed version is 18 (or higher)
|
||||
|
||||
# Environment
|
||||
|
||||
Setting up the environment for android builds requires some additional steps
|
||||
|
@ -51,17 +48,17 @@ Enter the repository `android` directory
|
|||
|
||||
`cd hifi/android`
|
||||
|
||||
Execute a gradle pre-build setup. This step should only need to be done once
|
||||
Execute two gradle pre-build steps. This steps should only need to be done once, unless you're working on the Android dependencies
|
||||
|
||||
`gradle setupDependencies`
|
||||
`./gradlew extractDependencies`
|
||||
|
||||
`./gradlew setupDependencies`
|
||||
|
||||
# Building & Running
|
||||
|
||||
* Open Android Studio
|
||||
* Choose _Open Existing Android Studio Project_
|
||||
* Navigate to the `hifi` repository and choose the `android` folder and select _OK_
|
||||
* If Android Studio asks you if you want to use the Gradle wrapper, select cancel and tell it where your local gradle installation is. If you used SDKMAN to install gradle it will be located in `$HOME/.sdkman/candidates/gradle/current/`
|
||||
* From the _Build_ menu select _Make Project_
|
||||
* Once the build completes, from the _Run_ menu select _Run App_
|
||||
|
||||
|
|
|
@ -40,6 +40,11 @@ Install build tools:
|
|||
sudo apt-get install cmake
|
||||
```
|
||||
|
||||
Install Python 3:
|
||||
```bash
|
||||
sudo apt-get install python3.6
|
||||
```
|
||||
|
||||
|
||||
### Get code and checkout the tag you need
|
||||
|
||||
|
@ -86,19 +91,73 @@ In a server, it does not make sense to compile interface
|
|||
|
||||
### Running the software
|
||||
|
||||
#### Domain server
|
||||
|
||||
Running domain server:
|
||||
```bash
|
||||
./domain-server/domain-server
|
||||
```
|
||||
|
||||
#### Assignment clients
|
||||
|
||||
Running assignment client:
|
||||
```bash
|
||||
./assignment-client/assignment-client -n 6
|
||||
```
|
||||
|
||||
#### Interface
|
||||
|
||||
Running interface:
|
||||
```bash
|
||||
./interface/interface
|
||||
```
|
||||
|
||||
Go to localhost in the running interface.
|
||||
|
||||
##### Ubuntu 18.04 only
|
||||
|
||||
In Ubuntu 18.04 there is a problem related with NVidia driver library version.
|
||||
|
||||
It can be workarounded following these steps:
|
||||
|
||||
Uninstall incompatible nvtt libraries:
|
||||
```bash
|
||||
sudo apt-get remove libnvtt2 libnvtt-dev
|
||||
```
|
||||
|
||||
Install libssl1.0-dev:
|
||||
```bash
|
||||
sudo apt-get -y install libssl1.0-dev
|
||||
```
|
||||
|
||||
Clone castano nvidia-texture-tools:
|
||||
```
|
||||
git clone https://github.com/castano/nvidia-texture-tools
|
||||
cd nvidia-texture-tools/
|
||||
```
|
||||
|
||||
Make these changes in repo:
|
||||
* In file **VERSION** set `2.2.1`
|
||||
* In file **configure**:
|
||||
* set `build="release"`
|
||||
* set `-DNVTT_SHARED=1`
|
||||
|
||||
Configure, build and install:
|
||||
```
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
Link compiled files:
|
||||
```
|
||||
sudo ln -s /usr/local/lib/libnvcore.so /usr/lib/libnvcore.so
|
||||
sudo ln -s /usr/local/lib/libnvimage.so /usr/lib/libnvimage.so
|
||||
sudo ln -s /usr/local/lib/libnvmath.so /usr/lib/libnvmath.so
|
||||
sudo ln -s /usr/local/lib/libnvtt.so /usr/lib/libnvtt.so
|
||||
```
|
||||
|
||||
After running this steps you can run interface:
|
||||
```
|
||||
interface/interface
|
||||
```
|
||||
|
|
|
@ -6,6 +6,10 @@ Please read the [general build guide](BUILD.md) for information on dependencies
|
|||
|
||||
brew install cmake openssl qt
|
||||
|
||||
### Python 3
|
||||
|
||||
Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/). Execute the `Update Shell Profile.command` script that is provided with the installer.
|
||||
|
||||
### OpenSSL
|
||||
|
||||
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
|
||||
|
@ -28,7 +32,9 @@ Note that this uses the version from the homebrew formula at the time of this wr
|
|||
|
||||
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
|
||||
|
||||
cmake .. -GXcode
|
||||
cmake .. -G Xcode
|
||||
|
||||
If `cmake` complains about Python 3 being missing, you may need to update your CMake binary with command `brew upgrade cmake`, or by downloading and running the latest CMake installer, depending on how you originally instaled CMake
|
||||
|
||||
After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run.
|
||||
|
||||
|
|
65
BUILD_QUEST.md
Normal file
65
BUILD_QUEST.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
Please read the [general build guide](BUILD.md) for information on building other platform. Only Quest specific instructions are found in this file.
|
||||
|
||||
# Dependencies
|
||||
|
||||
Building is currently supported on OSX, Windows and Linux platforms, but developers intending to do work on the library dependencies are strongly urged to use 64 bit Linux as a build platform
|
||||
|
||||
You will need the following tools to build Android targets.
|
||||
|
||||
* [Android Studio](https://developer.android.com/studio/index.html)
|
||||
|
||||
### Android Studio
|
||||
|
||||
Download the Android Studio installer and run it. Once installed, at the welcome screen, click configure in the lower right corner and select SDK manager
|
||||
|
||||
From the SDK Platforms tab, select API levels 24 and 26.
|
||||
|
||||
From the SDK Tools tab select the following
|
||||
|
||||
* Android SDK Build-Tools
|
||||
* GPU Debugging Tools
|
||||
* CMake (even if you have a separate CMake installation)
|
||||
* LLDB
|
||||
* Android SDK Platform-Tools
|
||||
* Android SDK Tools
|
||||
* NDK (even if you have the NDK installed separately)
|
||||
|
||||
Make sure the NDK installed version is 18 (or higher)
|
||||
|
||||
# Environment
|
||||
|
||||
Setting up the environment for android builds requires some additional steps
|
||||
|
||||
#### Set up machine specific Gradle properties
|
||||
|
||||
Create a `gradle.properties` file in $HOME/.gradle. Edit the file to contain the following
|
||||
|
||||
HIFI_ANDROID_PRECOMPILED=<your_home_directory>/Android/hifi_externals
|
||||
HIFI_ANDROID_KEYSTORE=<key_store_directory>/<keystore_name>.jks
|
||||
HIFI_ANDROID_KEYSTORE_PASSWORD=<password>
|
||||
HIFI_ANDROID_KEY_ALIAS=<key_alias>
|
||||
HIFI_ANDROID_KEY_PASSWORD=<key_password>
|
||||
|
||||
Note, do not use `$HOME` for the path. It must be a fully qualified path name.
|
||||
|
||||
### Setup the repository
|
||||
|
||||
Clone the repository
|
||||
|
||||
`git clone https://github.com/highfidelity/hifi.git`
|
||||
|
||||
Enter the repository `android` directory
|
||||
|
||||
`cd hifi/android`
|
||||
|
||||
# Building & Running
|
||||
|
||||
* Open Android Studio
|
||||
* Choose _Open Existing Android Studio Project_
|
||||
* Navigate to the `hifi` repository and choose the `android` folder and select _OK_
|
||||
* Open Gradle.settings and comment out any projects not necessary
|
||||
* From _File_ menu select _Sync with File System_ to resync Gradle settings
|
||||
* From the _Build_ menu select _Make Project_
|
||||
* From
|
||||
* Once the build completes, from the _Run_ menu select _Run App_
|
||||
|
33
BUILD_WIN.md
33
BUILD_WIN.md
|
@ -5,15 +5,21 @@ Note: We are now using Visual Studio 2017 and Qt 5.10.1. If you are upgrading fr
|
|||
|
||||
Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.
|
||||
|
||||
### Step 1. Visual Studio 2017
|
||||
### Step 1. Visual Studio 2017 & Python
|
||||
|
||||
If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
|
||||
|
||||
When selecting components, check "Desktop development with C++." Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)".
|
||||
When selecting components, check "Desktop development with C++". Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)". If you do not already have a python development environment installed, also check "Python Development" in this screen.
|
||||
|
||||
If you already have Visual Studio installed and need to add python, open the "Add or remove programs" control panel and find the "Microsoft Visual Studio Installer". Select it and click "Modify". In the installer, select "Modify" again, then check "Python Development" and allow the installer to apply the changes.
|
||||
|
||||
### Step 1a. Alternate Python
|
||||
|
||||
If you do not wish to use the Python installation bundled with Visual Studio, you can download the installer from [here](https://www.python.org/downloads/). Ensure you get version 3.6.6 or higher.
|
||||
|
||||
### Step 2. Installing CMake
|
||||
|
||||
Download and install the latest version of CMake 3.9.
|
||||
Download and install the latest version of CMake 3.9.
|
||||
|
||||
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
|
||||
|
||||
|
@ -29,20 +35,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables
|
|||
* Set "Variable name": `QT_CMAKE_PREFIX_PATH`
|
||||
* Set "Variable value": `C:\Qt\5.10.1\msvc2017_64\lib\cmake`
|
||||
|
||||
### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg)
|
||||
|
||||
* Clone the VCPKG [repository](https://github.com/Microsoft/vcpkg)
|
||||
* Follow the instructions in the [readme](https://github.com/Microsoft/vcpkg/blob/master/README.md) to bootstrap vcpkg
|
||||
* Note, you may need to do these in a _Developer Command Prompt_
|
||||
* Set an environment variable VCPKG_ROOT to the location of the cloned repository
|
||||
* Close and re-open any command prompts after setting the environment variable so that they will pick up the change
|
||||
|
||||
### Step 6. Installing OpenSSL via vcpkg
|
||||
|
||||
* In the vcpkg directory, install the 64 bit OpenSSL package with the command `.\vcpkg install openssl:x64-windows`
|
||||
* Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl`
|
||||
|
||||
### Step 7. Running CMake to Generate Build Files
|
||||
### Step 5. Running CMake to Generate Build Files
|
||||
|
||||
Run Command Prompt from Start and run the following commands:
|
||||
```
|
||||
|
@ -52,9 +45,9 @@ cd build
|
|||
cmake .. -G "Visual Studio 15 Win64"
|
||||
```
|
||||
|
||||
Where `%HIFI_DIR%` is the directory for the highfidelity repository.
|
||||
Where `%HIFI_DIR%` is the directory for the highfidelity repository.
|
||||
|
||||
### Step 8. Making a Build
|
||||
### Step 6. Making a Build
|
||||
|
||||
Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
|
||||
|
||||
|
@ -62,7 +55,7 @@ Change the Solution Configuration (menu ribbon under the menu bar, next to the g
|
|||
|
||||
Run from the menu bar `Build > Build Solution`.
|
||||
|
||||
### Step 9. Testing Interface
|
||||
### Step 7. Testing Interface
|
||||
|
||||
Create another environment variable (see Step #4)
|
||||
* Set "Variable name": `_NO_DEBUG_HEAP`
|
||||
|
|
|
@ -7,17 +7,36 @@ else()
|
|||
cmake_minimum_required(VERSION 3.2)
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/TargetPython.cmake")
|
||||
target_python()
|
||||
|
||||
if (HIFI_ANDROID )
|
||||
execute_process(
|
||||
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}
|
||||
)
|
||||
else()
|
||||
execute_process(
|
||||
COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --build-root ${CMAKE_BINARY_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
# squelch the Policy CMP0074 warning without requiring an update to cmake 3.12.
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${CMAKE_BINARY_DIR}/vcpkg.cmake")
|
||||
message(FATAL_ERROR "vcpkg configuration missing.")
|
||||
endif()
|
||||
|
||||
include("${CMAKE_BINARY_DIR}/vcpkg.cmake")
|
||||
project(hifi)
|
||||
|
||||
include("cmake/init.cmake")
|
||||
|
||||
include("cmake/compiler.cmake")
|
||||
|
||||
if (BUILD_SCRIBE_ONLY)
|
||||
add_subdirectory(tools/scribe)
|
||||
add_subdirectory(tools/shader_reflect)
|
||||
return()
|
||||
endif()
|
||||
add_paths_to_fixup_libs(${VCPKG_INSTALL_ROOT}/bin)
|
||||
add_paths_to_fixup_libs(${VCPKG_INSTALL_ROOT}/debug/bin)
|
||||
|
||||
if (NOT DEFINED CLIENT_ONLY)
|
||||
set(CLIENT_ONLY 0)
|
||||
|
@ -33,9 +52,11 @@ else()
|
|||
set(MOBILE 0)
|
||||
endif()
|
||||
|
||||
set(HIFI_USE_OPTIMIZED_IK OFF)
|
||||
set(BUILD_CLIENT_OPTION ON)
|
||||
set(BUILD_SERVER_OPTION ON)
|
||||
set(BUILD_TESTS_OPTION ON)
|
||||
set(BUILD_TESTS_OPTION OFF)
|
||||
set(BUILD_MANUAL_TESTS_OPTION ${BUILD_TESTS_OPTION})
|
||||
set(BUILD_TOOLS_OPTION ON)
|
||||
set(BUILD_INSTALLER_OPTION ON)
|
||||
set(GLES_OPTION OFF)
|
||||
|
@ -60,18 +81,46 @@ endif()
|
|||
if (ANDROID)
|
||||
set(GLES_OPTION ON)
|
||||
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()
|
||||
|
||||
# Allow client code to use preprocessor macros to distinguish between quest and non-quest builds
|
||||
if (${HIFI_ANDROID_APP} STREQUAL "questInterface")
|
||||
add_definitions(-DANDROID_APP_QUEST_INTERFACE)
|
||||
elseif(${HIFI_ANDROID_APP} STREQUAL "interface")
|
||||
add_definitions(-DANDROID_APP_INTERFACE)
|
||||
endif()
|
||||
else ()
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine Xml)
|
||||
endif ()
|
||||
|
||||
if (USE_GLES AND (NOT ANDROID))
|
||||
set(DISABLE_QML_OPTION ON)
|
||||
endif()
|
||||
|
||||
|
||||
option(HIFI_USE_OPTIMIZED_IK "USE OPTIMIZED IK" ${HIFI_USE_OPTIMIZED_IK_OPTION})
|
||||
option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
|
||||
option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
|
||||
option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
|
||||
option(BUILD_MANUAL_TESTS "Build manual tests" ${BUILD_MANUAL_TESTS_OPTION})
|
||||
option(BUILD_TOOLS "Build tools" ${BUILD_TOOLS_OPTION})
|
||||
option(BUILD_INSTALLER "Build installer" ${BUILD_INSTALLER_OPTION})
|
||||
option(USE_GLES "Use OpenGL ES" ${GLES_OPTION})
|
||||
|
@ -87,8 +136,10 @@ set(PLATFORM_QT_GL OpenGL)
|
|||
|
||||
if (USE_GLES)
|
||||
add_definitions(-DUSE_GLES)
|
||||
add_definitions(-DGPU_POINTER_STORAGE_SHARED)
|
||||
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles)
|
||||
else()
|
||||
add_definitions(-DGPU_POINTER_STORAGE_RAW)
|
||||
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl)
|
||||
endif()
|
||||
|
||||
|
@ -96,6 +147,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
|
|||
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
|
||||
endforeach()
|
||||
|
||||
MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_OPTIMIZED_IK})
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
|
||||
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
|
||||
|
@ -139,6 +191,12 @@ list(APPEND CMAKE_PREFIX_PATH "${QT_CMAKE_PREFIX_PATH}")
|
|||
find_package( Threads )
|
||||
|
||||
add_definitions(-DGLM_FORCE_RADIANS)
|
||||
add_definitions(-DGLM_ENABLE_EXPERIMENTAL)
|
||||
add_definitions(-DGLM_FORCE_CTOR_INIT)
|
||||
if (HIFI_USE_OPTIMIZED_IK)
|
||||
MESSAGE(STATUS "SET THE USE IK DEFINITION ")
|
||||
add_definitions(-DHIFI_USE_OPTIMIZED_IK)
|
||||
endif()
|
||||
set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries")
|
||||
|
||||
set(EXTERNAL_PROJECT_PREFIX "project")
|
||||
|
@ -151,7 +209,7 @@ set_packaging_parameters()
|
|||
|
||||
# FIXME hack to work on the proper Android toolchain
|
||||
if (ANDROID)
|
||||
add_subdirectory(android/app)
|
||||
add_subdirectory(android/apps/${HIFI_ANDROID_APP})
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
@ -186,7 +244,9 @@ if (BUILD_TESTS)
|
|||
include(CTest)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(tests-manual)
|
||||
if (BUILD_MANUAL_TESTS)
|
||||
add_subdirectory(tests-manual)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (BUILD_INSTALLER)
|
||||
|
|
75
INSTALL.md
75
INSTALL.md
|
@ -4,7 +4,8 @@ During generation, CMake should produce an `install` target and a `package` targ
|
|||
|
||||
### 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
|
||||
|
||||
|
@ -14,17 +15,67 @@ To produce an installer, run the `package` target.
|
|||
|
||||
To produce an executable installer on Windows, the following are required:
|
||||
|
||||
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
|
||||
- [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
|
||||
1. [7-zip](<https://www.7-zip.org/download.html>)
|
||||
|
||||
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
|
||||
|
||||
Run the `package` target to create an Apple Disk Image (.dmg).
|
||||
1. [npm](<https://www.npmjs.com/get-npm>)
|
||||
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
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2013-2016, High Fidelity, Inc.
|
||||
Copyright (c) 2013-2018, High Fidelity, Inc.
|
||||
All rights reserved.
|
||||
licensing@highfidelity.io
|
||||
|
||||
|
|
75
VCPKG.md
Normal file
75
VCPKG.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
[VCPKG](https://github.com/Microsoft/vcpkg) is an open source package management system created by Microsoft, intially just for Windows based system, but eventually extended to cover Linux and OSX as well, and in theory extensible enough to cover additional operating systems.
|
||||
|
||||
VCPKG is now our primary mechanism for managing the external libraries and tools on which we rely to build our applications.
|
||||
|
||||
Conventional usage of VCPKG involves cloning the repository, running the bootstrapping script to build the vcpkg binary, and then calling the binary to install a set of libraries. The libraries themselves are specified by a set of port files inside the [repository](https://github.com/Microsoft/vcpkg/tree/master/ports)
|
||||
|
||||
Because the main VCPKG repository does not contain all the ports we want, and because we want to be able to manage the precise versions of our dependencies, rather than allow it to be outside of our control, instead of using the main vcpkg repository, we use a combination of a [fork](https://github.com/highfidelity/vcpkg) of the repository (which allows us to customize the vcpkg binary, currently necessary to deal with some out of date tools on our build hosts) and a set of [custom port files](./cmake/ports) stored in our own repository.
|
||||
|
||||
## Adding new packages to vcpkg
|
||||
|
||||
Note... Android vcpkg usage is still experimental. Contact Austin for more detailed information if you need to add a new package for use by Android.
|
||||
|
||||
### Setup development environment
|
||||
|
||||
In order to add new packages, you will need to set up an environment for testing. This assumes you already have the tools for normal Hifi development (git, cmake, a working C++ compiler, etc)
|
||||
|
||||
* Clone our vcpkg [fork](https://github.com/highfidelity/vcpkg)
|
||||
* Remove the ports directory from the checkout and symlink to our own [custom port files](./cmake/ports)
|
||||
* Bootstrap the vcpkg binary with the `bootstrap-vcpkg.sh` or `bootstrap-vcpkg.bat` script
|
||||
|
||||
### Add a new port skeleton
|
||||
|
||||
Your new package will require, at minimum, a `CONTROL` file and a `portfile.cmake` file, located in a subdirectory of the ports folder. Assuming you're creating a new dependency named `foo` it should be located in `ports/foo` under the vcpkg directory. The `CONTROL` file will contain a small number of fields, such as the name, version, description and any other vcpkg ports on which you depend. The `portfile.cmake` is a CMake script that will instruct vcpkg how to build the packages. We'll cover that in more depth in a moment. For now, just create one and leave it blank.
|
||||
|
||||
### Add a reference to your package to one or more of the hifi meta-packages
|
||||
|
||||
We have three meta-packages used to do our building. When you modify one of these packages, make sure to bump the version number in the `CONTROL` file for the package
|
||||
|
||||
#### hifi-deps
|
||||
|
||||
This metapackage contains anything required for building the server or shared components. For instance, the `glm`, `tbb` and `zlib` packages are declared here because they're used everywhere, not just in our client application code.
|
||||
|
||||
#### hifi-client-deps
|
||||
|
||||
This metapackage contains anything required for building the client. For example, `sdl2` is listed here because it's required for our joystick input, but not for the server or shared components. Note that `hifi-client-deps` depends on `hifi-deps`, so you don't have to declare something twice if it's used in both he server and client. Just declare it in `hifi-deps` and it will still be includeded transitively.
|
||||
|
||||
#### hifi-host-tools
|
||||
|
||||
This metapackage contains anything we use to create executables that will then be used in the build process. The `hifi-deps` and `hifi-client-deps` packages are built for the target architecture, which may be different than the host architecture (for instance, when building for Android). The `hifi-host-tools` packages are always build for the host architecture, because they're tools that are intended to be run as part of the build process. Scribe for example is used at build time to generate shaders. Building an arm64 version of Scribe is useless because we need to run it on the host machine.
|
||||
|
||||
Note that packages can appear in both the `hifi-host-tools` and one of the other metapackages, indicating that the package both contains a library which we will use at runtime, and a tool which we will use at build time. The `spirv-tools` package is an example.
|
||||
|
||||
### Implement the portfile.cmake
|
||||
|
||||
How the portfile is written depends on what kind of package you're working with. It's basically still a CMake script, but there are a number of [functions](https://vcpkg.readthedocs.io/en/latest/maintainers/portfile-functions/) available to make fetching and building packages easier.
|
||||
|
||||
Typically there are three areas you need to deal with
|
||||
|
||||
* Getting the source
|
||||
* Building the source
|
||||
* Installing the artifacts
|
||||
|
||||
#### Getting sources
|
||||
|
||||
Getting sources from github, gitlab or bitbucket is easy. There are special functions specifcially for those. See the [etc2comp portfile](./cmake/ports/etc2comp/portfile.cmake) for an example of fetching source via github.
|
||||
|
||||
If the project isn't available that way, you can still use the [vcpkg_download_distfile](https://vcpkg.readthedocs.io/en/latest/maintainers/vcpkg_download_distfile/) function to explicitly download an archive and then use [vcpkg_extract_source_archive](https://vcpkg.readthedocs.io/en/latest/maintainers/vcpkg_extract_source_archive/) to unpack it. See the [zlib portfile](./cmake/ports/zlib/portfile.cmake) for an example there.
|
||||
|
||||
#### Building
|
||||
|
||||
If your package uses CMake, you'll be able to use the [vcpkg_configure_cmake](https://vcpkg.readthedocs.io/en/latest/maintainers/vcpkg_configure_cmake/) and [vcpkg_build_cmake](https://vcpkg.readthedocs.io/en/latest/maintainers/vcpkg_build_cmake/) commands to configure and build the package. If you're going to be relying on the CMake installation functionality, you can just call [vcpkg_install_cmake](https://vcpkg.readthedocs.io/en/latest/maintainers/vcpkg_install_cmake/), since it will implicitly run the build before the install.
|
||||
|
||||
If your package is not binary, but doesn't use CMake, you're just going to have to figure it out.
|
||||
|
||||
If your package is binary, then you can just skip this step
|
||||
|
||||
#### Installing
|
||||
|
||||
Once you've built the package, you need to install the artifacts in the target directory. Ideally, your package's CMake INSTALL commands will do the right thing. However, there are usually some things you have to do manually. Since VCPKG will build both the release and debug versions for all packages, you need to make sure if your package installed headers that you remove the _debug_ versions of these headers. This is typically done with the `file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)`. Additionally, if your package creates any standalone executables, you need to make sure they're installed in the destination `tools` directory, not the `bin` or `lib` directories, which are specifically for shared library binaries (like .so or .dll files) and link library files (like .a or .lib files) respectively.
|
||||
|
||||
If you're dealing with a binary package, then you'll need to explicitly perform all the required copies from the location where you extracted the archive to the installation directory. An example of this is available in the [openssl-android portfile](./cmake/ports/openssl-android/portfile.cmake)
|
||||
|
||||
### Commit and test
|
||||
|
||||
Once you've tested building your new package locally, you'll need to commit and push the changes and additions to the portfiles you've made and then monitor the build hosts to verify that the new package successfully built on all the target environments.
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
package io.highfidelity.hifiinterface.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
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.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.qtproject.qt5.android.QtNative;
|
||||
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
|
||||
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationActive;
|
||||
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationInactive;
|
||||
|
||||
public class LoginFragment extends Fragment {
|
||||
|
||||
private EditText mUsername;
|
||||
private EditText mPassword;
|
||||
private TextView mError;
|
||||
private TextView mForgotPassword;
|
||||
private TextView mSignup;
|
||||
private Button mLoginButton;
|
||||
|
||||
private ProgressDialog mDialog;
|
||||
|
||||
public native void nativeLogin(String username, String password, Activity usernameChangedListener);
|
||||
public native void nativeCancelLogin();
|
||||
|
||||
private LoginFragment.OnLoginInteractionListener mListener;
|
||||
|
||||
public LoginFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
public static LoginFragment newInstance() {
|
||||
LoginFragment fragment = new LoginFragment();
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_login, container, false);
|
||||
|
||||
mUsername = rootView.findViewById(R.id.username);
|
||||
mPassword = rootView.findViewById(R.id.password);
|
||||
mError = rootView.findViewById(R.id.error);
|
||||
mLoginButton = rootView.findViewById(R.id.loginButton);
|
||||
mForgotPassword = rootView.findViewById(R.id.forgotPassword);
|
||||
mSignup = rootView.findViewById(R.id.signupButton);
|
||||
|
||||
mLoginButton.setOnClickListener(view -> login());
|
||||
|
||||
mForgotPassword.setOnClickListener(view -> forgotPassword());
|
||||
mSignup.setOnClickListener(view -> signup());
|
||||
|
||||
mPassword.setOnEditorActionListener(
|
||||
(textView, actionId, keyEvent) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
mLoginButton.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof OnLoginInteractionListener) {
|
||||
mListener = (OnLoginInteractionListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement OnLoginInteractionListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// This hack intends to keep Qt threads running even after the app comes from background
|
||||
QtNative.setApplicationState(ApplicationActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
cancelActivityIndicator();
|
||||
// Leave the Qt app paused
|
||||
QtNative.setApplicationState(ApplicationInactive);
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
public void login() {
|
||||
String username = mUsername.getText().toString().trim();
|
||||
String password = mPassword.getText().toString();
|
||||
hideKeyboard();
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
showError(getString(R.string.login_username_or_password_incorrect));
|
||||
} else {
|
||||
mLoginButton.setEnabled(false);
|
||||
hideError();
|
||||
showActivityIndicator();
|
||||
nativeLogin(username, password, getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
public void signup() {
|
||||
if (mListener != null) {
|
||||
mListener.onSignupRequested();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
View view = getActivity().getCurrentFocus();
|
||||
if (view != null) {
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void forgotPassword() {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://highfidelity.com/users/password/new"));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void showActivityIndicator() {
|
||||
if (mDialog == null) {
|
||||
mDialog = new ProgressDialog(getContext());
|
||||
}
|
||||
mDialog.setMessage(getString(R.string.logging_in));
|
||||
mDialog.setCancelable(true);
|
||||
mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
nativeCancelLogin();
|
||||
cancelActivityIndicator();
|
||||
mLoginButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
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) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
mLoginButton.setEnabled(true);
|
||||
cancelActivityIndicator();
|
||||
if (success) {
|
||||
if (mListener != null) {
|
||||
mListener.onLoginCompleted();
|
||||
}
|
||||
} else {
|
||||
showError(getString(R.string.login_username_or_password_incorrect));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface OnLoginInteractionListener {
|
||||
void onLoginCompleted();
|
||||
void onSignupRequested();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package io.highfidelity.hifiinterface.fragment;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
|
||||
public class SignedInFragment extends Fragment {
|
||||
|
||||
private Button mGetStartedButton;
|
||||
private OnSignedInInteractionListener mListener;
|
||||
|
||||
public SignedInFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
public static SignedInFragment newInstance() {
|
||||
SignedInFragment fragment = new SignedInFragment();
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_signedin, container, false);
|
||||
mGetStartedButton = rootView.findViewById(R.id.getStarted);
|
||||
|
||||
mGetStartedButton.setOnClickListener(view -> {
|
||||
getStarted();
|
||||
});
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof SignedInFragment.OnSignedInInteractionListener) {
|
||||
mListener = (SignedInFragment.OnSignedInInteractionListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement OnSignedInInteractionListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public void getStarted() {
|
||||
if (mListener != null) {
|
||||
mListener.onGettingStarted();
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnSignedInInteractionListener {
|
||||
void onGettingStarted();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" android:padding="9dp">
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@android:color/black" />
|
||||
<solid android:color="@color/backgroundEditText"/>
|
||||
</shape>
|
|
@ -1,143 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLight">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/header"
|
||||
android:layout_width="@dimen/header_hifi_width"
|
||||
android:layout_height="@dimen/header_hifi_height"
|
||||
android:layout_marginTop="@dimen/header_hifi_margin_top"
|
||||
android:contentDescription="HighFidelity"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/hifi_header" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="25dp"
|
||||
android:layout_marginLeft="9dp"
|
||||
android:layout_marginRight="9dp"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textColor="@color/colorLoginError"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/username"
|
||||
app:layout_constraintLeft_toLeftOf="@id/username"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="46dp"
|
||||
android:layout_marginRight="46dp"
|
||||
android:background="@drawable/rounded_edit"
|
||||
android:padding="7dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="14dp"
|
||||
android:ems="10"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textSize="17sp"
|
||||
android:inputType="textEmailAddress"
|
||||
android:textStyle="italic"
|
||||
android:textColor="@color/editTextColor"
|
||||
android:textColorHint="@color/editTextColor"
|
||||
android:gravity="left|center_vertical"
|
||||
app:layout_constraintTop_toBottomOf="@id/header"
|
||||
android:layout_marginTop="70dp"
|
||||
android:hint="@string/username_or_email" />
|
||||
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/passwordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="46dp"
|
||||
android:layout_marginRight="46dp"
|
||||
app:passwordToggleTint="@color/showPasswordColor"
|
||||
app:passwordToggleEnabled="true"
|
||||
app:hintAnimationEnabled="false"
|
||||
app:passwordToggleDrawable="@drawable/selector_show_password"
|
||||
app:hintEnabled="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/username"
|
||||
android:layout_marginTop="13dp"
|
||||
>
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:background="@drawable/rounded_edit"
|
||||
android:padding="7dp"
|
||||
android:drawablePadding="55dp"
|
||||
android:paddingTop="14dp"
|
||||
android:drawableEnd="@drawable/ic_eye_noshow"
|
||||
android:ems="10"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="italic"
|
||||
android:textColor="@color/editTextColor"
|
||||
android:textColorHint="@color/editTextColor"
|
||||
android:gravity="left|center_vertical"
|
||||
android:imeOptions="actionDone"
|
||||
android:hint="@string/password"
|
||||
android:inputType="textPassword" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/loginButton"
|
||||
android:layout_width="154dp"
|
||||
android:layout_height="38dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingTop="0dp"
|
||||
android:text="@string/login"
|
||||
android:textColor="@color/white_opaque"
|
||||
android:textAllCaps="false"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintRight_toRightOf="@id/username"
|
||||
app:layout_constraintTop_toBottomOf="@id/forgotPassword"
|
||||
app:layout_goneMarginTop="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/forgotPassword"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="9dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:textSize="14dp"
|
||||
android:text="@string/forgot_password"
|
||||
android:textStyle="italic"
|
||||
app:layout_constraintRight_toRightOf="@id/passwordLayout"
|
||||
app:layout_constraintTop_toBottomOf="@id/passwordLayout"
|
||||
android:textColor="@color/colorButton1"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/signupButton"
|
||||
android:layout_width="0dp"
|
||||
app:layout_constraintWidth_default="spread"
|
||||
android:layout_height="38dp"
|
||||
android:background="@drawable/rounded_secondary_button"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingTop="0dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:text="@string/signup"
|
||||
android:textColor="@color/white_opaque"
|
||||
android:textAllCaps="false"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintLeft_toLeftOf="@id/passwordLayout"
|
||||
app:layout_constraintTop_toTopOf="@id/loginButton"
|
||||
app:layout_constraintRight_toLeftOf="@id/loginButton"
|
||||
app:layout_goneMarginTop="4dp"/>
|
||||
|
||||
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -1,63 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLight">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/header"
|
||||
android:layout_width="@dimen/header_hifi_width"
|
||||
android:layout_height="@dimen/header_hifi_height"
|
||||
android:layout_marginTop="@dimen/header_hifi_margin_top"
|
||||
android:contentDescription="HighFidelity"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/hifi_header" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/welcome"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="50dp"
|
||||
android:paddingLeft="86dp"
|
||||
android:paddingRight="86dp"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textColor="@color/clearText"
|
||||
android:textSize="24sp"
|
||||
android:text="@string/signedin_welcome"
|
||||
app:layout_constraintTop_toBottomOf="@id/header"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:gravity="center"
|
||||
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/getStarted"
|
||||
android:layout_width="217dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingLeft="25dp"
|
||||
android:paddingRight="25dp"
|
||||
android:paddingTop="0dp"
|
||||
android:text="@string/get_started"
|
||||
android:textColor="@color/white_opaque"
|
||||
android:textAllCaps="false"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/welcome"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
||||
app:layout_goneMarginTop="4dp"/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -1,151 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/backgroundLight">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/header"
|
||||
android:layout_width="@dimen/header_hifi_width"
|
||||
android:layout_height="@dimen/header_hifi_height"
|
||||
android:layout_marginTop="@dimen/header_hifi_margin_top"
|
||||
android:contentDescription="HighFidelity"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/hifi_header" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginLeft="9dp"
|
||||
android:layout_marginRight="9dp"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textColor="@color/colorLoginError"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/email"
|
||||
app:layout_constraintLeft_toLeftOf="@id/email"
|
||||
app:layout_constraintRight_toRightOf="@id/email"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="46dp"
|
||||
android:layout_marginRight="46dp"
|
||||
android:background="@drawable/rounded_edit"
|
||||
android:padding="7dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="14dp"
|
||||
android:ems="10"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textSize="17sp"
|
||||
android:inputType="textEmailAddress"
|
||||
android:textStyle="italic"
|
||||
android:textColor="@color/editTextColor"
|
||||
android:textColorHint="@color/editTextColor"
|
||||
android:gravity="left|center_vertical"
|
||||
app:layout_constraintTop_toBottomOf="@id/header"
|
||||
android:layout_marginTop="70dp"
|
||||
android:hint="@string/email" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginLeft="46dp"
|
||||
android:layout_marginRight="46dp"
|
||||
android:background="@drawable/rounded_edit"
|
||||
android:padding="7dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="14dp"
|
||||
android:ems="10"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textSize="17sp"
|
||||
android:inputType="text"
|
||||
android:textStyle="italic"
|
||||
android:textColor="@color/editTextColor"
|
||||
android:textColorHint="@color/editTextColor"
|
||||
android:gravity="left|center_vertical"
|
||||
app:layout_constraintTop_toBottomOf="@id/email"
|
||||
android:layout_marginTop="7dp"
|
||||
android:hint="@string/username" />
|
||||
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/passwordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="46dp"
|
||||
android:layout_marginRight="46dp"
|
||||
app:passwordToggleTint="@color/showPasswordColor"
|
||||
app:passwordToggleEnabled="true"
|
||||
app:hintAnimationEnabled="false"
|
||||
app:passwordToggleDrawable="@drawable/selector_show_password"
|
||||
app:hintEnabled="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/username"
|
||||
android:layout_marginTop="7dp"
|
||||
>
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:background="@drawable/rounded_edit"
|
||||
android:padding="7dp"
|
||||
android:drawablePadding="55dp"
|
||||
android:paddingTop="14dp"
|
||||
android:drawableEnd="@drawable/ic_eye_noshow"
|
||||
android:ems="10"
|
||||
android:fontFamily="@font/raleway"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="italic"
|
||||
android:textColor="@color/editTextColor"
|
||||
android:textColorHint="@color/editTextColor"
|
||||
android:gravity="left|center_vertical"
|
||||
android:imeOptions="actionDone"
|
||||
android:hint="@string/password"
|
||||
android:inputType="textPassword" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/signupButton"
|
||||
android:layout_width="154dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="44dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingTop="0dp"
|
||||
android:text="@string/signup"
|
||||
android:textColor="@color/white_opaque"
|
||||
android:textAllCaps="false"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintRight_toRightOf="@id/username"
|
||||
app:layout_constraintTop_toBottomOf="@id/passwordLayout"
|
||||
app:layout_goneMarginTop="4dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
android:layout_width="0dp"
|
||||
app:layout_constraintWidth_default="spread"
|
||||
android:layout_height="38dp"
|
||||
android:background="@drawable/rounded_secondary_button"
|
||||
android:fontFamily="@font/raleway_semibold"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingTop="0dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:text="@string/cancel"
|
||||
android:textColor="@color/white_opaque"
|
||||
android:textAllCaps="false"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintLeft_toLeftOf="@id/passwordLayout"
|
||||
app:layout_constraintTop_toTopOf="@id/signupButton"
|
||||
app:layout_constraintRight_toLeftOf="@id/signupButton"
|
||||
app:layout_goneMarginTop="4dp"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
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>
|
|
@ -1,13 +1,13 @@
|
|||
set(TARGET_NAME native-lib)
|
||||
setup_hifi_library()
|
||||
link_hifi_libraries(shared task networking gl gpu qml image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
|
||||
link_hifi_libraries(shared task networking gl gpu qml image fbx hfm render-utils physics entities octree ${PLATFORM_GL_BACKEND})
|
||||
target_opengl()
|
||||
target_bullet()
|
||||
|
||||
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface")
|
||||
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../interface")
|
||||
add_subdirectory("${INTERFACE_DIR}" "libraries/interface")
|
||||
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")
|
||||
|
||||
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/")
|
||||
target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src")
|
||||
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
|
||||
|
||||
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'
|
||||
|
||||
android {
|
||||
|
@ -19,19 +51,17 @@ android {
|
|||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DHIFI_ANDROID=1',
|
||||
'-DHIFI_ANDROID_APP=interface',
|
||||
'-DANDROID_PLATFORM=android-24',
|
||||
'-DANDROID_TOOLCHAIN=clang',
|
||||
'-DANDROID_STL=c++_shared',
|
||||
'-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake',
|
||||
'-DNATIVE_SCRIBE=' + HIFI_ANDROID_PRECOMPILED + '/scribe' + EXEC_SUFFIX,
|
||||
'-DNATIVE_SHREFLECT=' + HIFI_ANDROID_PRECOMPILED + '/shreflect' + EXEC_SUFFIX,
|
||||
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
|
||||
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
|
||||
'-DRELEASE_TYPE=' + RELEASE_TYPE,
|
||||
'-DSTABLE_BUILD=' + STABLE_BUILD,
|
||||
'-DDISABLE_QML=OFF',
|
||||
'-DDISABLE_KTX_CACHE=OFF',
|
||||
'-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF');
|
||||
targets = ['native-lib']
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
|
@ -53,6 +83,9 @@ android {
|
|||
debug {
|
||||
buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\""
|
||||
buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_SECRET", "\"" + (System.getenv("OAUTH_CLIENT_SECRET") ? System.getenv("OAUTH_CLIENT_SECRET") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_REDIRECT_URI", "\"" + (System.getenv("OAUTH_REDIRECT_URI") ? System.getenv("OAUTH_REDIRECT_URI") : '') + "\""
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
|
@ -63,12 +96,15 @@ android {
|
|||
project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null
|
||||
buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\""
|
||||
buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_SECRET", "\"" + (System.getenv("OAUTH_CLIENT_SECRET") ? System.getenv("OAUTH_CLIENT_SECRET") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_REDIRECT_URI", "\"" + (System.getenv("OAUTH_REDIRECT_URI") ? System.getenv("OAUTH_REDIRECT_URI") : '') + "\""
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path '../../CMakeLists.txt'
|
||||
path '../../../CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,6 +114,7 @@ android {
|
|||
variant.externalNativeBuildTasks.each { task ->
|
||||
variant.mergeResources.dependsOn(task)
|
||||
if (Os.isFamily(Os.FAMILY_UNIX)) {
|
||||
// FIXME
|
||||
def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${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()
|
||||
|
@ -93,7 +130,7 @@ android {
|
|||
|
||||
// Copy the compiled resources generated by the external native build
|
||||
copy {
|
||||
from new File(projectDir, "../../interface/compiledResources")
|
||||
from new File(projectDir, "../../../interface/compiledResources")
|
||||
into outputDir
|
||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||
eachFile { details ->
|
||||
|
@ -104,7 +141,7 @@ android {
|
|||
|
||||
// Copy the scripts directory
|
||||
copy {
|
||||
from new File(projectDir, "../../scripts")
|
||||
from new File(projectDir, "../../../scripts")
|
||||
into new File(outputDir, "scripts")
|
||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||
eachFile { details->
|
||||
|
@ -119,12 +156,6 @@ android {
|
|||
assetList.each { file -> out.println(file) }
|
||||
}
|
||||
}
|
||||
|
||||
variant.outputs.all {
|
||||
if (RELEASE_NUMBER != '0') {
|
||||
outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,23 +166,24 @@ dependencies {
|
|||
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
implementation 'com.android.support:design:26.1.0'
|
||||
compile 'com.android.support:support-v4:26.1.0'
|
||||
compile 'com.android.support:appcompat-v7:26.1.0'
|
||||
compile 'com.android.support:support-vector-drawable:26.1.0'
|
||||
api 'com.android.support:support-v4:26.1.0'
|
||||
api 'com.android.support:appcompat-v7:26.1.0'
|
||||
api 'com.android.support:support-vector-drawable:26.1.0'
|
||||
|
||||
implementation 'com.android.support:appcompat-v7:26.1.0'
|
||||
compile 'com.android.support:recyclerview-v7:26.1.0'
|
||||
compile 'com.android.support:cardview-v7:26.1.0'
|
||||
api 'com.android.support:recyclerview-v7:26.1.0'
|
||||
api 'com.android.support:cardview-v7:26.1.0'
|
||||
|
||||
compile 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
|
||||
api 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||
api 'com.squareup.retrofit2:converter-gson:2.4.0'
|
||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||
|
||||
compile 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
|
||||
api 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||
api 'com.squareup.retrofit2:converter-gson:2.4.0'
|
||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||
|
||||
compile '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
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.highfidelity.hifiinterface">
|
||||
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26" />
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
@ -71,6 +70,10 @@
|
|||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar" />
|
||||
|
||||
<activity android:name=".LoginMenuActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar" />
|
||||
|
||||
<service
|
||||
android:name=".BreakpadUploaderService"
|
||||
android:enabled="true"
|
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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -24,6 +24,9 @@
|
|||
#include <udt/PacketHeaders.h>
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#define AUTO_LOGOUT_SETTING_NAME "wallet/autoLogout"
|
||||
#define WALLET_USERNAME_SETTING_NAME "wallet/savedUsername"
|
||||
|
||||
QAndroidJniObject __interfaceActivity;
|
||||
QAndroidJniObject __loginCompletedListener;
|
||||
QAndroidJniObject __signupCompletedListener;
|
||||
|
@ -146,7 +149,7 @@ void unpackAndroidAssets() {
|
|||
|
||||
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);
|
||||
qRegisterMetaType<QAndroidJniObject>("QAndroidJniObject");
|
||||
__interfaceActivity = QAndroidJniObject(instance);
|
||||
|
@ -210,11 +213,13 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnDest
|
|||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUrl(JNIEnv* env, jobject obj, jstring url) {
|
||||
QAndroidJniObject jniUrl("java/lang/String", "(Ljava/lang/String;)V", url);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(jniUrl.toString());
|
||||
AndroidHelper::instance().muteMic();
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoToUser(JNIEnv* env, jobject obj, jstring username) {
|
||||
QAndroidJniObject jniUsername("java/lang/String", "(Ljava/lang/String;)V", username);
|
||||
DependencyManager::get<AddressManager>()->goToUser(jniUsername.toString(), false);
|
||||
AndroidHelper::instance().muteMic();
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
|
||||
|
@ -259,7 +264,56 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_fragment_HomeFragme
|
|||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeCancelLogin(JNIEnv *env, jobject instance) {
|
||||
Java_io_highfidelity_hifiinterface_HifiUtils_updateHifiSetting(JNIEnv *env, jobject instance,
|
||||
jstring group_, jstring key_,
|
||||
jboolean value_) {
|
||||
const char *c_group = env->GetStringUTFChars(group_, 0);
|
||||
const char *c_key = env->GetStringUTFChars(key_, 0);
|
||||
|
||||
const QString group = QString::fromUtf8(c_group);
|
||||
const QString key = QString::fromUtf8(c_key);
|
||||
|
||||
env->ReleaseStringUTFChars(group_, c_group);
|
||||
env->ReleaseStringUTFChars(key_, c_key);
|
||||
|
||||
bool value = value_;
|
||||
|
||||
Setting::Handle<bool> setting { QStringList() << group << key , !value };
|
||||
setting.set(value);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_highfidelity_hifiinterface_HifiUtils_getHifiSettingBoolean(JNIEnv *env,
|
||||
jobject instance,
|
||||
jstring group_,
|
||||
jstring key_,
|
||||
jboolean defaultValue) {
|
||||
const char *c_group = env->GetStringUTFChars(group_, 0);
|
||||
const char *c_key = env->GetStringUTFChars(key_, 0);
|
||||
|
||||
const QString group = QString::fromUtf8(c_group);
|
||||
const QString key = QString::fromUtf8(c_key);
|
||||
|
||||
env->ReleaseStringUTFChars(group_, c_group);
|
||||
env->ReleaseStringUTFChars(key_, c_key);
|
||||
|
||||
Setting::Handle<bool> setting { QStringList() << group << key , defaultValue};
|
||||
return setting.get();
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_highfidelity_hifiinterface_HifiUtils_isUserLoggedIn(JNIEnv *env, jobject instance) {
|
||||
return DependencyManager::get<AccountManager>()->isLoggedIn();
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_highfidelity_hifiinterface_HifiUtils_isKeepingLoggedIn(JNIEnv *env, jobject instance) {
|
||||
Setting::Handle<bool> setting(AUTO_LOGOUT_SETTING_NAME, true);
|
||||
return !setting.get();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_cancelLogin(JNIEnv *env, jobject instance) {
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
|
@ -269,17 +323,16 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeCancelLogin(JNIE
|
|||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeCancelLogin(JNIEnv *env,
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_cancelLogin(JNIEnv *env,
|
||||
jobject instance) {
|
||||
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeCancelLogin(env, instance);
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_cancelLogin(env, instance);
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *env, jobject instance,
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_login(JNIEnv *env, jobject instance,
|
||||
jstring username_, jstring password_,
|
||||
jobject usernameChangedListener) {
|
||||
jboolean keepLoggedIn) {
|
||||
const char *c_username = env->GetStringUTFChars(username_, 0);
|
||||
const char *c_password = env->GetStringUTFChars(password_, 0);
|
||||
QString username = QString(c_username);
|
||||
|
@ -290,7 +343,53 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en
|
|||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
__loginCompletedListener = QAndroidJniObject(instance);
|
||||
__usernameChangedListener = QAndroidJniObject(usernameChangedListener);
|
||||
|
||||
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [username, keepLoggedIn](const QUrl& authURL) {
|
||||
jboolean jSuccess = (jboolean) true;
|
||||
if (__loginCompletedListener.isValid()) {
|
||||
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
|
||||
}
|
||||
Setting::Handle<QVariant>(AUTO_LOGOUT_SETTING_NAME).set(!keepLoggedIn);
|
||||
QString usernameToSave = keepLoggedIn ? username : "";
|
||||
Setting::Handle<QVariant>(WALLET_USERNAME_SETTING_NAME).set(usernameToSave);
|
||||
});
|
||||
|
||||
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
|
||||
jboolean jSuccess = (jboolean) false;
|
||||
if (__loginCompletedListener.isValid()) {
|
||||
__loginCompletedListener.callMethod<void>("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_fragment_LoginFragment_retrieveAccessToken(JNIEnv *env,
|
||||
jobject instance,
|
||||
jstring authCode_,
|
||||
jstring clientId_,
|
||||
jstring clientSecret_,
|
||||
jstring redirectUri_) {
|
||||
const char *c_authCode = env->GetStringUTFChars(authCode_, 0);
|
||||
const char *c_clientId = env->GetStringUTFChars(clientId_, 0);
|
||||
const char *c_clientSecret = env->GetStringUTFChars(clientSecret_, 0);
|
||||
const char *c_redirectUri = env->GetStringUTFChars(redirectUri_, 0);
|
||||
|
||||
QString authCode = QString(c_authCode);
|
||||
QString clientId = QString(c_clientId);
|
||||
QString clientSecret = QString(c_clientSecret);
|
||||
QString redirectUri = QString(c_redirectUri);
|
||||
|
||||
env->ReleaseStringUTFChars(authCode_, c_authCode);
|
||||
env->ReleaseStringUTFChars(clientId_, c_clientId);
|
||||
env->ReleaseStringUTFChars(clientSecret_, c_clientSecret);
|
||||
env->ReleaseStringUTFChars(redirectUri_, c_redirectUri);
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
__loginCompletedListener = QAndroidJniObject(instance); // TODO: use a different listener?
|
||||
|
||||
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
|
||||
jboolean jSuccess = (jboolean) true;
|
||||
|
@ -306,24 +405,19 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en
|
|||
}
|
||||
});
|
||||
|
||||
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, [](const QString& username) {
|
||||
QAndroidJniObject string = QAndroidJniObject::fromString(username);
|
||||
if (__usernameChangedListener.isValid()) {
|
||||
__usernameChangedListener.callMethod<void>("handleUsernameChanged", "(Ljava/lang/String;)V", string.object<jstring>());
|
||||
}
|
||||
});
|
||||
QMetaObject::invokeMethod(accountManager.data(), "requestAccessTokenWithAuthCode",
|
||||
Q_ARG(const QString&, authCode), Q_ARG(const QString&, clientId),
|
||||
Q_ARG(const QString&, clientSecret), Q_ARG(const QString&, redirectUri));
|
||||
|
||||
QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken",
|
||||
Q_ARG(const QString&, username), Q_ARG(const QString&, password));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeLogin(JNIEnv *env,
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_login(JNIEnv *env,
|
||||
jobject instance,
|
||||
jstring username_,
|
||||
jstring password_,
|
||||
jobject usernameChangedListener) {
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(env, instance, username_, password_, usernameChangedListener);
|
||||
jboolean keepLoggedIn) {
|
||||
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_login(env, instance, username_, password_, keepLoggedIn);
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeInitAfterAppLoaded(JNIEnv* env, jobject obj) {
|
||||
|
@ -331,7 +425,7 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeInitAf
|
|||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeSignup(JNIEnv *env, jobject instance,
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_signup(JNIEnv *env, jobject instance,
|
||||
jstring email_, jstring username_,
|
||||
jstring password_) {
|
||||
|
||||
|
@ -359,8 +453,6 @@ Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeSignup(JNIEnv *
|
|||
});
|
||||
|
||||
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::handleSignupFailed, [](QString errorString) {
|
||||
jboolean jSuccess = (jboolean) false;
|
||||
jstring jError = QAndroidJniObject::fromString(errorString).object<jstring>();
|
||||
if (__signupCompletedListener.isValid()) {
|
||||
QAndroidJniObject string = QAndroidJniObject::fromString(errorString);
|
||||
__signupCompletedListener.callMethod<void>("handleSignupFailed", "(Ljava/lang/String;)V", string.object<jstring>());
|
||||
|
@ -371,19 +463,13 @@ Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeSignup(JNIEnv *
|
|||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeCancelSignup(JNIEnv *env, jobject instance) {
|
||||
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_cancelSignup(JNIEnv *env, jobject instance) {
|
||||
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::handleSignupCompleted, nullptr, nullptr);
|
||||
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::handleSignupFailed, nullptr, nullptr);
|
||||
|
||||
__signupCompletedListener = nullptr;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return accountManager->isLoggedIn();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeGetAccessToken(JNIEnv *env, jobject instance) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
@ -406,23 +492,68 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
|
|||
});
|
||||
|
||||
}
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_highfidelity_hifiinterface_MainActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
|
||||
return DependencyManager::get<AccountManager>()->isLoggedIn();
|
||||
|
||||
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_MainActivity_nativeLogout(JNIEnv *env, jobject instance) {
|
||||
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
|
||||
Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) {
|
||||
DependencyManager::get<AccountManager>()->logout();
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_io_highfidelity_hifiinterface_MainActivity_nativeGetDisplayName(JNIEnv *env,
|
||||
Java_io_highfidelity_hifiinterface_MainActivity_getUsername(JNIEnv *env,
|
||||
jobject instance) {
|
||||
QString username = DependencyManager::get<AccountManager>()->getAccountInfo().getUsername();
|
||||
return env->NewStringUTF(username.toLatin1().data());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_MainActivity_setUsernameChangedListener(JNIEnv *env,
|
||||
jobject instance,
|
||||
jobject usernameChangedListener) {
|
||||
__usernameChangedListener = QAndroidJniObject(usernameChangedListener);
|
||||
|
||||
if (!__usernameChangedListener.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, [](const QString& username) {
|
||||
QAndroidJniObject string = QAndroidJniObject::fromString(username);
|
||||
if (__usernameChangedListener.isValid()) {
|
||||
__usernameChangedListener.callMethod<void>("handleUsernameChanged", "(Ljava/lang/String;)V", string.object<jstring>());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeBeforeEnterBackground(JNIEnv *env, jobject obj) {
|
||||
AndroidHelper::instance().notifyBeforeEnterBackground();
|
||||
|
@ -443,46 +574,6 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_WebViewActivity_nativeProcessU
|
|||
AndroidHelper::instance().processURL(QString::fromUtf8(nativeString));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_updateHifiSetting(JNIEnv *env,
|
||||
jobject instance,
|
||||
jstring group_,
|
||||
jstring key_,
|
||||
jboolean value_) {
|
||||
const char *c_group = env->GetStringUTFChars(group_, 0);
|
||||
const char *c_key = env->GetStringUTFChars(key_, 0);
|
||||
|
||||
const QString group = QString::fromUtf8(c_group);
|
||||
const QString key = QString::fromUtf8(c_key);
|
||||
|
||||
env->ReleaseStringUTFChars(group_, c_group);
|
||||
env->ReleaseStringUTFChars(key_, c_key);
|
||||
|
||||
bool value = value_;
|
||||
|
||||
Setting::Handle<bool> setting { QStringList() << group << key , !value };
|
||||
setting.set(value);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_getHifiSettingBoolean(JNIEnv *env,
|
||||
jobject instance,
|
||||
jstring group_,
|
||||
jstring key_,
|
||||
jboolean defaultValue) {
|
||||
const char *c_group = env->GetStringUTFChars(group_, 0);
|
||||
const char *c_key = env->GetStringUTFChars(key_, 0);
|
||||
|
||||
const QString group = QString::fromUtf8(c_group);
|
||||
const QString key = QString::fromUtf8(c_key);
|
||||
|
||||
env->ReleaseStringUTFChars(group_, c_group);
|
||||
env->ReleaseStringUTFChars(key_, c_key);
|
||||
|
||||
Setting::Handle<bool> setting { QStringList() << group << key , defaultValue};
|
||||
return setting.get();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_hifiinterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env,
|
||||
jobject instance,
|
|
@ -64,4 +64,10 @@ public class HifiUtils {
|
|||
|
||||
public native String protocolVersionSignature();
|
||||
|
||||
public native boolean isUserLoggedIn();
|
||||
|
||||
public native void updateHifiSetting(String group, String key, boolean value);
|
||||
public native boolean getHifiSettingBoolean(String group, String key, boolean defaultValue);
|
||||
|
||||
public native boolean isKeepingLoggedIn();
|
||||
}
|
|
@ -24,6 +24,7 @@ import android.net.Uri;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -31,6 +32,7 @@ import android.view.WindowManager;
|
|||
import android.widget.FrameLayout;
|
||||
import android.widget.SlidingDrawer;
|
||||
|
||||
import org.qtproject.qt5.android.QtNative;
|
||||
import org.qtproject.qt5.android.QtLayout;
|
||||
import org.qtproject.qt5.android.QtSurface;
|
||||
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 EXTRA_GOTO_USERNAME = "gotousername";
|
||||
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_BOTTOM_MARGIN = 150;
|
||||
private static final int NORMAL_DPI = 160;
|
||||
|
@ -61,7 +64,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
private HeadsetStateReceiver headsetStateReceiver;
|
||||
|
||||
//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 nativeGotoUrl(String url);
|
||||
private native void nativeGoToUser(String username);
|
||||
|
@ -76,7 +79,9 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
private static boolean inVrMode;
|
||||
|
||||
private boolean nativeEnterBackgroundCallEnqueued = false;
|
||||
private SlidingDrawer webSlidingDrawer;
|
||||
private SlidingDrawer mWebSlidingDrawer;
|
||||
private boolean mStartInDomain;
|
||||
private boolean isLoading;
|
||||
// private GvrApi gvrApi;
|
||||
// Opaque native pointer to the Application C++ object.
|
||||
// This object is owned by the InterfaceActivity instance and passed to the native methods.
|
||||
|
@ -90,10 +95,16 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.isLoading = true;
|
||||
isLoading = true;
|
||||
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));
|
||||
} 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);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
@ -114,18 +125,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
assetManager = getResources().getAssets();
|
||||
|
||||
//nativeGvrApi =
|
||||
nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/);
|
||||
|
||||
Point size = new Point();
|
||||
getWindowManager().getDefaultDisplay().getRealSize(size);
|
||||
|
||||
try {
|
||||
PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
String version = pInfo.versionName;
|
||||
// setAppVersion(version);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e("GVR", "Error getting application version", e);
|
||||
}
|
||||
nativeOnCreate(assetManager /*, gvrApi.getNativeGvrContext()*/);
|
||||
|
||||
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||
|
||||
|
@ -135,35 +135,18 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
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);
|
||||
|
||||
FrameLayout mainLayout = findViewById(android.R.id.content);
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
webSlidingDrawer = (SlidingDrawer) inflater.inflate(R.layout.web_drawer, mainLayout, false);
|
||||
QtLayout qtLayout = (QtLayout) mainLayout.getChildAt(0);
|
||||
QtLayout.LayoutParams layoutParams = new QtLayout.LayoutParams(webSlidingDrawer.getLayoutParams());
|
||||
webSlidingDrawer.setOnDrawerCloseListener(() -> {
|
||||
WebViewFragment webViewFragment = (WebViewFragment) getFragmentManager().findFragmentByTag("webViewFragment");
|
||||
webViewFragment.close();
|
||||
});
|
||||
int widthPx = Math.max(size.x, size.y);
|
||||
int heightPx = Math.min(size.x, size.y);
|
||||
|
||||
layoutParams.x = (int) (widthPx - WEB_DRAWER_RIGHT_MARGIN * getResources().getDisplayMetrics().xdpi / NORMAL_DPI);
|
||||
layoutParams.y = (int) (heightPx - WEB_DRAWER_BOTTOM_MARGIN * getResources().getDisplayMetrics().ydpi / NORMAL_DPI);
|
||||
|
||||
layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL);
|
||||
qtLayout.addView(webSlidingDrawer, layoutParams);
|
||||
webSlidingDrawer.setVisibility(View.GONE);
|
||||
|
||||
headsetStateReceiver = new HeadsetStateReceiver();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (super.isLoading) {
|
||||
if (isLoading) {
|
||||
nativeEnterBackgroundCallEnqueued = true;
|
||||
} else {
|
||||
nativeEnterBackground();
|
||||
|
@ -190,15 +173,33 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
super.onResume();
|
||||
nativeEnterForeground();
|
||||
surfacesWorkaround();
|
||||
keepInterfaceRunning = false;
|
||||
registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
|
||||
//gvrApi.resumeTracking();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
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
|
||||
|
@ -289,14 +290,47 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
if (intent.hasExtra(DOMAIN_URL)) {
|
||||
webSlidingDrawer.setVisibility(View.GONE);
|
||||
hideWebDrawer();
|
||||
nativeGotoUrl(intent.getStringExtra(DOMAIN_URL));
|
||||
} else if (intent.hasExtra(EXTRA_GOTO_USERNAME)) {
|
||||
webSlidingDrawer.setVisibility(View.GONE);
|
||||
hideWebDrawer();
|
||||
nativeGoToUser(intent.getStringExtra(EXTRA_GOTO_USERNAME));
|
||||
}
|
||||
}
|
||||
|
||||
private void hideWebDrawer() {
|
||||
if (mWebSlidingDrawer != null) {
|
||||
mWebSlidingDrawer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void showWebDrawer() {
|
||||
if (mWebSlidingDrawer == null) {
|
||||
FrameLayout mainLayout = findViewById(android.R.id.content);
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
QtLayout qtLayout = (QtLayout) mainLayout.getChildAt(0);
|
||||
mWebSlidingDrawer = (SlidingDrawer) inflater.inflate(R.layout.web_drawer, mainLayout, false);
|
||||
|
||||
QtLayout.LayoutParams layoutParams = new QtLayout.LayoutParams(mWebSlidingDrawer.getLayoutParams());
|
||||
mWebSlidingDrawer.setOnDrawerCloseListener(() -> {
|
||||
WebViewFragment webViewFragment = (WebViewFragment) getFragmentManager().findFragmentByTag("webViewFragment");
|
||||
webViewFragment.close();
|
||||
});
|
||||
|
||||
Point size = new Point();
|
||||
getWindowManager().getDefaultDisplay().getRealSize(size);
|
||||
int widthPx = Math.max(size.x, size.y);
|
||||
int heightPx = Math.min(size.x, size.y);
|
||||
|
||||
layoutParams.x = (int) (widthPx - WEB_DRAWER_RIGHT_MARGIN * getResources().getDisplayMetrics().xdpi / NORMAL_DPI);
|
||||
layoutParams.y = (int) (heightPx - WEB_DRAWER_BOTTOM_MARGIN * getResources().getDisplayMetrics().ydpi / NORMAL_DPI);
|
||||
|
||||
layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL);
|
||||
qtLayout.addView(mWebSlidingDrawer, layoutParams);
|
||||
}
|
||||
mWebSlidingDrawer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void openAndroidActivity(String activityName, boolean backToScene) {
|
||||
openAndroidActivity(activityName, backToScene, null);
|
||||
}
|
||||
|
@ -313,29 +347,29 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
break;
|
||||
case "Login":
|
||||
nativeBeforeEnterBackground();
|
||||
Intent loginIntent = new Intent(this, MainActivity.class);
|
||||
loginIntent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
|
||||
loginIntent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
|
||||
Intent loginIntent = new Intent(this, LoginMenuActivity.class);
|
||||
loginIntent.putExtra(LoginMenuActivity.EXTRA_BACK_TO_SCENE, backToScene);
|
||||
loginIntent.putExtra(LoginMenuActivity.EXTRA_BACK_ON_SKIP, true);
|
||||
if (args != null && args.containsKey(DOMAIN_URL)) {
|
||||
loginIntent.putExtra(DOMAIN_URL, (String) args.get(DOMAIN_URL));
|
||||
loginIntent.putExtra(LoginMenuActivity.EXTRA_DOMAIN_URL, (String) args.get(DOMAIN_URL));
|
||||
}
|
||||
startActivity(loginIntent);
|
||||
break;
|
||||
case "WebView":
|
||||
runOnUiThread(() -> {
|
||||
webSlidingDrawer.setVisibility(View.VISIBLE);
|
||||
if (!webSlidingDrawer.isOpened()) {
|
||||
webSlidingDrawer.animateOpen();
|
||||
showWebDrawer();
|
||||
if (!mWebSlidingDrawer.isOpened()) {
|
||||
mWebSlidingDrawer.animateOpen();
|
||||
}
|
||||
if (args != null && args.containsKey(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL)) {
|
||||
WebViewFragment webViewFragment = (WebViewFragment) getFragmentManager().findFragmentByTag("webViewFragment");
|
||||
webViewFragment.loadUrl((String) args.get(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL), true);
|
||||
webViewFragment.setToolbarVisible(true);
|
||||
webViewFragment.setCloseAction(() -> {
|
||||
if (webSlidingDrawer.isOpened()) {
|
||||
webSlidingDrawer.animateClose();
|
||||
if (mWebSlidingDrawer.isOpened()) {
|
||||
mWebSlidingDrawer.animateClose();
|
||||
}
|
||||
webSlidingDrawer.setVisibility(View.GONE);
|
||||
hideWebDrawer();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -348,7 +382,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
}
|
||||
|
||||
public void onAppLoadedComplete() {
|
||||
super.isLoading = false;
|
||||
isLoading = false;
|
||||
if (nativeEnterBackgroundCallEnqueued) {
|
||||
nativeEnterBackground();
|
||||
}
|
||||
|
@ -379,6 +413,8 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
|
||||
@Override
|
||||
public void onExpand() {
|
||||
keepInterfaceRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOAuthAuthorizeCallback(Uri uri) { }
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
package io.highfidelity.hifiinterface;
|
||||
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
import io.highfidelity.hifiinterface.fragment.LoginFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.OnBackPressedListener;
|
||||
import io.highfidelity.hifiinterface.fragment.SignupFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.StartMenuFragment;
|
||||
|
||||
public class LoginMenuActivity extends AppCompatActivity
|
||||
implements StartMenuFragment.StartMenuInteractionListener,
|
||||
LoginFragment.OnLoginInteractionListener,
|
||||
SignupFragment.OnSignupInteractionListener {
|
||||
|
||||
/**
|
||||
* Set EXTRA_FINISH_ON_BACK to finish the app when back button is pressed
|
||||
*/
|
||||
public static final String EXTRA_FINISH_ON_BACK = "finishOnBack";
|
||||
|
||||
/**
|
||||
* Set EXTRA_BACK_TO_SCENE to back to the scene
|
||||
*/
|
||||
public static final String EXTRA_BACK_TO_SCENE = "backToScene";
|
||||
|
||||
/**
|
||||
* Set EXTRA_BACK_ON_SKIP to finish this activity when skip button is pressed
|
||||
*/
|
||||
public static final String EXTRA_BACK_ON_SKIP = "backOnSkip";
|
||||
|
||||
public static final String EXTRA_DOMAIN_URL = "url";
|
||||
|
||||
private boolean finishOnBack;
|
||||
private boolean backToScene;
|
||||
private boolean backOnSkip;
|
||||
private String domainUrlToBack;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_encourage_login);
|
||||
|
||||
finishOnBack = getIntent().getBooleanExtra(EXTRA_FINISH_ON_BACK, false);
|
||||
backToScene = getIntent().getBooleanExtra(EXTRA_BACK_TO_SCENE, false);
|
||||
domainUrlToBack = getIntent().getStringExtra(EXTRA_DOMAIN_URL);
|
||||
backOnSkip = getIntent().getBooleanExtra(EXTRA_BACK_ON_SKIP, false);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
finishOnBack = savedInstanceState.getBoolean(EXTRA_FINISH_ON_BACK, false);
|
||||
backToScene = savedInstanceState.getBoolean(EXTRA_BACK_TO_SCENE, false);
|
||||
backOnSkip = savedInstanceState.getBoolean(EXTRA_BACK_ON_SKIP, false);
|
||||
domainUrlToBack = savedInstanceState.getString(EXTRA_DOMAIN_URL);
|
||||
}
|
||||
|
||||
loadMenuFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(EXTRA_FINISH_ON_BACK, finishOnBack);
|
||||
outState.putBoolean(EXTRA_BACK_TO_SCENE, backToScene);
|
||||
outState.putString(EXTRA_DOMAIN_URL, domainUrlToBack);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
finishOnBack = savedInstanceState.getBoolean(EXTRA_FINISH_ON_BACK, false);
|
||||
backToScene = savedInstanceState.getBoolean(EXTRA_BACK_TO_SCENE, false);
|
||||
backOnSkip = savedInstanceState.getBoolean(EXTRA_BACK_ON_SKIP, false);
|
||||
domainUrlToBack = savedInstanceState.getString(EXTRA_DOMAIN_URL);
|
||||
}
|
||||
|
||||
private void loadMenuFragment() {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
Fragment fragment = StartMenuFragment.newInstance();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment);
|
||||
fragmentTransaction.addToBackStack(fragment.toString());
|
||||
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
||||
fragmentTransaction.commit();
|
||||
hideStatusBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
hideStatusBar();
|
||||
}
|
||||
|
||||
private void hideStatusBar() {
|
||||
View decorView = getWindow().getDecorView();
|
||||
// Hide the status bar.
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
decorView.setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignupButtonClicked() {
|
||||
loadSignupFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoginButtonClicked() {
|
||||
loadLoginFragment(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipLoginClicked() {
|
||||
if (backOnSkip) {
|
||||
onBackPressed();
|
||||
} else {
|
||||
loadMainActivity();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSteamLoginButtonClicked() {
|
||||
loadLoginFragment(true);
|
||||
}
|
||||
|
||||
private void loadSignupFragment() {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
Fragment fragment = SignupFragment.newInstance();
|
||||
String tag = getString(R.string.tagFragmentSignup);
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment, tag);
|
||||
fragmentTransaction.addToBackStack(tag);
|
||||
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
||||
fragmentTransaction.commit();
|
||||
hideStatusBar();
|
||||
}
|
||||
|
||||
private void loadLoginFragment(boolean useOauth) {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
Fragment fragment = LoginFragment.newInstance(useOauth);
|
||||
String tag = getString(R.string.tagFragmentLogin);
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment, tag);
|
||||
fragmentTransaction.addToBackStack(tag);
|
||||
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
||||
fragmentTransaction.commit();
|
||||
hideStatusBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoginCompleted() {
|
||||
loadMainActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelLogin() {
|
||||
getFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelSignup() {
|
||||
getFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
private void loadMainActivity() {
|
||||
finish();
|
||||
if (backToScene) {
|
||||
backToScene = false;
|
||||
goToDomain(domainUrlToBack != null? domainUrlToBack : "");
|
||||
} else {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
private void goToDomain(String domainUrl) {
|
||||
Intent intent = new Intent(this, InterfaceActivity.class);
|
||||
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
|
||||
finish();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSignupCompleted() {
|
||||
loadMainActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
FragmentManager fm = getFragmentManager();
|
||||
int index = fm.getBackStackEntryCount() - 1;
|
||||
if (index > 0) {
|
||||
FragmentManager.BackStackEntry backEntry = fm.getBackStackEntryAt(index);
|
||||
String tag = backEntry.getName();
|
||||
Fragment topFragment = getFragmentManager().findFragmentByTag(tag);
|
||||
if (!(topFragment instanceof OnBackPressedListener) ||
|
||||
!((OnBackPressedListener) topFragment).doBack()) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
} else if (finishOnBack){
|
||||
finishAffinity();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.highfidelity.hifiinterface;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
|
@ -29,23 +30,18 @@ import android.widget.TextView;
|
|||
import com.squareup.picasso.Callback;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.highfidelity.hifiinterface.fragment.FriendsFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.HomeFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.LoginFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.ProfileFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.SettingsFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.SignedInFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.SignupFragment;import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
|
||||
import io.highfidelity.hifiinterface.fragment.SignupFragment;
|
||||
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
|
||||
LoginFragment.OnLoginInteractionListener,
|
||||
HomeFragment.OnHomeInteractionListener,
|
||||
FriendsFragment.OnHomeInteractionListener,
|
||||
SignupFragment.OnSignupInteractionListener,
|
||||
SignedInFragment.OnSignedInInteractionListener {
|
||||
ProfileFragment.OnProfileInteractionListener {
|
||||
|
||||
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
|
||||
public static final String DEFAULT_FRAGMENT = "Home";
|
||||
|
@ -55,9 +51,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
|
||||
private String TAG = "HighFidelity";
|
||||
|
||||
public native boolean nativeIsLoggedIn();
|
||||
public native void nativeLogout();
|
||||
public native String nativeGetDisplayName();
|
||||
public native void logout();
|
||||
public native void setUsernameChangedListener(Activity usernameChangedListener);
|
||||
public native String getUsername();
|
||||
|
||||
private DrawerLayout mDrawerLayout;
|
||||
private NavigationView mNavigationView;
|
||||
|
@ -67,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
private View mProfilePanel;
|
||||
private TextView mLogoutOption;
|
||||
private MenuItem mPeopleMenuItem;
|
||||
private MenuItem mProfileMenuItem;
|
||||
|
||||
private boolean backToScene;
|
||||
private String backToUrl;
|
||||
|
@ -89,6 +86,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
|
||||
mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
|
||||
|
||||
mProfileMenuItem = mNavigationView.getMenu().findItem(R.id.action_profile);
|
||||
|
||||
updateDebugMenu(mNavigationView.getMenu());
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
|
@ -130,9 +129,6 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
|
||||
private void loadFragment(String fragment) {
|
||||
switch (fragment) {
|
||||
case "Login":
|
||||
loadLoginFragment();
|
||||
break;
|
||||
case "Home":
|
||||
loadHomeFragment(true);
|
||||
break;
|
||||
|
@ -153,19 +149,10 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
loadFragment(fragment, getString(R.string.home), getString(R.string.tagFragmentHome), addToBackStack, true);
|
||||
}
|
||||
|
||||
private void loadLoginFragment() {
|
||||
Fragment fragment = LoginFragment.newInstance();
|
||||
loadFragment(fragment, getString(R.string.login), getString(R.string.tagFragmentLogin), true, true);
|
||||
}
|
||||
|
||||
private void loadSignedInFragment() {
|
||||
Fragment fragment = SignedInFragment.newInstance();
|
||||
loadFragment(fragment, getString(R.string.welcome), getString(R.string.tagFragmentSignedIn), true, true);
|
||||
}
|
||||
|
||||
private void loadSignupFragment() {
|
||||
Fragment fragment = SignupFragment.newInstance();
|
||||
loadFragment(fragment, getString(R.string.signup), getString(R.string.tagFragmentSignup), true, false);
|
||||
private void startLoginMenuActivity() {
|
||||
Intent intent = new Intent(this, LoginMenuActivity.class);
|
||||
intent.putExtra(LoginMenuActivity.EXTRA_BACK_ON_SKIP, true);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void loadPrivacyPolicyFragment() {
|
||||
|
@ -180,6 +167,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
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() {
|
||||
SettingsFragment fragment = SettingsFragment.newInstance();
|
||||
|
||||
|
@ -223,7 +216,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
|
||||
|
||||
private void updateLoginMenu() {
|
||||
if (nativeIsLoggedIn()) {
|
||||
if (HifiUtils.getInstance().isUserLoggedIn()) {
|
||||
mLoginPanel.setVisibility(View.GONE);
|
||||
mProfilePanel.setVisibility(View.VISIBLE);
|
||||
mLogoutOption.setVisibility(View.VISIBLE);
|
||||
|
@ -239,7 +232,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
}
|
||||
|
||||
private void updateProfileHeader() {
|
||||
updateProfileHeader(nativeGetDisplayName());
|
||||
updateProfileHeader(getUsername());
|
||||
}
|
||||
private void updateProfileHeader(String username) {
|
||||
if (!username.isEmpty()) {
|
||||
|
@ -279,6 +272,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
case R.id.action_people:
|
||||
loadPeopleFragment();
|
||||
return true;
|
||||
case R.id.action_profile:
|
||||
loadProfileFragment();
|
||||
break;
|
||||
case R.id.action_debug_settings:
|
||||
loadSettingsFragment();
|
||||
return true;
|
||||
|
@ -289,15 +285,22 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
setUsernameChangedListener(this);
|
||||
updateLoginMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
setUsernameChangedListener(null);
|
||||
}
|
||||
|
||||
public void onLoginClicked(View view) {
|
||||
loadLoginFragment();
|
||||
startLoginMenuActivity();
|
||||
}
|
||||
|
||||
public void onLogoutClicked(View view) {
|
||||
nativeLogout();
|
||||
logout();
|
||||
updateLoginMenu();
|
||||
exitLoggedInFragment();
|
||||
|
||||
|
@ -338,42 +341,6 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoginCompleted() {
|
||||
loadHomeFragment(false);
|
||||
updateLoginMenu();
|
||||
if (backToScene) {
|
||||
backToScene = false;
|
||||
goToLastLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGettingStarted() {
|
||||
loadHomeFragment(false);
|
||||
if (backToScene) {
|
||||
backToScene = false;
|
||||
goToLastLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoginRequested() {
|
||||
// go back from signup to login
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignupRequested() {
|
||||
loadSignupFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignupCompleted() {
|
||||
loadSignedInFragment();
|
||||
updateLoginMenu();
|
||||
}
|
||||
|
||||
public void handleUsernameChanged(String username) {
|
||||
runOnUiThread(() -> updateProfileHeader(username));
|
||||
}
|
||||
|
@ -398,6 +365,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
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 {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
|
@ -418,7 +400,6 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
|
|||
public void onBackPressed() {
|
||||
// if a fragment needs to internally manage back presses..
|
||||
FragmentManager fm = getFragmentManager();
|
||||
Log.d("[BACK]", "getBackStackEntryCount " + fm.getBackStackEntryCount());
|
||||
Fragment friendsFragment = fm.findFragmentByTag(getString(R.string.tagFragmentPeople));
|
||||
if (friendsFragment != null && friendsFragment instanceof FriendsFragment) {
|
||||
if (((FriendsFragment) friendsFragment).onBackPressed()) {
|
|
@ -9,6 +9,7 @@ import android.content.pm.PackageManager;
|
|||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
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 String TAG = "Interface";
|
||||
|
||||
private static final String EXTRA_ARGS = "args";
|
||||
private String mArgs;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mArgs =(getIntent().getStringExtra(EXTRA_ARGS));
|
||||
|
||||
Intent myIntent = new Intent(this, BreakpadUploaderService.class);
|
||||
startService(myIntent);
|
||||
if (CHOOSE_AVATAR_ON_STARTUP) {
|
||||
|
@ -76,6 +82,11 @@ public class PermissionChecker extends Activity {
|
|||
|
||||
private void launchActivityWithPermissions(){
|
||||
Intent i = new Intent(this, InterfaceActivity.class);
|
||||
|
||||
if (!TextUtils.isEmpty(mArgs)) {
|
||||
i.putExtra(EXTRA_ARGS, mArgs);
|
||||
}
|
||||
|
||||
startActivity(i);
|
||||
finish();
|
||||
}
|
|
@ -3,11 +3,13 @@ package io.highfidelity.hifiinterface;
|
|||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
|
||||
public static final String EXTRA_START_IN_DOMAIN = "start-in-domain";
|
||||
private boolean mStartInDomain;
|
||||
|
||||
private native void registerLoadCompleteListener();
|
||||
|
||||
@Override
|
||||
|
@ -37,7 +39,27 @@ public class SplashActivity extends Activity {
|
|||
}
|
||||
|
||||
public void onAppLoadedComplete() {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
if (!mStartInDomain) {
|
||||
if (HifiUtils.getInstance().isUserLoggedIn()) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
} else {
|
||||
Intent menuIntent = new Intent(this, LoginMenuActivity.class);
|
||||
menuIntent.putExtra(LoginMenuActivity.EXTRA_FINISH_ON_BACK, true);
|
||||
startActivity(menuIntent);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -28,11 +28,18 @@ import java.net.MalformedURLException;
|
|||
import java.net.URL;
|
||||
|
||||
import io.highfidelity.hifiinterface.fragment.WebViewFragment;
|
||||
import io.highfidelity.hifiinterface.fragment.WebViewFragment.OnWebViewInteractionListener;
|
||||
|
||||
public class WebViewActivity extends Activity implements WebViewFragment.OnWebViewInteractionListener {
|
||||
public class WebViewActivity extends Activity implements OnWebViewInteractionListener {
|
||||
|
||||
public static final String WEB_VIEW_ACTIVITY_EXTRA_URL = "url";
|
||||
public static final String WEB_VIEW_ACTIVITY_EXTRA_CLEAR_COOKIES = "clear_cookies";
|
||||
public static final String RESULT_OAUTH_CODE = "code";
|
||||
public static final String RESULT_OAUTH_STATE = "state";
|
||||
|
||||
private static final String FRAGMENT_TAG = "WebViewActivity_WebFragment";
|
||||
private static final String OAUTH_CODE = "code";
|
||||
private static final String OAUTH_STATE = "state";
|
||||
|
||||
private native void nativeProcessURL(String url);
|
||||
|
||||
|
@ -47,14 +54,15 @@ public class WebViewActivity extends Activity implements WebViewFragment.OnWebVi
|
|||
mActionBar = getActionBar();
|
||||
mActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
loadWebViewFragment(getIntent().getStringExtra(WEB_VIEW_ACTIVITY_EXTRA_URL));
|
||||
loadWebViewFragment(getIntent().getStringExtra(WEB_VIEW_ACTIVITY_EXTRA_URL), getIntent().getBooleanExtra(WEB_VIEW_ACTIVITY_EXTRA_CLEAR_COOKIES, false));
|
||||
}
|
||||
|
||||
private void loadWebViewFragment(String url) {
|
||||
private void loadWebViewFragment(String url, boolean clearCookies) {
|
||||
WebViewFragment fragment = WebViewFragment.newInstance();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(WebViewFragment.URL, url);
|
||||
bundle.putBoolean(WebViewFragment.TOOLBAR_VISIBLE, false);
|
||||
bundle.putBoolean(WebViewFragment.CLEAR_COOKIES, clearCookies);
|
||||
fragment.setArguments(bundle);
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
FragmentTransaction ft = fragmentManager.beginTransaction();
|
||||
|
@ -131,4 +139,13 @@ public class WebViewActivity extends Activity implements WebViewFragment.OnWebVi
|
|||
@Override
|
||||
public void onExpand() { }
|
||||
|
||||
@Override
|
||||
public void onOAuthAuthorizeCallback(Uri uri) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(RESULT_OAUTH_CODE, uri.getQueryParameter(OAUTH_CODE));
|
||||
result.putExtra(RESULT_OAUTH_STATE, uri.getQueryParameter(OAUTH_STATE));
|
||||
setResult(Activity.RESULT_OK, result);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
|
@ -23,8 +23,6 @@ import io.highfidelity.hifiinterface.view.UserListAdapter;
|
|||
|
||||
public class FriendsFragment extends Fragment {
|
||||
|
||||
public native boolean nativeIsLoggedIn();
|
||||
|
||||
public native String nativeGetAccessToken();
|
||||
|
||||
private RecyclerView mUsersView;
|
|
@ -0,0 +1,328 @@
|
|||
package io.highfidelity.hifiinterface.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
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.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.qtproject.qt5.android.QtNative;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Random;
|
||||
|
||||
import io.highfidelity.hifiinterface.BuildConfig;
|
||||
import io.highfidelity.hifiinterface.HifiUtils;
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
import io.highfidelity.hifiinterface.WebViewActivity;
|
||||
|
||||
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationActive;
|
||||
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationInactive;
|
||||
|
||||
public class LoginFragment extends Fragment
|
||||
implements OnBackPressedListener {
|
||||
|
||||
private static final String ARG_USE_OAUTH = "use_oauth";
|
||||
private static final String TAG = "Interface";
|
||||
|
||||
private final String OAUTH_CLIENT_ID = BuildConfig.OAUTH_CLIENT_ID;
|
||||
private final String OAUTH_REDIRECT_URI = BuildConfig.OAUTH_REDIRECT_URI;
|
||||
private final String OAUTH_AUTHORIZE_BASE_URL = "https://highfidelity.com/oauth/authorize";
|
||||
private static final int OAUTH_AUTHORIZE_REQUEST = 1;
|
||||
|
||||
private EditText mUsername;
|
||||
private EditText mPassword;
|
||||
private TextView mError;
|
||||
private Button mLoginButton;
|
||||
private CheckBox mKeepMeLoggedInCheckbox;
|
||||
private ViewGroup mLoginForm;
|
||||
private ViewGroup mLoggingInFrame;
|
||||
private ViewGroup mLoggedInFrame;
|
||||
private boolean mLoginInProgress;
|
||||
private boolean mLoginSuccess;
|
||||
private boolean mUseOauth;
|
||||
private String mOauthState;
|
||||
|
||||
public native void login(String username, String password, boolean keepLoggedIn);
|
||||
private native void retrieveAccessToken(String authCode, String clientId, String clientSecret, String redirectUri);
|
||||
|
||||
public native void cancelLogin();
|
||||
|
||||
private LoginFragment.OnLoginInteractionListener mListener;
|
||||
|
||||
public LoginFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
public static LoginFragment newInstance(boolean useOauth) {
|
||||
LoginFragment fragment = new LoginFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(ARG_USE_OAUTH, useOauth);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getArguments() != null) {
|
||||
mUseOauth = getArguments().getBoolean(ARG_USE_OAUTH, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_login, container, false);
|
||||
|
||||
mUsername = rootView.findViewById(R.id.username);
|
||||
mPassword = rootView.findViewById(R.id.password);
|
||||
mError = rootView.findViewById(R.id.error);
|
||||
mLoginButton = rootView.findViewById(R.id.loginButton);
|
||||
mLoginForm = rootView.findViewById(R.id.loginForm);
|
||||
mLoggingInFrame = rootView.findViewById(R.id.loggingInFrame);
|
||||
mLoggedInFrame = rootView.findViewById(R.id.loggedInFrame);
|
||||
mKeepMeLoggedInCheckbox = rootView.findViewById(R.id.keepMeLoggedIn);
|
||||
|
||||
rootView.findViewById(R.id.forgotPassword).setOnClickListener(view -> onForgotPasswordClicked());
|
||||
|
||||
rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelLogin());
|
||||
|
||||
rootView.findViewById(R.id.getStarted).setOnClickListener(view -> onGetStartedClicked());
|
||||
|
||||
mLoginButton.setOnClickListener(view -> onLoginButtonClicked());
|
||||
|
||||
rootView.findViewById(R.id.takeMeInWorld).setOnClickListener(view -> skipLogin());
|
||||
mPassword.setOnEditorActionListener((textView, actionId, keyEvent) -> onPasswordEditorAction(textView, actionId, keyEvent));
|
||||
|
||||
mKeepMeLoggedInCheckbox.setChecked(HifiUtils.getInstance().isKeepingLoggedIn());
|
||||
|
||||
if (mUseOauth) {
|
||||
openWebForAuthorization();
|
||||
} else {
|
||||
showLoginForm();
|
||||
}
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof OnLoginInteractionListener) {
|
||||
mListener = (OnLoginInteractionListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement OnLoginInteractionListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// This hack intends to keep Qt threads running even after the app comes from background
|
||||
QtNative.setApplicationState(ApplicationActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
// Leave the Qt app paused
|
||||
QtNative.setApplicationState(ApplicationInactive);
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == OAUTH_AUTHORIZE_REQUEST) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
String authCode = data.getStringExtra(WebViewActivity.RESULT_OAUTH_CODE);
|
||||
String state = data.getStringExtra(WebViewActivity.RESULT_OAUTH_STATE);
|
||||
if (state != null && state.equals(mOauthState) && mListener != null) {
|
||||
mOauthState = null;
|
||||
showActivityIndicator();
|
||||
mLoginInProgress = true;
|
||||
retrieveAccessToken(authCode, BuildConfig.OAUTH_CLIENT_ID, BuildConfig.OAUTH_CLIENT_SECRET, BuildConfig.OAUTH_REDIRECT_URI);
|
||||
}
|
||||
} else {
|
||||
onCancelLogin();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void onCancelLogin() {
|
||||
if (mListener != null) {
|
||||
mListener.onCancelLogin();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onPasswordEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
mLoginButton.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void skipLogin() {
|
||||
if (mListener != null) {
|
||||
mListener.onSkipLoginClicked();
|
||||
}
|
||||
}
|
||||
|
||||
private void onGetStartedClicked() {
|
||||
if (mListener != null) {
|
||||
mListener.onLoginCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoginButtonClicked() {
|
||||
String username = mUsername.getText().toString().trim();
|
||||
String password = mPassword.getText().toString();
|
||||
hideKeyboard();
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
showError(getString(R.string.login_username_or_password_incorrect));
|
||||
} else {
|
||||
mLoginButton.setEnabled(false);
|
||||
hideError();
|
||||
showActivityIndicator();
|
||||
mLoginInProgress = true;
|
||||
mLoginSuccess = false;
|
||||
boolean keepUserLoggedIn = mKeepMeLoggedInCheckbox.isChecked();
|
||||
login(username, password, keepUserLoggedIn);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
View view = getActivity().getCurrentFocus();
|
||||
if (view != null) {
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void onForgotPasswordClicked() {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://highfidelity.com/users/password/new"));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void showActivityIndicator() {
|
||||
mLoginForm.setVisibility(View.GONE);
|
||||
mLoggedInFrame.setVisibility(View.GONE);
|
||||
mLoggingInFrame.setVisibility(View.VISIBLE);
|
||||
mLoggingInFrame.bringToFront();
|
||||
}
|
||||
|
||||
private void showLoginForm() {
|
||||
mLoggingInFrame.setVisibility(View.GONE);
|
||||
mLoggedInFrame.setVisibility(View.GONE);
|
||||
mLoginForm.setVisibility(View.VISIBLE);
|
||||
mLoginForm.bringToFront();
|
||||
}
|
||||
|
||||
private void showLoggedInMessage() {
|
||||
mLoginForm.setVisibility(View.GONE);
|
||||
mLoggingInFrame.setVisibility(View.GONE);
|
||||
mLoggedInFrame.setVisibility(View.VISIBLE);
|
||||
mLoggedInFrame.bringToFront();
|
||||
}
|
||||
|
||||
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) {
|
||||
mLoginInProgress = false;
|
||||
getActivity().runOnUiThread(() -> {
|
||||
mLoginButton.setEnabled(true);
|
||||
if (success) {
|
||||
mLoginSuccess = true;
|
||||
showLoggedInMessage();
|
||||
} else {
|
||||
if (!mUseOauth) {
|
||||
showLoginForm();
|
||||
showError(getString(R.string.login_username_or_password_incorrect));
|
||||
} else {
|
||||
openWebForAuthorization();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doBack() {
|
||||
if (mLoginInProgress) {
|
||||
cancelLogin();
|
||||
showLoginForm();
|
||||
mLoginInProgress = false;
|
||||
mLoginButton.setEnabled(true);
|
||||
return true;
|
||||
} else if (mLoginSuccess) {
|
||||
onGetStartedClicked();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOauthState() {
|
||||
// as we only use oauth for steam that's ok for now
|
||||
mOauthState = "steam-" + Long.toString(new Random().nextLong());
|
||||
}
|
||||
|
||||
private String buildAuthorizeUrl() {
|
||||
StringBuilder sb = new StringBuilder(OAUTH_AUTHORIZE_BASE_URL);
|
||||
sb.append("?client_id=").append(OAUTH_CLIENT_ID);
|
||||
try {
|
||||
String redirectUri = URLEncoder.encode(OAUTH_REDIRECT_URI, "utf-8");
|
||||
sb.append("&redirect_uri=").append(redirectUri);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "Cannot build oauth autorization url", e);
|
||||
}
|
||||
sb.append("&response_type=code&scope=owner");
|
||||
sb.append("&state=").append(mOauthState);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void openWebForAuthorization() {
|
||||
Intent openUrlIntent = new Intent(getActivity(), WebViewActivity.class);
|
||||
updateOauthState();
|
||||
openUrlIntent.putExtra(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL, buildAuthorizeUrl());
|
||||
openUrlIntent.putExtra(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_CLEAR_COOKIES, true);
|
||||
startActivityForResult(openUrlIntent, OAUTH_AUTHORIZE_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
public interface OnLoginInteractionListener {
|
||||
void onLoginCompleted();
|
||||
void onCancelLogin();
|
||||
void onSkipLoginClicked();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.highfidelity.hifiinterface.fragment;
|
||||
|
||||
public interface OnBackPressedListener {
|
||||
|
||||
/**
|
||||
* Processes the back pressed event and returns true if it was managed by this Fragment
|
||||
* @return
|
||||
*/
|
||||
boolean doBack();
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -3,32 +3,35 @@ package io.highfidelity.hifiinterface.fragment;
|
|||
import android.content.SharedPreferences;
|
||||
import android.media.audiofx.AcousticEchoCanceler;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import io.highfidelity.hifiinterface.HifiUtils;
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
public native void updateHifiSetting(String group, String key, boolean value);
|
||||
public native boolean getHifiSettingBoolean(String group, String key, boolean defaultValue);
|
||||
|
||||
private final String HIFI_SETTINGS_ANDROID_GROUP = "Android";
|
||||
private final String HIFI_SETTINGS_AEC_KEY = "aec";
|
||||
private final String PREFERENCE_KEY_AEC = "aec";
|
||||
|
||||
private final boolean DEFAULT_AEC_ENABLED = true;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.settings);
|
||||
boolean aecAvailable = AcousticEchoCanceler.isAvailable();
|
||||
PreferenceManager.setDefaultValues(getContext(), R.xml.settings, false);
|
||||
|
||||
if (!AcousticEchoCanceler.isAvailable()) {
|
||||
getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false);
|
||||
if (!aecAvailable) {
|
||||
findPreference(PREFERENCE_KEY_AEC).setEnabled(false);
|
||||
HifiUtils.getInstance().updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false);
|
||||
}
|
||||
|
||||
getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC,
|
||||
getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false));
|
||||
aecAvailable && HifiUtils.getInstance().getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, DEFAULT_AEC_ENABLED)).commit();
|
||||
}
|
||||
|
||||
public static SettingsFragment newInstance() {
|
||||
|
@ -46,15 +49,13 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
|
|||
public void onPause() {
|
||||
super.onPause();
|
||||
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
Preference pref = findPreference(key);
|
||||
switch (key) {
|
||||
case "aec":
|
||||
updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false));
|
||||
HifiUtils.getInstance().updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false));
|
||||
break;
|
||||
default:
|
||||
break;
|
|
@ -1,42 +1,50 @@
|
|||
package io.highfidelity.hifiinterface.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
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.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.qtproject.qt5.android.QtNative;
|
||||
|
||||
import io.highfidelity.hifiinterface.HifiUtils;
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
|
||||
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationActive;
|
||||
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationInactive;
|
||||
|
||||
public class SignupFragment extends Fragment {
|
||||
public class SignupFragment extends Fragment
|
||||
implements OnBackPressedListener {
|
||||
|
||||
private EditText mEmail;
|
||||
private EditText mUsername;
|
||||
private EditText mPassword;
|
||||
private TextView mError;
|
||||
private TextView mCancelButton;
|
||||
|
||||
private TextView mActivityText;
|
||||
private Button mSignupButton;
|
||||
private CheckBox mKeepMeLoggedInCheckbox;
|
||||
|
||||
private ProgressDialog mDialog;
|
||||
private ViewGroup mSignupForm;
|
||||
private ViewGroup mLoggingInFrame;
|
||||
private ViewGroup mLoggedInFrame;
|
||||
|
||||
public native void nativeSignup(String email, String username, String password); // move to SignupFragment
|
||||
public native void nativeCancelSignup();
|
||||
public native void nativeLogin(String username, String password, Activity usernameChangedListener);
|
||||
public native void nativeCancelLogin();
|
||||
private boolean mLoginInProgress;
|
||||
private boolean mSignupInProgress;
|
||||
private boolean mSignupSuccess;
|
||||
|
||||
public native void signup(String email, String username, String password); // move to SignupFragment
|
||||
public native void cancelSignup();
|
||||
public native void login(String username, String password, boolean keepLoggedIn);
|
||||
public native void cancelLogin();
|
||||
|
||||
private SignupFragment.OnSignupInteractionListener mListener;
|
||||
|
||||
|
@ -59,18 +67,23 @@ public class SignupFragment extends Fragment {
|
|||
mPassword = rootView.findViewById(R.id.password);
|
||||
mError = rootView.findViewById(R.id.error);
|
||||
mSignupButton = rootView.findViewById(R.id.signupButton);
|
||||
mCancelButton = rootView.findViewById(R.id.cancelButton);
|
||||
mActivityText = rootView.findViewById(R.id.activityText);
|
||||
mKeepMeLoggedInCheckbox = rootView.findViewById(R.id.keepMeLoggedIn);
|
||||
|
||||
mSignupForm = rootView.findViewById(R.id.signupForm);
|
||||
mLoggedInFrame = rootView.findViewById(R.id.loggedInFrame);
|
||||
mLoggingInFrame = rootView.findViewById(R.id.loggingInFrame);
|
||||
|
||||
rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelSignup());
|
||||
|
||||
mSignupButton.setOnClickListener(view -> signup());
|
||||
mCancelButton.setOnClickListener(view -> login());
|
||||
mPassword.setOnEditorActionListener(
|
||||
(textView, actionId, keyEvent) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
mSignupButton.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
rootView.findViewById(R.id.getStarted).setOnClickListener(view -> onGetStartedClicked());
|
||||
|
||||
mPassword.setOnEditorActionListener((textView, actionId, keyEvent) -> onPasswordEditorAction(textView, actionId, keyEvent));
|
||||
|
||||
mKeepMeLoggedInCheckbox.setChecked(HifiUtils.getInstance().isKeepingLoggedIn());
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
@ -101,15 +114,22 @@ public class SignupFragment extends Fragment {
|
|||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
cancelActivityIndicator();
|
||||
// Leave the Qt app paused
|
||||
QtNative.setApplicationState(ApplicationInactive);
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
private void login() {
|
||||
private boolean onPasswordEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
mSignupButton.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onCancelSignup() {
|
||||
if (mListener != null) {
|
||||
mListener.onLoginRequested();
|
||||
mListener.onCancelSignup();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,8 +143,11 @@ public class SignupFragment extends Fragment {
|
|||
} else {
|
||||
mSignupButton.setEnabled(false);
|
||||
hideError();
|
||||
mActivityText.setText(R.string.creating_account);
|
||||
showActivityIndicator();
|
||||
nativeSignup(email, username, password);
|
||||
mSignupInProgress = true;
|
||||
mSignupSuccess = false;
|
||||
signup(email, username, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,23 +160,21 @@ public class SignupFragment extends Fragment {
|
|||
}
|
||||
|
||||
private void showActivityIndicator() {
|
||||
if (mDialog == null) {
|
||||
mDialog = new ProgressDialog(getContext());
|
||||
}
|
||||
mDialog.setMessage(getString(R.string.creating_account));
|
||||
mDialog.setCancelable(true);
|
||||
mDialog.setOnCancelListener(dialogInterface -> {
|
||||
nativeCancelSignup();
|
||||
cancelActivityIndicator();
|
||||
mSignupButton.setEnabled(true);
|
||||
});
|
||||
mDialog.show();
|
||||
mSignupForm.setVisibility(View.GONE);
|
||||
mLoggedInFrame.setVisibility(View.GONE);
|
||||
mLoggingInFrame.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void cancelActivityIndicator() {
|
||||
if (mDialog != null) {
|
||||
mDialog.cancel();
|
||||
}
|
||||
private void showLoggedInMessage() {
|
||||
mSignupForm.setVisibility(View.GONE);
|
||||
mLoggingInFrame.setVisibility(View.GONE);
|
||||
mLoggedInFrame.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void showSignupForm() {
|
||||
mLoggingInFrame.setVisibility(View.GONE);
|
||||
mLoggedInFrame.setVisibility(View.GONE);
|
||||
mSignupForm.setVisibility(View.VISIBLE);
|
||||
}
|
||||
private void showError(String error) {
|
||||
mError.setText(error);
|
||||
|
@ -167,51 +188,73 @@ public class SignupFragment extends Fragment {
|
|||
|
||||
public interface OnSignupInteractionListener {
|
||||
void onSignupCompleted();
|
||||
void onLoginRequested();
|
||||
void onCancelSignup();
|
||||
}
|
||||
|
||||
private void onGetStartedClicked() {
|
||||
if (mListener != null) {
|
||||
mListener.onSignupCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handleSignupCompleted() {
|
||||
mSignupInProgress = false;
|
||||
String username = mUsername.getText().toString().trim();
|
||||
String password = mPassword.getText().toString();
|
||||
mDialog.setMessage(getString(R.string.logging_in));
|
||||
mDialog.setCancelable(true);
|
||||
mDialog.setOnCancelListener(dialogInterface -> {
|
||||
nativeCancelLogin();
|
||||
cancelActivityIndicator();
|
||||
if (mListener != null) {
|
||||
mListener.onLoginRequested();
|
||||
}
|
||||
getActivity().runOnUiThread(() -> {
|
||||
mActivityText.setText(R.string.logging_in);
|
||||
});
|
||||
mDialog.show();
|
||||
nativeLogin(username, password, getActivity());
|
||||
mLoginInProgress = true;
|
||||
boolean keepUserLoggedIn = mKeepMeLoggedInCheckbox.isChecked();
|
||||
login(username, password, keepUserLoggedIn);
|
||||
}
|
||||
|
||||
public void handleSignupFailed(String error) {
|
||||
mSignupInProgress = false;
|
||||
getActivity().runOnUiThread(() -> {
|
||||
mSignupButton.setEnabled(true);
|
||||
cancelActivityIndicator();
|
||||
showSignupForm();
|
||||
mError.setText(error);
|
||||
mError.setVisibility(View.VISIBLE);
|
||||
});
|
||||
}
|
||||
|
||||
public void handleLoginCompleted(boolean success) {
|
||||
mLoginInProgress = false;
|
||||
getActivity().runOnUiThread(() -> {
|
||||
mSignupButton.setEnabled(true);
|
||||
cancelActivityIndicator();
|
||||
|
||||
if (success) {
|
||||
if (mListener != null) {
|
||||
mListener.onSignupCompleted();
|
||||
}
|
||||
mSignupSuccess = true;
|
||||
showLoggedInMessage();
|
||||
} else {
|
||||
// Registration was successful but login failed.
|
||||
// Let the user to login manually
|
||||
mListener.onLoginRequested();
|
||||
mListener.onCancelSignup();
|
||||
showSignupForm();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doBack() {
|
||||
if (mSignupInProgress) {
|
||||
cancelSignup();
|
||||
} else if (mLoginInProgress) {
|
||||
cancelLogin();
|
||||
}
|
||||
|
||||
|
||||
if (mSignupInProgress || mLoginInProgress) {
|
||||
showSignupForm();
|
||||
mLoginInProgress = false;
|
||||
mSignupInProgress = false;
|
||||
mSignupButton.setEnabled(true);
|
||||
return true;
|
||||
} else if (mSignupSuccess) {
|
||||
onGetStartedClicked();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package io.highfidelity.hifiinterface.fragment;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
|
||||
public class StartMenuFragment extends Fragment {
|
||||
|
||||
private String TAG = "HighFidelity";
|
||||
private StartMenuInteractionListener mListener;
|
||||
|
||||
public StartMenuFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
public static StartMenuFragment newInstance() {
|
||||
StartMenuFragment fragment = new StartMenuFragment();
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View rootView = inflater.inflate(R.layout.fragment_login_menu, container, false);
|
||||
rootView.findViewById(R.id.signupButton).setOnClickListener(view -> {
|
||||
if (mListener != null) {
|
||||
mListener.onSignupButtonClicked();
|
||||
}
|
||||
});
|
||||
|
||||
rootView.findViewById(R.id.loginButton).setOnClickListener(view -> {
|
||||
if (mListener != null) {
|
||||
mListener.onLoginButtonClicked();
|
||||
}
|
||||
});
|
||||
|
||||
rootView.findViewById(R.id.steamLoginButton).setOnClickListener(view -> {
|
||||
if (mListener != null) {
|
||||
mListener.onSteamLoginButtonClicked();
|
||||
}
|
||||
});
|
||||
|
||||
rootView.findViewById(R.id.takeMeInWorld).setOnClickListener(view -> {
|
||||
if (mListener != null) {
|
||||
mListener.onSkipLoginClicked();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof StartMenuInteractionListener) {
|
||||
mListener = (StartMenuInteractionListener) context;
|
||||
} else {
|
||||
throw new RuntimeException(context.toString()
|
||||
+ " must implement StartMenuInteractionListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface must be implemented by activities that contain this
|
||||
* fragment to allow an interaction in this fragment to be communicated
|
||||
* to the activity and potentially other fragments contained in that
|
||||
* activity.
|
||||
* <p>
|
||||
* See the Android Training lesson <a href=
|
||||
* "http://developer.android.com/training/basics/fragments/communicating.html"
|
||||
* >Communicating with Other Fragments</a> for more information.
|
||||
*/
|
||||
public interface StartMenuInteractionListener {
|
||||
void onSignupButtonClicked();
|
||||
void onLoginButtonClicked();
|
||||
void onSkipLoginClicked();
|
||||
void onSteamLoginButtonClicked();
|
||||
}
|
||||
}
|
|
@ -4,9 +4,11 @@ import android.app.Fragment;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -14,6 +16,7 @@ import android.view.MotionEvent;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceError;
|
||||
|
@ -25,6 +28,7 @@ import android.webkit.WebViewClient;
|
|||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import io.highfidelity.hifiinterface.BuildConfig;
|
||||
import io.highfidelity.hifiinterface.R;
|
||||
import io.highfidelity.hifiinterface.WebViewActivity;
|
||||
|
||||
|
@ -32,6 +36,7 @@ public class WebViewFragment extends Fragment implements GestureDetector.OnGestu
|
|||
|
||||
public static final String URL = "url";
|
||||
public static final String TOOLBAR_VISIBLE = "toolbar_visible";
|
||||
public static final String CLEAR_COOKIES = "clear_cookies";
|
||||
private static final long DELAY_HIDE_TOOLBAR_MILLIS = 3000;
|
||||
private static final long FADE_OUT_DURATION = 2000;
|
||||
|
||||
|
@ -41,6 +46,7 @@ public class WebViewFragment extends Fragment implements GestureDetector.OnGestu
|
|||
private ProgressBar mProgressBar;
|
||||
private String mUrl;
|
||||
private boolean mToolbarVisible;
|
||||
private boolean mClearCookies;
|
||||
|
||||
private OnWebViewInteractionListener mListener;
|
||||
private Runnable mCloseAction;
|
||||
|
@ -170,6 +176,7 @@ public class WebViewFragment extends Fragment implements GestureDetector.OnGestu
|
|||
if (getArguments() != null) {
|
||||
mUrl = getArguments().getString(URL);
|
||||
mToolbarVisible = getArguments().getBoolean(TOOLBAR_VISIBLE);
|
||||
mClearCookies = getArguments().getBoolean(CLEAR_COOKIES);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,6 +186,10 @@ public class WebViewFragment extends Fragment implements GestureDetector.OnGestu
|
|||
View rootView = inflater.inflate(R.layout.fragment_web_view, container, false);
|
||||
mProgressBar = rootView.findViewById(R.id.toolbarProgressBar);
|
||||
myWebView = rootView.findViewById(R.id.web_view);
|
||||
if (mClearCookies) {
|
||||
CookieManager.getInstance().removeAllCookies(null);
|
||||
}
|
||||
|
||||
mHandler = new Handler();
|
||||
gestureDetector = new GestureDetector(this);
|
||||
gestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
|
||||
|
@ -251,6 +262,7 @@ public class WebViewFragment extends Fragment implements GestureDetector.OnGestu
|
|||
void onWebLoaded(String url, SafenessLevel safenessLevel);
|
||||
void onTitleReceived(String title);
|
||||
void onExpand();
|
||||
void onOAuthAuthorizeCallback(Uri uri);
|
||||
}
|
||||
|
||||
|
||||
|
@ -320,6 +332,18 @@ public class WebViewFragment extends Fragment implements GestureDetector.OnGestu
|
|||
super.onLoadResource(view, url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
if (!TextUtils.isEmpty(BuildConfig.OAUTH_REDIRECT_URI) &&
|
||||
request.getUrl().toString().startsWith(BuildConfig.OAUTH_REDIRECT_URI)) {
|
||||
if (mListener != null) {
|
||||
mListener.onOAuthAuthorizeCallback(request.getUrl());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.shouldOverrideUrlLoading(view, request);
|
||||
}
|
||||
}
|
||||
|
||||
class HiFiWebChromeClient extends WebChromeClient {
|
|
@ -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 ![]() (image error) Size: 5.6 KiB After ![]() (image error) Size: 5.6 KiB ![]() ![]() |
Binary file not shown.
After ![]() (image error) Size: 48 KiB |
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="350dp"
|
||||
android:width="340dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="350"
|
||||
android:viewportHeight="100">
|
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>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="11dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="11"
|
||||
android:viewportHeight="14">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M2.98427,0.868092 C2.35913,0.324495,1.41169,0.390596,0.868092,1.01573 C0.324495,1.64087,0.390596,2.58831,1.01573,3.13191 L2.98427,0.868092 Z M8,7.21739 L8.93497,8.39035 L10.3436,7.26752 L8.98427,6.08548 L8,7.21739 Z M1.06503,10.827 C0.417224,11.3434,0.310672,12.2872,0.82704,12.935 C1.34341,13.5828,2.28716,13.6893,2.93497,13.173 L1.06503,10.827 Z M1.01573,3.13191 L7.01573,8.3493 L8.98427,6.08548 L2.98427,0.868092 L1.01573,3.13191 Z M7.06503,6.04443 L1.06503,10.827 L2.93497,13.173 L8.93497,8.39035 L7.06503,6.04443 Z" />
|
||||
</vector>
|
11
android/apps/interface/src/main/res/drawable/ic_steam.xml
Normal file
11
android/apps/interface/src/main/res/drawable/ic_steam.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="21dp"
|
||||
android:height="21dp"
|
||||
android:viewportWidth="21"
|
||||
android:viewportHeight="21">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10.4866,0 C4.92045,0,0.367395,4.32104,0,9.78734 L5.45262,11.9841 C5.93184,11.651,6.51333,11.4545,7.14145,11.4545 C7.27641,11.4545,7.4083,11.4666,7.53829,11.4841 L10.1047,7.72495 C10.1083,5.56672,11.861,3.81818,14.0229,3.81818 C16.1872,3.81818,17.9416,5.57035,17.9416,7.73182 C17.9416,9.89329,16.1872,11.6455,14.0229,11.6455 C14.021,11.6455,14.0189,11.6453,14.017,11.6453 L10.0936,14.2008 C10.0986,14.2712,10.1043,14.3419,10.1043,14.4136 C10.1043,16.048,8.77791,17.3727,7.14145,17.3727 C5.69539,17.3727,4.49304,16.3376,4.2325,14.969 L0.378099,13.3841 C1.6334,17.7801,5.6822,21,10.4866,21 C16.2931,21,21,16.2991,21,10.5 C21,4.70114,16.2931,0,10.4866,0 Z M7.14145,16.0364 C6.96655,16.0364,6.79833,16.0081,6.64044,15.9569 L6.63968,15.9589 L6.59151,15.939 C6.54506,15.9224,6.49975,15.9037,6.45541,15.8831 L5.15462,15.3483 C5.50614,16.0927,6.26253,16.6091,7.14145,16.6091 C8.35546,16.6091,9.33971,15.6263,9.33971,14.4136 C9.33971,13.201,8.35546,12.2182,7.14145,12.2182 C6.87269,12.2182,6.61636,12.2688,6.37818,12.357 L7.75448,12.9114 C7.76404,12.9154,7.77359,12.9188,7.78296,12.923 L7.89001,12.9662 L7.88714,12.9732 C8.40898,13.243,8.76625,13.7861,8.76625,14.4136 C8.76625,15.3098,8.03872,16.0364,7.14145,16.0364 Z M16.7946,7.73182 C16.7946,6.20302,15.5537,4.96364,14.0229,4.96364 C12.4922,4.96364,11.2512,6.20302,11.2512,7.73182 C11.2512,9.26062,12.4922,10.5,14.0229,10.5 C15.5537,10.5,16.7946,9.26062,16.7946,7.73182 Z M12.0158,7.73182 C12.0158,6.62474,12.9144,5.72727,14.0229,5.72727 C15.1314,5.72727,16.03,6.62474,16.03,7.73182 C16.03,8.8389,15.1314,9.73636,14.0229,9.73636 C12.9144,9.73636,12.0158,8.8389,12.0158,7.73182 Z" />
|
||||
</vector>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:state_pressed="true" >
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton4" />
|
||||
<solid android:color="@color/colorButton4"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton4" />
|
||||
<solid android:color="@color/colorButton4"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton4" />
|
||||
<solid android:color="@color/colorButton4"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:state_pressed="true" >
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton2" />
|
||||
<solid android:color="@color/colorButton2"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton2" />
|
||||
<solid android:color="@color/colorButton2"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle" >
|
||||
<corners android:radius="4dip" />
|
||||
<stroke android:width="1dip" android:color="@color/colorButton2" />
|
||||
<solid android:color="@color/colorButton2"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue