From cb2070f75c65446430a688d81ca42624ce15de33 Mon Sep 17 00:00:00 2001 From: Michael Baetgen Date: Wed, 24 Dec 2025 02:12:04 -0600 Subject: [PATCH 1/2] slopdated to android 35 / gradle 8.14.2 / projectM 4 api --- app/build.gradle | 59 ++++-- app/src/main/AndroidManifest.xml | 7 +- app/src/main/cpp/CMakeLists.txt | 115 ++++++++--- app/src/main/cpp/jni.cpp | 190 +++++++++++------- .../github/projectm_android/MainActivity.java | 105 +++++++--- app/src/main/res/layout/activity_main.xml | 4 +- build.gradle | 20 +- gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 345 insertions(+), 161 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6c98870..91de39a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,44 +1,67 @@ -apply plugin: 'com.android.application' +plugins { + id 'com.android.application' +} android { - compileSdkVersion 22 + namespace 'com.github.projectm_android' + compileSdk 35 + defaultConfig { applicationId "com.github.projectm_android" - minSdkVersion 22 - targetSdkVersion 22 + minSdk 21 + targetSdk 35 + versionCode 1 versionName "1.0" + + ndk { + abiFilters 'x86_64', 'arm64-v8a', 'armeabi-v7a' + } + externalNativeBuild { cmake { - cppFlags "-std=c++11" - arguments "-DANDROID_STL=c++_shared" + cppFlags "-std=c++17 -fexceptions -frtti" + // optional: use local projectM sources + // arguments "-DPROJECTM_SOURCE_DIR=/my/local/projectm" } } - ndk { - abiFilters "armeabi-v7a" - } } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + debug { + debuggable true + } } + externalNativeBuild { cmake { - path "src/main/cpp/CMakeLists.txt" + path file('src/main/cpp/CMakeLists.txt') } } - sourceSets { - main { - jniLibs.srcDirs 'jniLibs', 'jniLibs/armeabi-v7a' - } + + buildFeatures { + viewBinding true } - productFlavors { + + packaging { + jniLibs { + useLegacyPackaging = true + } + resources { + excludes += ['/META-INF/{AL2.0,LGPL2.1}'] + } } } + dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:22.2.1' - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + // Ensure consistent Kotlin stdlib versions across all transitive deps. + implementation platform('org.jetbrains.kotlin:kotlin-bom:1.9.24') + + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 244feba..ed19c3f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,9 +1,6 @@ - + + android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen" android:exported="true"> diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 7b25830..5867d6d 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -2,22 +2,89 @@ # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. +cmake_minimum_required(VERSION 3.22.1) +project(projectm_android LANGUAGES C CXX) -cmake_minimum_required(VERSION 3.4.1) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Install/staging prefix inside this ABI build directory +set(PROJECTM_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/projectm-install" CACHE PATH "") + +# Force install prefix so projectM installs headers into: +# ${PROJECTM_INSTALL_PREFIX}/include/projectM-4/... +set(CMAKE_INSTALL_PREFIX "${PROJECTM_INSTALL_PREFIX}" CACHE PATH "" FORCE) + + +# ---- Fetch OR use local libprojectM (projectM 4.x) ---- +include(FetchContent) + +# If set, use local source instead of fetching. +# Example: -DPROJECTM_SOURCE_DIR=/home/mba/dev/widerup/projectm +set(PROJECTM_SOURCE_DIR "" CACHE PATH "Path to local projectM source directory (with top-level CMakeLists.txt)") + +# Configure projectM build options (mirrors upstream Android workflow). +set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) +set(BUILD_TESTING NO CACHE BOOL "" FORCE) +set(ENABLE_SDL_UI OFF CACHE BOOL "" FORCE) + +if (PROJECTM_SOURCE_DIR AND EXISTS "${PROJECTM_SOURCE_DIR}/CMakeLists.txt") + message(STATUS "Using local projectM source: ${PROJECTM_SOURCE_DIR}") + + # FetchContent will treat SOURCE_DIR as the content and won't hit the network. + FetchContent_Declare( + projectm + SOURCE_DIR "${PROJECTM_SOURCE_DIR}" + ) +else() + message(STATUS "Fetching projectM via Git (PROJECTM_SOURCE_DIR not set or invalid)") + + FetchContent_Declare( + projectm + GIT_REPOSITORY https://github.com/projectM-visualizer/projectm.git + GIT_TAG master + ) +endif() + +FetchContent_MakeAvailable(projectm) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. -add_library( # Sets the name of the library. - jniwrapper +# ---- JNI wrapper ---- +add_library(jniwrapper SHARED + jni.cpp +) - # Sets the library as a shared library. - SHARED +# Always produce libjniwrapper.so (no debug postfix) +set_target_properties(jniwrapper PROPERTIES + OUTPUT_NAME "jniwrapper" + DEBUG_POSTFIX "" + RELEASE_POSTFIX "" + RELWITHDEBINFO_POSTFIX "" + MINSIZEREL_POSTFIX "" +) + +# Also guard against a global postfix set by a toolchain / parent project +set(CMAKE_DEBUG_POSTFIX "" CACHE STRING "" FORCE) + +# find projectM headers +if (DEFINED projectm_SOURCE_DIR) + target_include_directories(jniwrapper PRIVATE + "${projectm_SOURCE_DIR}/src/playlist/api" + ) +endif() - # Provides a relative path to your source file(s). - jni.cpp) +if (DEFINED projectm_BINARY_DIR) + target_include_directories(jniwrapper PRIVATE + "${projectm_BINARY_DIR}/src/api/include" + "${projectm_BINARY_DIR}/src/playlist/include" + ) +else() + message(FATAL_ERROR "projectm_BINARY_DIR not defined; cannot set build-tree include paths") +endif() # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by @@ -25,25 +92,23 @@ add_library( # Sets the name of the library. # you want to add. CMake verifies that the library exists before # completing its build. -find_library( - log-lib - log) +# Android + GLES +find_library(log-lib log) +find_library(android-lib android) +find_library(glesv2-lib GLESv2) +find_library(egl-lib EGL) - -add_library( - projectM - SHARED - IMPORTED +target_include_directories(jniwrapper + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} ) -set_target_properties( - projectM - PROPERTIES IMPORTED_LOCATION - ${CMAKE_CURRENT_SOURCE_DIR}/../../../jniLibs/armeabi-v7a/libprojectM.so) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../jniLibs/include/ ) - -target_link_libraries( - jniwrapper +target_link_libraries(jniwrapper + PRIVATE ${log-lib} - projectM) \ No newline at end of file + ${android-lib} + ${glesv2-lib} + ${egl-lib} + libprojectM::playlist + projectM +) diff --git a/app/src/main/cpp/jni.cpp b/app/src/main/cpp/jni.cpp index fcf78f2..7fd6a89 100644 --- a/app/src/main/cpp/jni.cpp +++ b/app/src/main/cpp/jni.cpp @@ -1,102 +1,136 @@ -#include #include -#include -#include +#include #include -#include -#include "libprojectM/projectM.hpp" -#include "libprojectM/PCM.hpp" +#include +#include + +#include +#include + +#define LOG_TAG "projectm-android" +#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +// Global state +static projectm_handle g_pm = nullptr; +static projectm_playlist_handle g_playlist = nullptr; +static std::mutex g_lock; -#define TAG "ProjectM" -#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) -#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) -#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) -#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) +// Helper: build a stable C-string array for projectM properties. +static std::vector g_path_storage; -projectM *instance = NULL; +static void set_preset_search_path(const std::string& assetRoot) { + // Assets are extracted by ProjectMApplication into /projectM/... + // That directory contains: config.inp, fonts/, presets/... + const std::string presetsDir = assetRoot + "/presets"; -void next_preset(bool hard_cut) { - if (!instance) { - ALOGE("libprojectM not initialized"); + g_path_storage.clear(); + g_path_storage.push_back(presetsDir); + + for (auto& s : g_path_storage) { + projectm_playlist_add_path(g_playlist, s.c_str(), true, false); + ALOGI("Preset search path: %s", presetsDir.c_str()); + } + +} + +static void ensure_initialized(int width, int height, const std::string& assetRoot) { + if (g_pm) return; + + g_pm = projectm_create(); + if (!g_pm) { + ALOGE("projectm_create() failed (GL context not ready?)"); return; } - srand((unsigned) time(NULL)); - int preset_list_size = instance->getPlaylistSize(); - if (preset_list_size <= 0) { - ALOGE("Could not load any presets"); + + projectm_set_window_size(g_pm, width, height); + + // Basic config. + projectm_set_aspect_correction(g_pm, true); + projectm_set_hard_cut_enabled(g_pm, true); + projectm_set_soft_cut_duration(g_pm, 10); + projectm_set_hard_cut_duration(g_pm, 10); + projectm_set_hard_cut_sensitivity(g_pm, 1.0); + projectm_set_beat_sensitivity(g_pm, 0.5); + projectm_set_preset_duration(g_pm, 15); + + // Playlist is the easiest way to do preset navigation with projectM 4. + g_playlist = projectm_playlist_create(g_pm); + + set_preset_search_path(assetRoot); + + if (g_playlist) { + projectm_playlist_set_shuffle(g_playlist, false); + projectm_playlist_set_position(g_playlist, 0, true); + } else { + ALOGE("projectm_playlist_create() failed"); } - int preset_number = (int)(rand() % (preset_list_size-1)); - ALOGD("Switching to preset %d", preset_number); - instance->selectPreset(preset_number, hard_cut); + + ALOGI("Initialized projectM (%dx%d)", width, height); } -extern "C" JNIEXPORT void JNICALL +extern "C" { + +// Java: libprojectMJNIWrapper.onSurfaceCreated(int w,int h,String assetPath) +JNIEXPORT void JNICALL Java_com_github_projectm_1android_libprojectMJNIWrapper_onSurfaceCreated( - JNIEnv *env, - jobject obj, - jint window_width, - jint window_height, - jstring jasset_path) { - if (instance) { - ALOGD("Destroy existing instance"); - delete instance; - } - const char* asset_path_chars = env->GetStringUTFChars(jasset_path, NULL); - std::string asset_path(asset_path_chars); - projectM::Settings settings; - settings.windowHeight = window_height; - settings.windowWidth = window_width; - settings.presetURL = asset_path + "/presets"; - settings.smoothPresetDuration = 7; - settings.presetDuration = 3; - settings.shuffleEnabled = true; - ALOGD("presetURL: %s", settings.presetURL.c_str()); - env->ReleaseStringUTFChars(jasset_path, asset_path_chars); - ALOGD("Creating new instance"); - instance = new projectM(settings); - srand(time(0)); - next_preset(true); + JNIEnv* env, jclass, jint window_width, jint window_height, jstring assetPath) { + const char* pathChars = env->GetStringUTFChars(assetPath, nullptr); + std::string assetRoot = pathChars ? pathChars : ""; + env->ReleaseStringUTFChars(assetPath, pathChars); + + std::lock_guard lk(g_lock); + ensure_initialized((int)window_width, (int)window_height, assetRoot); } -extern "C" JNIEXPORT void JNICALL +JNIEXPORT void JNICALL Java_com_github_projectm_1android_libprojectMJNIWrapper_onSurfaceChanged( - JNIEnv *env, - jobject obj, - jint window_width, - jint window_height) { - if (!instance) { - return; - } - instance->projectM_resetGL(window_width, window_height); + JNIEnv*, jclass, jint width, jint height) { + std::lock_guard lk(g_lock); + if (!g_pm) return; + projectm_set_window_size(g_pm, (int)width, (int)height); } -extern "C" JNIEXPORT void JNICALL +JNIEXPORT void JNICALL Java_com_github_projectm_1android_libprojectMJNIWrapper_onDrawFrame( - JNIEnv *env, - jobject obj) { - if (!instance) { - return; - } - instance->renderFrame(); + JNIEnv*, jclass) { + std::lock_guard lk(g_lock); + if (!g_pm) return; + + // Render one frame into the current GL framebuffer. + projectm_opengl_render_frame(g_pm); } -extern "C" JNIEXPORT void JNICALL +JNIEXPORT void JNICALL Java_com_github_projectm_1android_libprojectMJNIWrapper_addPCM( - JNIEnv *env, - jobject obj, - jshortArray pcm_data, - jshort nsamples) { - if (!instance) { - return; - } - jshort *data = env->GetShortArrayElements(pcm_data, NULL); - instance->pcm()->addPCM16Data(data, nsamples); - env->ReleaseShortArrayElements(pcm_data, data, 0); + JNIEnv* env, jclass, jshortArray pcm_data, jshort nsamples) { + std::lock_guard lk(g_lock); + if (!g_pm) return; + + jsize len = env->GetArrayLength(pcm_data); + if (len <= 0) return; + + // nsamples is passed in as "bufferSize" by AudioThread; treat it as frames. + // AudioThread records mono int16 PCM. + + jboolean isCopy = JNI_FALSE; + jshort* data = env->GetShortArrayElements(pcm_data, &isCopy); + if (!data) return; + + // Guard: never read beyond actual array length. + const int frames = (int)std::min(len, (jsize)nsamples); + + projectm_pcm_add_int16(g_pm, reinterpret_cast(data), frames, PROJECTM_MONO); + + env->ReleaseShortArrayElements(pcm_data, data, JNI_ABORT); } -extern "C" JNIEXPORT void JNICALL +JNIEXPORT void JNICALL Java_com_github_projectm_1android_libprojectMJNIWrapper_nextPreset( - JNIEnv *env, - jobject obj) { - next_preset(true); + JNIEnv*, jclass) { + std::lock_guard lk(g_lock); + if (!g_playlist) return; + projectm_playlist_play_next(g_playlist, true); } + +} // extern "C" diff --git a/app/src/main/java/com/github/projectm_android/MainActivity.java b/app/src/main/java/com/github/projectm_android/MainActivity.java index 6b7f389..5fe4876 100644 --- a/app/src/main/java/com/github/projectm_android/MainActivity.java +++ b/app/src/main/java/com/github/projectm_android/MainActivity.java @@ -3,23 +3,35 @@ import android.content.Context; import android.opengl.GLSurfaceView; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import android.content.pm.PackageManager; + +import android.util.Log; import android.view.MotionEvent; import android.view.WindowManager; import java.io.File; public class MainActivity extends AppCompatActivity { + private static final String TAG = "PM"; + private static final int REQ_AUDIO = 1001; + private AudioThread audioThread; + private projectMGLView glSurfaceView; public class projectMGLView extends GLSurfaceView { - private RendererWrapper mRenderer; + private final RendererWrapper mRenderer; public projectMGLView(Context context, String assetPath) { super(context); - this.setEGLContextClientVersion(2); + setEGLContextClientVersion(2); mRenderer = new RendererWrapper(assetPath); - this.setRenderer(mRenderer); + setRenderer(mRenderer); + + // Keep rendering even if audio isn't available (emulator often provides silence). + setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); } @Override @@ -27,47 +39,90 @@ public boolean onTouchEvent(MotionEvent e) { mRenderer.NextPreset(); return true; } - - @Override - public void onPause() { - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - } - } - private projectMGLView glSurfaceView; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + String assetPath = new File(getCacheDir(), "projectM").toString(); glSurfaceView = new projectMGLView(this, assetPath); setContentView(glSurfaceView); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } @Override protected void onPause() { super.onPause(); - glSurfaceView.onPause(); - audioThread.stop_recording(); - try { - audioThread.join(); - } catch (Exception e) { - e.printStackTrace(); - } + if (glSurfaceView != null) glSurfaceView.onPause(); + + stopAudioThreadIfRunning(); } @Override protected void onResume() { super.onResume(); - glSurfaceView.onResume(); + if (glSurfaceView != null) glSurfaceView.onResume(); + + ensureAudioPermissionAndStart(); + } + + private void ensureAudioPermissionAndStart() { + if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED) { + startAudioThreadIfNeeded(); + } else { + // Trigger runtime permission prompt + ActivityCompat.requestPermissions( + this, + new String[]{android.Manifest.permission.RECORD_AUDIO}, + REQ_AUDIO + ); + } + } + + private void startAudioThreadIfNeeded() { + if (audioThread != null) return; + + Log.i(TAG, "Starting AudioThread"); audioThread = new AudioThread(); audioThread.start(); } + + private void stopAudioThreadIfRunning() { + if (audioThread == null) return; + + Log.i(TAG, "Stopping AudioThread"); + try { + audioThread.stop_recording(); + } catch (Throwable t) { + Log.w(TAG, "stop_recording() threw", t); + } + + try { + audioThread.join(1500); + } catch (InterruptedException ignored) { + } catch (Throwable t) { + Log.w(TAG, "join() threw", t); + } + + audioThread = null; + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if (requestCode == REQ_AUDIO) { + boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; + if (granted) { + Log.i(TAG, "RECORD_AUDIO granted"); + startAudioThreadIfNeeded(); + } else { + Log.w(TAG, "RECORD_AUDIO denied; running without audio input"); + // Visuals still render due to RENDERMODE_CONTINUOUSLY. + } + } + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 7d8d24e..a4e8d40 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6942f5e..581c2f9 100644 --- a/build.gradle +++ b/build.gradle @@ -3,11 +3,10 @@ buildscript { repositories { google() - jcenter() - - } + mavenCentral() + } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:8.13.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -17,8 +16,19 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() + } + // Align all Kotlin stdlib artifacts to a single version. + // AGP (data binding, etc.) can pull an older kotlin-stdlib-jdk7/jdk8 which + // then conflicts with newer androidx core-ktx transitive kotlin-stdlib. + configurations.configureEach { + resolutionStrategy.eachDependency { details -> + if (details.requested.group == 'org.jetbrains.kotlin') { + details.useVersion '1.9.24' + details.because 'Avoid duplicate Kotlin stdlib classes (mixed versions)' + } + } } } diff --git a/gradle.properties b/gradle.properties index 82618ce..9592636 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true - - +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5974ea3..8c4b512 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip From fbee46bc9441ccd95e7204934831d7abad576f0c Mon Sep 17 00:00:00 2001 From: Michael Baetgen Date: Wed, 24 Dec 2025 03:20:32 -0600 Subject: [PATCH 2/2] project files, too --- .idea/AndroidProjectSystem.xml | 6 + .idea/caches/deviceStreaming.xml | 1186 ++++++++++++++++++++++++++++ .idea/compiler.xml | 6 + .idea/deploymentTargetSelector.xml | 10 + .idea/deviceManager.xml | 13 + .idea/editor.xml | 249 ++++++ .idea/gradle.xml | 14 +- .idea/migrations.xml | 10 + .idea/misc.xml | 7 +- .idea/runConfigurations.xml | 8 + 10 files changed, 1502 insertions(+), 7 deletions(-) create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/caches/deviceStreaming.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetSelector.xml create mode 100644 .idea/deviceManager.xml create mode 100644 .idea/editor.xml create mode 100644 .idea/migrations.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..7d60e7b --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,1186 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..fef3201 --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,249 @@ + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2996d53..639c779 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,14 +1,18 @@ + diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 37a7509..2731899 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ - - + + + + + diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml index 7f68460..72f00ed 100644 --- a/.idea/runConfigurations.xml +++ b/.idea/runConfigurations.xml @@ -3,6 +3,14 @@