YACWC
This commit is contained in:
21
.idea/deploymentTargetSelector.xml
generated
21
.idea/deploymentTargetSelector.xml
generated
@@ -4,18 +4,29 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="mobile">
|
<SelectionState runConfigName="mobile">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
</SelectionState>
|
<DropdownSelection timestamp="2025-03-09T15:35:16.375287800Z">
|
||||||
<SelectionState runConfigName="wear">
|
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
|
||||||
<DropdownSelection timestamp="2025-03-08T17:32:57.542834800Z">
|
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="Default" identifier="serial=192.168.1.195:35507;connection=7a90992e" />
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=RFAX803LMFR" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
<DialogSelection />
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="wear">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-03-29T14:49:45.062493500Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\thebears\.android\avd\Wear_OS_Large_Round_API_34.avd" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="MainActivity">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.0.20" />
|
<option name="version" value="2.1.10" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
254
.idea/workspace (SFConflict ispatel 2025-03-10-10-03-26).xml
generated
Normal file
254
.idea/workspace (SFConflict ispatel 2025-03-10-10-03-26).xml
generated
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidLayouts">
|
||||||
|
<shared>
|
||||||
|
<config />
|
||||||
|
</shared>
|
||||||
|
<layouts>
|
||||||
|
<layout url="file://$PROJECT_DIR$/wear/src/main/res/layout/layout.xml">
|
||||||
|
<config>
|
||||||
|
<theme>@android:style/Theme.DeviceDefault</theme>
|
||||||
|
</config>
|
||||||
|
</layout>
|
||||||
|
</layouts>
|
||||||
|
</component>
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="NONE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ClangdSettings">
|
||||||
|
<option name="formatViaClangd" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[]" />
|
||||||
|
<component name="ExternalProjectsData">
|
||||||
|
<projectState path="$PROJECT_DIR$">
|
||||||
|
<ProjectState />
|
||||||
|
</projectState>
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Class" />
|
||||||
|
<option value="Kotlin Class" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 0
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"Android App.mobile.executor": "Run",
|
||||||
|
"Android App.wear.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true",
|
||||||
|
"com.google.services.firebase.aqiPopupShown": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos",
|
||||||
|
"project.structure.last.edited": "Modules",
|
||||||
|
"project.structure.proportion": "0.17",
|
||||||
|
"project.structure.side.proportion": "0.2"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\isp\Seafile\Designs\Android\bird_sound_identify_wearos\mobile\src\main\assets\2024_08_16" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
|
||||||
|
<recent name="com.birdsounds.identify.presentation" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Android App.mobile">
|
||||||
|
<configuration name="mobile" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.mobile" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration name="wear" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.wear" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="" />
|
||||||
|
<created>1741017174616</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1741017174616</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="play_dynamic_filters_status">
|
||||||
|
<option name="appIdToCheckInfo">
|
||||||
|
<map>
|
||||||
|
<entry key="com.birdsounds.identify">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="com.birdsounds.identify.test">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
254
.idea/workspace (SFConflict ispatel@live.com 2025-03-08-01-11-41).xml
generated
Normal file
254
.idea/workspace (SFConflict ispatel@live.com 2025-03-08-01-11-41).xml
generated
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidLayouts">
|
||||||
|
<shared>
|
||||||
|
<config />
|
||||||
|
</shared>
|
||||||
|
<layouts>
|
||||||
|
<layout url="file://$PROJECT_DIR$/wear/src/main/res/layout/layout.xml">
|
||||||
|
<config>
|
||||||
|
<theme>@android:style/Theme.DeviceDefault</theme>
|
||||||
|
</config>
|
||||||
|
</layout>
|
||||||
|
</layouts>
|
||||||
|
</component>
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="NONE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/Downloader.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" beforeDir="false" afterPath="$PROJECT_DIR$/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ClangdSettings">
|
||||||
|
<option name="formatViaClangd" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[]" />
|
||||||
|
<component name="ExternalProjectsData">
|
||||||
|
<projectState path="$PROJECT_DIR$">
|
||||||
|
<ProjectState />
|
||||||
|
</projectState>
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Class" />
|
||||||
|
<option value="Kotlin Class" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 0
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"Android App.mobile.executor": "Run",
|
||||||
|
"Android App.wear.executor": "Run",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
|
"cf.first.check.clang-format": "false",
|
||||||
|
"cidr.known.project.marker": "true",
|
||||||
|
"com.google.services.firebase.aqiPopupShown": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos",
|
||||||
|
"project.structure.last.edited": "Modules",
|
||||||
|
"project.structure.proportion": "0.17",
|
||||||
|
"project.structure.side.proportion": "0.2"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\isp\Seafile\Designs\Android\bird_sound_identify_wearos\mobile\src\main\assets\2024_08_16" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
|
||||||
|
<recent name="com.birdsounds.identify.presentation" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Android App.mobile">
|
||||||
|
<configuration name="mobile" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.mobile" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration name="wear" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||||
|
<module name="identify.wear" />
|
||||||
|
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||||
|
<option name="DEPLOY" value="true" />
|
||||||
|
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||||
|
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||||
|
<option name="ARTIFACT_NAME" value="" />
|
||||||
|
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||||
|
<option name="ALL_USERS" value="false" />
|
||||||
|
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||||
|
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
||||||
|
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||||
|
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||||
|
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||||
|
<option name="MODE" value="default_activity" />
|
||||||
|
<option name="RESTORE_ENABLED" value="false" />
|
||||||
|
<option name="RESTORE_FILE" value="" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||||
|
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java>
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Java>
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||||
|
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<option name="DEEP_LINK" value="" />
|
||||||
|
<option name="ACTIVITY" value="" />
|
||||||
|
<option name="ACTIVITY_CLASS" value="" />
|
||||||
|
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||||
|
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="e1c82fb5-3f9e-43af-a0be-9f1954a6c1b9" name="Changes" comment="" />
|
||||||
|
<created>1741017174616</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1741017174616</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="play_dynamic_filters_status">
|
||||||
|
<option name="appIdToCheckInfo">
|
||||||
|
<map>
|
||||||
|
<entry key="com.birdsounds.identify">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="com.birdsounds.identify.test">
|
||||||
|
<value>
|
||||||
|
<CheckInfo lastCheckTimestamp="1741381679027" />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,11 +1,23 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
accompanistFlowlayout = "0.28.0"
|
||||||
agp = "8.8.1"
|
agp = "8.8.1"
|
||||||
|
composeNavigationVersion = "1.3.1"
|
||||||
|
horologistComposeTools = "0.6.18"
|
||||||
|
horologistAudio = "0.6.18"
|
||||||
|
horologistMediaData = "0.6.8"
|
||||||
|
horologistMediaUi = "0.6.8"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.13.1"
|
coreKtx = "1.13.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.1.5"
|
junitVersion = "1.1.5"
|
||||||
espressoCore = "3.5.1"
|
espressoCore = "3.5.1"
|
||||||
appcompat = "1.6.1"
|
appcompat = "1.6.1"
|
||||||
|
kotlinxCoroutinesPlayServices = "1.8.1"
|
||||||
|
lifecycleViewmodelCompose = "2.8.4"
|
||||||
|
materialIconsCore = "1.6.8"
|
||||||
|
materialIconsExtended = "1.7.8"
|
||||||
|
media3Exoplayer = "1.4.0"
|
||||||
|
navigationCompose = "2.8.0-rc01"
|
||||||
playServicesWearable = "18.2.0"
|
playServicesWearable = "18.2.0"
|
||||||
material = "1.10.0"
|
material = "1.10.0"
|
||||||
activity = "1.9.1"
|
activity = "1.9.1"
|
||||||
@@ -13,6 +25,9 @@ constraintlayout = "2.1.4"
|
|||||||
composeBom = "2024.04.01"
|
composeBom = "2024.04.01"
|
||||||
composeMaterial = "1.2.1"
|
composeMaterial = "1.2.1"
|
||||||
composeFoundation = "1.2.1"
|
composeFoundation = "1.2.1"
|
||||||
|
statelyConcurrentCollections = "2.0.0"
|
||||||
|
uiTooling = "1.3.1"
|
||||||
|
wearOngoing = "1.0.0"
|
||||||
wearToolingPreview = "1.0.0"
|
wearToolingPreview = "1.0.0"
|
||||||
activityCompose = "1.9.1"
|
activityCompose = "1.9.1"
|
||||||
coreSplashscreen = "1.0.1"
|
coreSplashscreen = "1.0.1"
|
||||||
@@ -22,14 +37,32 @@ composeMaterial3 = "1.0.0-alpha23"
|
|||||||
workRuntimeKtx = "2.9.1"
|
workRuntimeKtx = "2.9.1"
|
||||||
lifecycleRuntimeKtx = "2.6.1"
|
lifecycleRuntimeKtx = "2.6.1"
|
||||||
runtimeAndroid = "1.6.6"
|
runtimeAndroid = "1.6.6"
|
||||||
|
datastoreCoreAndroid = "1.1.3"
|
||||||
|
coreKtxVersion = "1.13.0"
|
||||||
|
animationCoreAndroid = "1.6.6"
|
||||||
#litert = "1.0.1"
|
#litert = "1.0.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanistFlowlayout" }
|
||||||
|
androidx-compose-navigation-v131 = { module = "androidx.wear.compose:compose-navigation", version.ref = "composeNavigationVersion" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
||||||
|
androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCore" }
|
||||||
|
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
|
||||||
|
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" }
|
||||||
|
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||||
|
androidx-wear-ongoing = { module = "androidx.wear:wear-ongoing", version.ref = "wearOngoing" }
|
||||||
|
horologist-audio-ui = { module = "com.google.android.horologist:horologist-audio-ui", version.ref = "horologistComposeTools" }
|
||||||
|
horologist-audio = { module = "com.google.android.horologist:horologist-audio", version.ref = "horologistAudio" }
|
||||||
|
horologist-compose-material = { module = "com.google.android.horologist:horologist-compose-material", version.ref = "horologistMediaData" }
|
||||||
|
horologist-compose-tools = { module = "com.google.android.horologist:horologist-compose-tools", version.ref = "horologistComposeTools" }
|
||||||
|
horologist-media-ui = { module = "com.google.android.horologist:horologist-media-ui", version.ref = "horologistMediaUi" }
|
||||||
|
horologist-media-data = { module = "com.google.android.horologist:horologist-media-data", version.ref = "horologistMediaData" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" }
|
||||||
play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" }
|
play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||||
@@ -53,10 +86,15 @@ androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx"
|
|||||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }
|
androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }
|
||||||
|
androidx-datastore-core-android = { group = "androidx.datastore", name = "datastore-core-android", version.ref = "datastoreCoreAndroid" }
|
||||||
|
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtxVersion" }
|
||||||
|
stately-concurrent-collections = { module = "co.touchlab:stately-concurrent-collections", version.ref = "statelyConcurrentCollections" }
|
||||||
|
ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" }
|
||||||
|
androidx-animation-core-android = { group = "androidx.compose.animation", name = "animation-core-android", version.ref = "animationCoreAndroid" }
|
||||||
#litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
|
#litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "litert" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.0.20" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.1.10" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
||||||
|
|||||||
@@ -55,12 +55,16 @@ dependencies {
|
|||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
|
|
||||||
|
implementation("androidx.datastore:datastore-preferences:1.1.3")
|
||||||
implementation("org.tensorflow:tensorflow-lite:2.6.0")
|
implementation("org.tensorflow:tensorflow-lite:2.6.0")
|
||||||
// implementation("com.google.android.gms:play-services-tflite:20.0.0")
|
// implementation("com.google.android.gms:play-services-tflite:20.0.0")
|
||||||
implementation("uk.me.berndporr:iirj:1.7")
|
implementation("uk.me.berndporr:iirj:1.7")
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
|
implementation(libs.androidx.datastore.core.android)
|
||||||
|
implementation(libs.core.ktx)
|
||||||
// implementation(libs.litert)
|
// implementation(libs.litert)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import java.io.IOException
|
|||||||
|
|
||||||
class Downloader(mainActivity: MainActivity) {
|
class Downloader(mainActivity: MainActivity) {
|
||||||
|
|
||||||
private val settings = Settings();
|
private val settings = Settings;
|
||||||
private var activity: MainActivity = mainActivity;
|
private var activity: MainActivity = mainActivity;
|
||||||
private var context: Context = activity.applicationContext;
|
private var context: Context = activity.applicationContext;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
package com.birdsounds.identify
|
package com.birdsounds.identify
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.location.Address
|
||||||
|
import android.location.Geocoder
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationListener
|
import android.location.LocationListener
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
object Location {
|
object Location {
|
||||||
private var locationListenerGPS: LocationListener? = null
|
private var locationListenerGPS: LocationListener? = null
|
||||||
@@ -30,7 +39,10 @@ object Location {
|
|||||||
) {
|
) {
|
||||||
val locationManager =
|
val locationManager =
|
||||||
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
|
||||||
|
|
||||||
if (locationListenerGPS == null) locationListenerGPS = object : LocationListener {
|
if (locationListenerGPS == null) locationListenerGPS = object : LocationListener {
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onLocationChanged(location: Location) {
|
override fun onLocationChanged(location: Location) {
|
||||||
Log.w(TAG, "Got location changed");
|
Log.w(TAG, "Got location changed");
|
||||||
while (!soundClassifier.is_model_ready()) {
|
while (!soundClassifier.is_model_ready()) {
|
||||||
@@ -39,7 +51,35 @@ object Location {
|
|||||||
Log.w(TAG, "Sound classifier is ready");
|
Log.w(TAG, "Sound classifier is ready");
|
||||||
soundClassifier.runMetaInterpreter(location)
|
soundClassifier.runMetaInterpreter(location)
|
||||||
|
|
||||||
soundClassifier.runMetaInterpreter(location)
|
val activity = context as? Activity;
|
||||||
|
activity?.let {
|
||||||
|
|
||||||
|
val text_species: TextView = it.findViewById(R.id.local_species)
|
||||||
|
text_species.text = local_species;
|
||||||
|
|
||||||
|
val loc_lon: TextView = it.findViewById(R.id.location_long)
|
||||||
|
loc_lon.text =
|
||||||
|
"Longitude: ${location.longitude}"
|
||||||
|
|
||||||
|
val loc_lat: TextView = it.findViewById(R.id.location_lat)
|
||||||
|
loc_lat.text =
|
||||||
|
"Latitude: ${location.latitude}"
|
||||||
|
|
||||||
|
val loc_string: TextView = it.findViewById(R.id.location_string);
|
||||||
|
val geocoder = Geocoder(context, Locale.getDefault())
|
||||||
|
val addresses: MutableList<Address>? =
|
||||||
|
geocoder.getFromLocation(location.latitude, location.longitude, 1)
|
||||||
|
if (addresses?.isNotEmpty() == true) {
|
||||||
|
val address: Address = addresses[0]
|
||||||
|
|
||||||
|
// loc_string.text = address.locality.toString() + ", " + address.adminArea.toString() + " " + address.countryName.toString()
|
||||||
|
loc_string.text = address.getAddressLine(0).toString()
|
||||||
|
// Log.w(TAG, address.toString())
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("")
|
@Deprecated("")
|
||||||
@@ -53,7 +93,7 @@ object Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
locationManager.requestLocationUpdates(
|
locationManager.requestLocationUpdates(
|
||||||
LocationManager.GPS_PROVIDER, 60000, 0f,
|
LocationManager.PASSIVE_PROVIDER, 60000, 0f,
|
||||||
locationListenerGPS!!
|
locationListenerGPS!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,42 @@
|
|||||||
package com.birdsounds.identify
|
package com.birdsounds.identify
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.Manifest
|
import android.preference.PreferenceManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.SeekBar
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.gms.wearable.ChannelClient
|
import com.google.android.gms.wearable.ChannelClient
|
||||||
import com.google.android.gms.wearable.Wearable
|
import com.google.android.gms.wearable.Wearable
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
private var updateJob: Job? = null
|
||||||
|
private var updateCounter = 0
|
||||||
|
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val Any.TAG: String
|
val Any.TAG: String
|
||||||
get() {
|
get() {
|
||||||
@@ -19,11 +45,39 @@ val Any.TAG: String
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SynchronousDataStore(private val context: Context) {
|
||||||
|
|
||||||
|
// Define keys
|
||||||
|
companion object {
|
||||||
|
val THRESHOLD_KEY = intPreferencesKey("user_age")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous write operations
|
||||||
|
fun saveThreshold(value: Int) {
|
||||||
|
runBlocking {
|
||||||
|
context.dataStore.edit { preferences ->
|
||||||
|
preferences[THRESHOLD_KEY] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getThreshold(): Int {
|
||||||
|
|
||||||
|
return runBlocking {
|
||||||
|
context.dataStore.data.first()[THRESHOLD_KEY] ?: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
// private lateinit var soundClassifier: SoundClassifier
|
// private lateinit var soundClassifier: SoundClassifier
|
||||||
val REQUEST_PERMISSIONS = 1337
|
val REQUEST_PERMISSIONS = 1337
|
||||||
|
|
||||||
|
private lateinit var dataStore: SynchronousDataStore
|
||||||
|
|
||||||
|
private lateinit var last_message_delay: TextView
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
@@ -49,6 +103,34 @@ class MainActivity : AppCompatActivity() {
|
|||||||
insets
|
insets
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val thresholdText = findViewById<TextView>(R.id.threshold_value_text)
|
||||||
|
val seekBar = findViewById<SeekBar>(R.id.threshold_set_scale_bar)
|
||||||
|
last_message_delay = findViewById<TextView>(R.id.last_message_delay)
|
||||||
|
dataStore = SynchronousDataStore(applicationContext)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Settings.threshold = dataStore.getThreshold();
|
||||||
|
seekBar.progress = Settings.threshold
|
||||||
|
thresholdText.text = String.format("%.2f", Settings.threshold/100f)
|
||||||
|
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
|
||||||
|
var actualValue = progress
|
||||||
|
dataStore.saveThreshold(actualValue);
|
||||||
|
Settings.threshold = actualValue;
|
||||||
|
thresholdText.text = String.format("%.2f", actualValue/100f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
var soundClassifier: SoundClassifier? = null
|
var soundClassifier: SoundClassifier? = null
|
||||||
@@ -57,6 +139,34 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
// Start periodic updates using coroutines
|
||||||
|
updateJob = lifecycleScope.launch {
|
||||||
|
while (isActive) { // isActive is a property of the coroutine scope
|
||||||
|
updateCounter++
|
||||||
|
if (last_message_time == 0.toLong())
|
||||||
|
{
|
||||||
|
last_message_delay.text = "No messages received"
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
last_message_delay.text = "Last message: ${(Instant.now().toEpochMilli() - last_message_time)/1000F.toInt()} seconds ago"
|
||||||
|
}
|
||||||
|
|
||||||
|
// last_message_delay.text = "Update count: $updateCounter"
|
||||||
|
delay(500) // Update every 1 second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
// Cancel the coroutine when activity is not visible
|
||||||
|
updateJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
private fun requestPermissions() {
|
private fun requestPermissions() {
|
||||||
val perms = mutableListOf<String>()
|
val perms = mutableListOf<String>()
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,19 @@ import decodeAACToPCM
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
||||||
|
var last_message_time = 0L;
|
||||||
|
fun ByteArray.toLong(): Long {
|
||||||
|
require(size <= 8) { "ByteArray too large to fit in Long" }
|
||||||
|
|
||||||
|
var result = 0L
|
||||||
|
for (byte in this) {
|
||||||
|
result = (result shl 8) or (byte.toLong() and 0xFF)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
class MessageListenerService : WearableListenerService() {
|
class MessageListenerService : WearableListenerService() {
|
||||||
|
|
||||||
@@ -30,6 +43,10 @@ class MessageListenerService : WearableListenerService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES)
|
var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES)
|
||||||
|
|
||||||
|
last_message_time = tstamp_bytes.toLong()
|
||||||
|
|
||||||
|
|
||||||
var audio_bytes_og = p0.data.copyOfRange(Long.SIZE_BYTES, p0.data.size)
|
var audio_bytes_og = p0.data.copyOfRange(Long.SIZE_BYTES, p0.data.size)
|
||||||
|
|
||||||
|
|
||||||
@@ -63,8 +80,12 @@ class MessageListenerService : WearableListenerService() {
|
|||||||
var sorted_list = soundclassifier.executeScoring(short_array)
|
var sorted_list = soundclassifier.executeScoring(short_array)
|
||||||
Log.w(TAG, "FINISHED SCORING");
|
Log.w(TAG, "FINISHED SCORING");
|
||||||
Log.w(TAG, "")
|
Log.w(TAG, "")
|
||||||
for (i in 0 until 5) {
|
val threshold = Settings.threshold/100f
|
||||||
|
for (i in 0 until 10) {
|
||||||
val score = sorted_list[i].value
|
val score = sorted_list[i].value
|
||||||
|
if (score < threshold) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
val index = sorted_list[i].index
|
val index = sorted_list[i].index
|
||||||
val species_name = soundclassifier.labelList[index]
|
val species_name = soundclassifier.labelList[index]
|
||||||
Log.w(TAG, species_name + ", " + score.toString())
|
Log.w(TAG, species_name + ", " + score.toString())
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.birdsounds.identify
|
package com.birdsounds.identify
|
||||||
|
|
||||||
class Settings {
|
object Settings {
|
||||||
var local_model_file: String = "2024_08_16_audio_model.tflite"
|
var local_model_file: String = "2024_08_16_audio_model.tflite"
|
||||||
var pkg_model_file: String = "2024_08_16/audio-model.tflite"
|
var pkg_model_file: String = "2024_08_16/audio-model.tflite"
|
||||||
|
|
||||||
var local_meta_model_file: String = "2024_08_16_meta_model.tflite"
|
var local_meta_model_file: String = "2024_08_16_meta_model.tflite"
|
||||||
var pkg_meta_model_file: String = "2024_08_16/meta-model.tflite"
|
var pkg_meta_model_file: String = "2024_08_16/meta-model.tflite"
|
||||||
|
|
||||||
|
var threshold: Int = 50;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import uk.me.berndporr.iirj.Butterworth
|
|||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var local_species = ""
|
||||||
class SoundClassifier(
|
class SoundClassifier(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val options: Options = Options()
|
private val options: Options = Options()
|
||||||
@@ -81,7 +83,7 @@ class SoundClassifier(
|
|||||||
/** Number of output classes of the TFLite model. */
|
/** Number of output classes of the TFLite model. */
|
||||||
private var modelNumClasses = 0
|
private var modelNumClasses = 0
|
||||||
private var metaModelNumClasses = 0
|
private var metaModelNumClasses = 0
|
||||||
private var settings: Settings = Settings();
|
private var settings = Settings;
|
||||||
|
|
||||||
/** Used to hold the real-time probabilities predicted by the model for the output classes. */
|
/** Used to hold the real-time probabilities predicted by the model for the output classes. */
|
||||||
private lateinit var predictionProbs: FloatArray
|
private lateinit var predictionProbs: FloatArray
|
||||||
@@ -274,6 +276,17 @@ class SoundClassifier(
|
|||||||
metaOutputBuffer.rewind()
|
metaOutputBuffer.rewind()
|
||||||
metaOutputBuffer.get(metaPredictionProbs) // Copy data to metaPredictionProbs.
|
metaOutputBuffer.get(metaPredictionProbs) // Copy data to metaPredictionProbs.
|
||||||
|
|
||||||
|
var cloned = metaPredictionProbs.clone()
|
||||||
|
|
||||||
|
val sorted_indices = cloned.withIndex().sortedByDescending { it.value }.map{it.index}
|
||||||
|
|
||||||
|
local_species = "Most likely:\n"
|
||||||
|
for (i in 0..5) {
|
||||||
|
local_species += " " +labelList[sorted_indices[i]].split("_")[1]
|
||||||
|
local_species += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (i in metaPredictionProbs.indices) {
|
for (i in metaPredictionProbs.indices) {
|
||||||
metaPredictionProbs[i] =
|
metaPredictionProbs[i] =
|
||||||
|
|||||||
@@ -7,13 +7,89 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Hello World!"
|
android:paddingLeft="5dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:paddingTop="15dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:text="Threshold:"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:textSize="34sp" />
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="55dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/threshold_value_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Threshold:"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="28sp" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/threshold_set_scale_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center|fill_horizontal|fill_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:max="100"
|
||||||
|
android:min="0" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_string"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="Location:"
|
||||||
|
android:textSize="34sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_lat"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="Latitude:"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_long"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="Longitude:"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/local_species"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:text="Local Species:"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/last_message_delay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Last Message" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -44,21 +44,20 @@ dependencies {
|
|||||||
include("*.jar")
|
include("*.jar")
|
||||||
})
|
})
|
||||||
api(files("libs/opus.aar"))
|
api(files("libs/opus.aar"))
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4")
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation("androidx.compose.ui:ui-tooling:1.3.1")
|
implementation(libs.ui.tooling)
|
||||||
implementation("androidx.navigation:navigation-compose:2.8.0-rc01")
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation("androidx.wear.compose:compose-navigation:1.3.1")
|
implementation(libs.androidx.compose.navigation.v131)
|
||||||
implementation("com.google.android.horologist:horologist-audio-ui:0.6.18")
|
implementation(libs.horologist.audio.ui)
|
||||||
implementation("com.google.android.horologist:horologist-audio:0.6.18")
|
implementation(libs.horologist.audio)
|
||||||
implementation("com.google.android.horologist:horologist-compose-tools:0.6.18")
|
implementation(libs.horologist.compose.tools)
|
||||||
implementation("com.google.android.horologist:horologist-compose-tools:0.6.18")
|
implementation(libs.androidx.material.icons.core)
|
||||||
implementation("androidx.compose.material:material-icons-core:1.6.8")
|
implementation(libs.androidx.material.icons.extended)
|
||||||
implementation("androidx.compose.material:material-icons-extended:1.6.8")
|
implementation(libs.horologist.compose.material)
|
||||||
implementation("com.google.android.horologist:horologist-compose-material:0.6.8")
|
implementation(libs.horologist.media.ui)
|
||||||
implementation("com.google.android.horologist:horologist-media-ui:0.6.8")
|
implementation(libs.horologist.media.data)
|
||||||
implementation("com.google.android.horologist:horologist-media-data:0.6.8")
|
implementation(libs.kotlinx.coroutines.play.services)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.1")
|
implementation(libs.androidx.media3.exoplayer)
|
||||||
implementation("androidx.media3:media3-exoplayer:1.4.0")
|
|
||||||
implementation(libs.play.services.wearable)
|
implementation(libs.play.services.wearable)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
@@ -66,15 +65,20 @@ dependencies {
|
|||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.compose.material)
|
implementation(libs.androidx.compose.material)
|
||||||
implementation(libs.androidx.compose.foundation)
|
implementation(libs.androidx.compose.foundation)
|
||||||
|
// implementation("androidx.compose.foundation:foundation:1.8.0-rc01")
|
||||||
implementation(libs.androidx.wear.tooling.preview)
|
implementation(libs.androidx.wear.tooling.preview)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.core.splashscreen)
|
implementation(libs.androidx.core.splashscreen)
|
||||||
implementation(libs.androidx.compose.navigation)
|
implementation(libs.androidx.compose.navigation)
|
||||||
implementation(libs.androidx.media3.common)
|
implementation(libs.androidx.media3.common)
|
||||||
implementation("co.touchlab:stately-concurrent-collections:2.0.0")
|
implementation(libs.androidx.wear.ongoing)
|
||||||
|
implementation(libs.accompanist.flowlayout)
|
||||||
|
implementation(libs.stately.concurrent.collections)
|
||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
implementation(libs.androidx.work.runtime.ktx)
|
implementation(libs.androidx.work.runtime.ktx)
|
||||||
implementation(libs.androidx.runtime.android) // androidTestImplementation(platform(libs.androidx.compose.bom))
|
implementation(libs.androidx.runtime.android)
|
||||||
|
implementation(libs.androidx.animation.core.android)
|
||||||
|
// androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
// androidTestImplementation(libs.androidx.ui.test.junit4)
|
// androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
// debugImplementation(libs.androidx.ui.tooling)
|
// debugImplementation(libs.androidx.ui.tooling)
|
||||||
// debugImplementation(libs.androidx.ui.test.manifest)
|
// debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-feature android:name="android.hardware.type.watch" />
|
<uses-feature android:name="android.hardware.type.watch" />
|
||||||
|
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".presentation.MainActivity"
|
android:name=".presentation.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:alwaysRetainTaskState="true"
|
||||||
|
android:immersive="true"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/MainActivityTheme.Starting">
|
android:theme="@style/MainActivityTheme.Starting">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.TextUnitType
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.wear.compose.material.Text
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FlashingText(
|
||||||
|
text: String,
|
||||||
|
color: Color,
|
||||||
|
textDecor: TextDecoration = TextDecoration.None,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
key: Long,
|
||||||
|
fontStyle: FontStyle = FontStyle.Normal
|
||||||
|
) {
|
||||||
|
// Set up the flashing state
|
||||||
|
var isFlashing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// Create animation for the background color
|
||||||
|
val backgroundColor by animateColorAsState(
|
||||||
|
targetValue = if (isFlashing) Color.DarkGray else Color.Transparent,
|
||||||
|
animationSpec = tween(durationMillis = 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Trigger the flash effect once
|
||||||
|
LaunchedEffect(key1 = key) {
|
||||||
|
isFlashing = true
|
||||||
|
delay(1500)
|
||||||
|
isFlashing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the text with animated background
|
||||||
|
AutoSizedText(
|
||||||
|
text = text,
|
||||||
|
color = color,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = modifier
|
||||||
|
.background(color = backgroundColor),
|
||||||
|
textDecor = textDecor,
|
||||||
|
fontStyle = fontStyle
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AutoSizedText(
|
||||||
|
text: String,
|
||||||
|
color: Color = Color.White,
|
||||||
|
textAlign: TextAlign = TextAlign.Center,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
textDecor: TextDecoration = TextDecoration.None,
|
||||||
|
minFontSize: TextUnit = 5.sp,
|
||||||
|
targetFontSize: TextUnit = 24.sp,
|
||||||
|
fontWeight: FontWeight = FontWeight.Normal,
|
||||||
|
fontStyle: FontStyle = FontStyle.Normal,
|
||||||
|
) {
|
||||||
|
var display_text = text;
|
||||||
|
val textMeasurer = rememberTextMeasurer()
|
||||||
|
BoxWithConstraints(modifier) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
// val minFontSizePx = with(density) { minFontSize.toPx() }
|
||||||
|
// val maxFontSizePx = with(density) { targetFontSize.toPx() }
|
||||||
|
val availableWidthPx = with(density) { maxWidth.toPx() }
|
||||||
|
|
||||||
|
// Binary search to find the appropriate font size that fits the available width
|
||||||
|
var lowPx: TextUnit = minFontSize
|
||||||
|
var highPx: TextUnit = targetFontSize
|
||||||
|
var bestFontSizePx = targetFontSize;
|
||||||
|
val textStyle = TextStyle(fontWeight = fontWeight, color = color, fontSize=bestFontSizePx)
|
||||||
|
while (lowPx <= highPx) {
|
||||||
|
val midPx: TextUnit = TextUnit(value=lowPx.value/2 + highPx.value/2 , type= TextUnitType.Sp)
|
||||||
|
val style = textStyle.copy(fontSize = midPx)
|
||||||
|
|
||||||
|
val textLayoutResult = textMeasurer.measure(
|
||||||
|
text = display_text,
|
||||||
|
style = style,
|
||||||
|
maxLines = 1,
|
||||||
|
softWrap = false,
|
||||||
|
density = density,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (textLayoutResult.size.width <= availableWidthPx) {
|
||||||
|
// // This font size fits, try a larger oneb
|
||||||
|
bestFontSizePx = midPx;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// // This font size is too large, try a smaller one
|
||||||
|
// highPx = (midPx - 1).toFloat()
|
||||||
|
highPx = TextUnit(value=midPx.value - 1, type=TextUnitType.Sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the best font size back to sp and use it
|
||||||
|
// val bestFontSize = with(density) { bestFontSizePx.toSp() }
|
||||||
|
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = display_text,
|
||||||
|
color = color,
|
||||||
|
fontSize = bestFontSizePx,
|
||||||
|
fontStyle = fontStyle,
|
||||||
|
fontWeight = fontWeight,
|
||||||
|
maxLines = 1,
|
||||||
|
textDecoration = textDecor,
|
||||||
|
overflow = TextOverflow.Clip,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = textAlign
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.wear.compose.material.CompactChip
|
|
||||||
import androidx.wear.compose.material.MaterialTheme
|
|
||||||
import androidx.wear.compose.material.Text
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ControlDashboard(controlDashboardUiState: ControlDashboardUiState,
|
|
||||||
onMicClicked: () -> Unit,
|
|
||||||
onNavClicked: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier) {
|
|
||||||
Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxSize()) {
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
ControlDashboardButton(buttonState = controlDashboardUiState.micState,
|
|
||||||
onClick = onMicClicked,
|
|
||||||
labelText = if (controlDashboardUiState.micState.expanded) {
|
|
||||||
"Stop"
|
|
||||||
} else {
|
|
||||||
"Start"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ControlDashboardButton(buttonState: ControlDashboardButtonUiState,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
labelText: String,
|
|
||||||
modifier: Modifier = Modifier) {
|
|
||||||
CompactChip(modifier = Modifier,
|
|
||||||
onClick = onClick,
|
|
||||||
enabled = true,
|
|
||||||
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
|
||||||
shape = MaterialTheme.shapes.small,
|
|
||||||
label = {
|
|
||||||
Text(text = labelText)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Button(modifier = modifier, enabled = buttonState.enabled && buttonState.visible, onClick = onClick) {
|
|
||||||
// Text(contentDescription);
|
|
||||||
//// Icon(imageVector = imageVector, contentDescription = contentDescription)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
data class ControlDashboardButtonUiState(val expanded: Boolean, val enabled: Boolean, val visible: Boolean)
|
|
||||||
|
|
||||||
|
|
||||||
data class ControlDashboardUiState(val micState: ControlDashboardButtonUiState) {
|
|
||||||
init { // Check that at most one of the buttons is expanded
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,56 +6,62 @@
|
|||||||
package com.birdsounds.identify.presentation
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.ServiceCompat.startForeground
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.wear.ambient.AmbientLifecycleObserver
|
||||||
import androidx.wear.compose.material.CompactChip
|
import androidx.wear.compose.material.CompactChip
|
||||||
import androidx.wear.compose.material.MaterialTheme
|
import androidx.wear.compose.material.MaterialTheme
|
||||||
import androidx.wear.compose.material.Text
|
import androidx.wear.compose.material.Text
|
||||||
import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
||||||
import androidx.wear.compose.navigation.composable
|
import androidx.wear.compose.navigation.composable
|
||||||
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
||||||
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
|
import androidx.wear.ongoing.OngoingActivity
|
||||||
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
|
import com.birdsounds.identify.R
|
||||||
import com.birdsounds.identify.presentation.theme.IdentifyTheme
|
import com.birdsounds.identify.presentation.theme.IdentifyTheme
|
||||||
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
||||||
import com.google.android.horologist.audio.ui.VolumeViewModel
|
import com.google.android.horologist.audio.ui.VolumeViewModel
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumn
|
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
|
|
||||||
import com.google.android.horologist.compose.layout.ScreenScaffold
|
import com.google.android.horologist.compose.layout.ScreenScaffold
|
||||||
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
|
|
||||||
import com.google.android.horologist.compose.material.Chip
|
|
||||||
import com.google.android.horologist.compose.material.ListHeaderDefaults.firstItemPadding
|
|
||||||
import com.google.android.horologist.compose.material.ResponsiveListHeader
|
|
||||||
import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
val flow_stream = MutableStateFlow<String>("")
|
val flow_stream = MutableStateFlow<String>("")
|
||||||
|
|
||||||
val Any.TAG: String
|
val Any.TAG: String
|
||||||
@@ -64,26 +70,30 @@ val Any.TAG: String
|
|||||||
return if (tag.length <= 23) tag else tag.substring(0, 23)
|
return if (tag.length <= 23) tag else tag.substring(0, 23)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
|
||||||
|
lateinit var ambientObserver:AmbientLifecycleObserver
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
|
ambientObserver= AmbientLifecycleObserver(this.findActivity(), ambientCallback)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
lifecycle.addObserver(ambientObserver)
|
||||||
setTheme(android.R.style.Theme_DeviceDefault)
|
setTheme(android.R.style.Theme_DeviceDefault)
|
||||||
setContent {
|
setContent {
|
||||||
WearApp()
|
WearApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ForegroundServiceType")
|
||||||
@OptIn(ExperimentalHorologistApi::class)
|
@OptIn(ExperimentalHorologistApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun WearApp() {
|
fun WearApp() {
|
||||||
|
|
||||||
IdentifyTheme {
|
IdentifyTheme {
|
||||||
lateinit var requestPermissionLauncher: ManagedActivityResultLauncher<String, Boolean>
|
lateinit var requestPermissionLauncher: ManagedActivityResultLauncher<String, Boolean>
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -91,6 +101,48 @@ fun WearApp() {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
||||||
|
val touchIntent =
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
Intent(context, MainActivity::class.java),
|
||||||
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val notificationBuilder =
|
||||||
|
NotificationCompat.Builder(context, "Identify Watch App")
|
||||||
|
.setOngoing(true)
|
||||||
|
.setSmallIcon(com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||||
|
|
||||||
|
|
||||||
|
val ongoingActivity =
|
||||||
|
OngoingActivity.Builder(
|
||||||
|
context, 4, notificationBuilder
|
||||||
|
).setTouchIntent(touchIntent)
|
||||||
|
.setStaticIcon(
|
||||||
|
com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
ongoingActivity.apply(context)
|
||||||
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.notify(
|
||||||
|
4,
|
||||||
|
notificationBuilder.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
val notification: Notification = NotificationCompat.Builder(
|
||||||
|
context, "Identify Watch App"
|
||||||
|
)
|
||||||
|
.setContentTitle("Always On App")
|
||||||
|
.setContentText("Running in foreground")
|
||||||
|
.setSmallIcon(com.birdsounds.identify.R.mipmap.ic_launcher)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
|
val volumeViewModel: VolumeViewModel = viewModel(factory = VolumeViewModel.Factory)
|
||||||
|
|
||||||
val mainState = remember(activity) {
|
val mainState = remember(activity) {
|
||||||
@@ -99,7 +151,6 @@ fun WearApp() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
|
requestPermissionLauncher = rememberLauncherForActivityResult(RequestPermission()) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
mainState.permissionResultReturned()
|
mainState.permissionResultReturned()
|
||||||
@@ -108,41 +159,110 @@ fun WearApp() {
|
|||||||
|
|
||||||
val navController: NavHostController = rememberSwipeDismissableNavController()
|
val navController: NavHostController = rememberSwipeDismissableNavController()
|
||||||
mainState.setNavController(navController);
|
mainState.setNavController(navController);
|
||||||
SwipeDismissableNavHost(navController = navController, userSwipeEnabled = true, startDestination = "speaker") {
|
SwipeDismissableNavHost(
|
||||||
|
navController = navController,
|
||||||
|
userSwipeEnabled = true,
|
||||||
|
startDestination = "speaker"
|
||||||
|
) {
|
||||||
composable("species_list") {
|
composable("species_list") {
|
||||||
ScreenScaffold {
|
ScreenScaffold {
|
||||||
SpeciesListView(context = context)
|
SpeciesListView(context = context, appState = mainState.appState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composable("speaker") {
|
composable("speaker") {
|
||||||
ScreenScaffold {
|
ScreenScaffold {
|
||||||
CompactChip(modifier = Modifier,
|
Column(
|
||||||
onClick = { navController.navigate("species_list") },
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
Row() {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
// Define the animation for the scale
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 2.0f else 1.0f
|
||||||
|
)
|
||||||
|
|
||||||
|
CompactChip(
|
||||||
|
onClick = {
|
||||||
|
vibrate(
|
||||||
|
context,
|
||||||
|
250
|
||||||
|
);navController.navigate("species_list")
|
||||||
|
},
|
||||||
enabled = true,
|
enabled = true,
|
||||||
|
modifier = Modifier.scale(scale),
|
||||||
|
interactionSource = interactionSource,
|
||||||
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
label = {
|
label = {
|
||||||
Text(text = "View>")
|
Text(text = "Show List")
|
||||||
})
|
},
|
||||||
|
|
||||||
StartRecordingScreen(
|
)
|
||||||
appState = mainState.appState,
|
|
||||||
onMicClicked = {
|
|
||||||
|
}
|
||||||
|
Row() {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
// Define the animation for the scale
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 2.0f else 1.0f
|
||||||
|
)
|
||||||
|
CompactChip(
|
||||||
|
onClick = {
|
||||||
|
vibrate(context, 250);
|
||||||
scope.launch {
|
scope.launch {
|
||||||
mainState.onMicClicked()
|
mainState.onMicClicked()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onNavClicked = {
|
modifier = Modifier.scale(scale),
|
||||||
navController.navigate("species_list")
|
interactionSource = interactionSource,
|
||||||
|
enabled = true,
|
||||||
|
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
label = {
|
||||||
|
Text(text = if (mainState.appState == AppState.Recording) "Stop Recording" else "Start Recording")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
Row() {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
// Define the animation for the scale
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 2.0f else 1.0f
|
||||||
|
)
|
||||||
|
CompactChip(
|
||||||
|
onClick = {
|
||||||
|
vibrate(context, 250);
|
||||||
|
species_list_show.clear();
|
||||||
|
internal_list.clear();
|
||||||
|
},
|
||||||
|
enabled = true,
|
||||||
|
modifier = Modifier.scale(scale),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
label = {
|
||||||
|
Text(text = "Clear List")
|
||||||
|
},
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class MainState(private val activity: Activity, private val requestPermission: (
|
|||||||
public fun setNavController(_navController: NavHostController) {
|
public fun setNavController(_navController: NavHostController) {
|
||||||
navController = _navController
|
navController = _navController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun onMicClicked() {
|
suspend fun onMicClicked() {
|
||||||
playbackStateMutatorMutex.mutate {
|
playbackStateMutatorMutex.mutate {
|
||||||
when (appState) {
|
when (appState) {
|
||||||
|
|||||||
@@ -1,28 +1,58 @@
|
|||||||
package com.birdsounds.identify.presentation
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import com.google.android.gms.wearable.MessageEvent
|
import com.google.android.gms.wearable.MessageEvent
|
||||||
import com.google.android.gms.wearable.WearableListenerService
|
import com.google.android.gms.wearable.WearableListenerService
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
var last_message_recv_tstamp: Long = 0L;
|
||||||
|
|
||||||
class MessageListenerService : WearableListenerService() {
|
class MessageListenerService : WearableListenerService() {
|
||||||
private val tag = "MessageListenerService"
|
private val tag = "MessageListenerService"
|
||||||
|
lateinit var this_context: Context;
|
||||||
|
fun set_context(context: Context)
|
||||||
|
{
|
||||||
|
this_context = context;
|
||||||
|
}
|
||||||
override fun onMessageReceived(p0: MessageEvent) {
|
override fun onMessageReceived(p0: MessageEvent) {
|
||||||
super.onMessageReceived(p0)
|
super.onMessageReceived(p0)
|
||||||
val t_scored = ByteBuffer.wrap(p0.data).getLong()
|
val t_scored = ByteBuffer.wrap(p0.data).getLong()
|
||||||
|
last_message_recv_tstamp = t_scored;
|
||||||
|
|
||||||
var byte_strings: ByteArray = p0.data.copyOfRange(8, p0.data.size)
|
var byte_strings: ByteArray = p0.data.copyOfRange(8, p0.data.size)
|
||||||
var score_species_string = byte_strings.decodeToString()
|
var score_species_string = byte_strings.decodeToString()
|
||||||
var list_strings: List<String> = score_species_string.split(';')
|
var list_strings: List<String> = score_species_string.split(';')
|
||||||
|
var do_vibrate = false;
|
||||||
|
var did_add = false;
|
||||||
|
var new_entries = 0;
|
||||||
|
SpeciesList.clear_new_flags();
|
||||||
list_strings.map({
|
list_strings.map({
|
||||||
var split_str = it.split(',')
|
var split_str = it.split(',')
|
||||||
if (split_str.size == 2) {
|
if (split_str.size == 2) {
|
||||||
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored)
|
new_entries+=1;
|
||||||
if (out.score > 0.005) {
|
var out = AScore(split_str[0], split_str[1].toFloat(), t_scored);
|
||||||
SpeciesList.add_observation(out)
|
out.new_entry = true;
|
||||||
|
|
||||||
|
if (SpeciesList.add_observation(out))
|
||||||
|
{
|
||||||
|
did_add = true;
|
||||||
}
|
}
|
||||||
|
do_vibrate = true;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// SpeciesList.set_num_new_entries(new_entries);
|
||||||
|
// SpeciesList.insert_new_entry_spacer(new_entries);
|
||||||
|
if (did_add) {
|
||||||
|
vibrateDouble(this, 100, 250, 100);
|
||||||
|
} else if (do_vibrate) {
|
||||||
|
vibrate(this,100)
|
||||||
|
}
|
||||||
|
// if (new_entries > 0) {
|
||||||
|
SpeciesList.update_list_on_ui_thread();
|
||||||
|
// }
|
||||||
|
|
||||||
MessageSender.messageLog.add(t_scored)
|
MessageSender.messageLog.add(t_scored)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.birdsounds.identify.presentation
|
||||||
|
|
||||||
|
import androidx.wear.ambient.AmbientLifecycleObserver
|
||||||
|
|
||||||
|
val ambientCallback = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
|
||||||
|
override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
|
||||||
|
// ... Called when moving from interactive mode into ambient mode.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onExitAmbient() {
|
||||||
|
// ... Called when leaving ambient mode, back into interactive mode.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpdateAmbient() {
|
||||||
|
// ... Called by the system in order to allow the app to periodically
|
||||||
|
// update the display while in ambient mode. Typically the system will
|
||||||
|
// call this every 60 seconds.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,13 @@ import com.theeasiestway.opus.Opus
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
import android.media.AudioFormat
|
import android.media.AudioFormat
|
||||||
import android.media.AudioRecord
|
import android.media.AudioRecord
|
||||||
import android.media.MediaRecorder
|
import android.media.MediaRecorder
|
||||||
import android.media.audiofx.AutomaticGainControl
|
import android.media.audiofx.AutomaticGainControl
|
||||||
import android.media.audiofx.NoiseSuppressor
|
import android.media.audiofx.NoiseSuppressor
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.annotation.RequiresPermission
|
||||||
import encodePcmToAac
|
import encodePcmToAac
|
||||||
@@ -21,6 +23,66 @@ import java.time.Instant
|
|||||||
/**
|
/**
|
||||||
* A helper class to provide methods to record audio input from the MIC to the internal storage.
|
* A helper class to provide methods to record audio input from the MIC to the internal storage.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.Vibrator
|
||||||
|
import android.os.VibratorManager
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
fun vibrateDouble(
|
||||||
|
context: Context,
|
||||||
|
firstDuration: Long = 100,
|
||||||
|
pauseDuration: Long = 200,
|
||||||
|
secondDuration: Long = 100
|
||||||
|
) {
|
||||||
|
// Get the vibrator service
|
||||||
|
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||||
|
vibratorManager.defaultVibrator
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a pattern for double vibration: 0ms delay, firstDuration vibrate, pauseDuration pause, secondDuration vibrate
|
||||||
|
val vibrationPattern = longArrayOf(0, firstDuration, pauseDuration, secondDuration)
|
||||||
|
|
||||||
|
// The -1 parameter means don't repeat the pattern
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, -1)
|
||||||
|
vibrator.vibrate(vibrationEffect)
|
||||||
|
} else {
|
||||||
|
// Deprecated method for older API levels
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
vibrator.vibrate(vibrationPattern, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun vibrate(context: Context, duration: Long = 500) {
|
||||||
|
val vibrator = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||||
|
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||||
|
vibratorManager.defaultVibrator
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
|
}
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||||
|
val vibrationEffect = VibrationEffect.createOneShot(
|
||||||
|
duration, // duration in milliseconds
|
||||||
|
VibrationEffect.DEFAULT_AMPLITUDE // strength of vibration
|
||||||
|
)
|
||||||
|
vibrator.vibrate(vibrationEffect)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
vibrator.vibrate(duration) // Vibration for 500ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
class SoundRecorder(
|
class SoundRecorder(
|
||||||
context_in: Context,
|
context_in: Context,
|
||||||
@@ -44,7 +106,7 @@ class SoundRecorder(
|
|||||||
var noiseSuppressor: NoiseSuppressor? = null
|
var noiseSuppressor: NoiseSuppressor? = null
|
||||||
var automaticGainControl: AutomaticGainControl? = null
|
var automaticGainControl: AutomaticGainControl? = null
|
||||||
var chunk_index: Int = 0
|
var chunk_index: Int = 0
|
||||||
val audioSource = MediaRecorder.AudioSource.DEFAULT
|
val audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION
|
||||||
val sampleRateInHz = 48000
|
val sampleRateInHz = 48000
|
||||||
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
||||||
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package com.birdsounds.identify.presentation
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@@ -12,16 +15,24 @@ class AScore(
|
|||||||
_species: String,
|
_species: String,
|
||||||
_score: Float,
|
_score: Float,
|
||||||
_timestamp: Long,
|
_timestamp: Long,
|
||||||
_trigger: Boolean = false
|
_trigger: Boolean = false,
|
||||||
|
_new_entry: Boolean = false,
|
||||||
|
_redraw_number: Long = 0,
|
||||||
) {
|
) {
|
||||||
|
var redraw_number = _redraw_number;
|
||||||
val split_stuff: List<String> = _species.split("_");
|
val split_stuff: List<String> = _species.split("_");
|
||||||
val species = split_stuff[0];
|
val species = split_stuff[0];
|
||||||
val score = _score;
|
val score = _score;
|
||||||
var trigger = _trigger;
|
var trigger = _trigger;
|
||||||
|
var new_entry = _new_entry;
|
||||||
|
|
||||||
// var common_name = split_stuff[1];
|
// var common_name = split_stuff[1];
|
||||||
val common_name = if (split_stuff.size > 1) split_stuff[1] else "";
|
val common_name = if (split_stuff.size > 1) split_stuff[1] else "";
|
||||||
val timestamp = _timestamp
|
val timestamp = _timestamp
|
||||||
|
fun set_redraw_number(redraw_num: Long)
|
||||||
|
{
|
||||||
|
redraw_number = redraw_num
|
||||||
|
}
|
||||||
fun age(): Long {
|
fun age(): Long {
|
||||||
var tstamp: Long = Instant.now().toEpochMilli()
|
var tstamp: Long = Instant.now().toEpochMilli()
|
||||||
return (tstamp - timestamp)
|
return (tstamp - timestamp)
|
||||||
@@ -34,20 +45,28 @@ class AScore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var internal_list = mutableListOf<AScore>()
|
||||||
|
|
||||||
object SpeciesList {
|
object SpeciesList {
|
||||||
var internal_list = mutableListOf<AScore>()
|
var trigger_redraw = 0;
|
||||||
|
var num_new_entries = 0;
|
||||||
var do_add_observation = false
|
var do_add_observation = false
|
||||||
var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null
|
var _list_on_ui: SnapshotStateList<MutableState<AScore>>? = null
|
||||||
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) {
|
fun setSpeciecsShow_list(list_in: SnapshotStateList<MutableState<AScore>>) {
|
||||||
_list_on_ui = list_in;
|
_list_on_ui = list_in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clear_new_flags() {
|
||||||
|
for (i in internal_list) {
|
||||||
|
i.new_entry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun add_observation(species_in: AScore) {
|
fun add_observation(species_in: AScore): Boolean {
|
||||||
Log.w(TAG, "In add observation")
|
Log.w(TAG, "In add observation")
|
||||||
var idx = 0
|
var idx = 0
|
||||||
var idx_replace = -1
|
var idx_replace = -1
|
||||||
|
var added_new = false;
|
||||||
for (i in internal_list) {
|
for (i in internal_list) {
|
||||||
if (i.species == species_in.species) {
|
if (i.species == species_in.species) {
|
||||||
idx_replace = idx
|
idx_replace = idx
|
||||||
@@ -60,22 +79,31 @@ object SpeciesList {
|
|||||||
internal_list[idx_replace].trigger = true;
|
internal_list[idx_replace].trigger = true;
|
||||||
} else {
|
} else {
|
||||||
internal_list.add(species_in)
|
internal_list.add(species_in)
|
||||||
|
added_new = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList()
|
// internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList()
|
||||||
internal_list = internal_list.sortedWith(compareBy({ it.age() }, { it.score})).toMutableList()
|
internal_list =
|
||||||
|
internal_list.sortedWith(compareBy({ it.age() }, { it.score })).toMutableList()
|
||||||
|
|
||||||
|
return added_new
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun update_list_on_ui_thread(
|
||||||
|
) {
|
||||||
while (_list_on_ui!!.size < internal_list.size) {
|
while (_list_on_ui!!.size < internal_list.size) {
|
||||||
_list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L)))
|
_list_on_ui!!.add(mutableStateOf(AScore("", 0.0F, 0L)))
|
||||||
}
|
}
|
||||||
|
val time_stamp = System.currentTimeMillis()
|
||||||
for ((index, value) in internal_list.withIndex()) {
|
for ((index, value) in internal_list.withIndex()) {
|
||||||
_list_on_ui?.get(index)?.value = value;
|
_list_on_ui?.get(index)?.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, internal_list.size.toString())
|
|
||||||
Log.w(TAG, _list_on_ui?.size.toString()) // internal_list.add(species_in)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,23 +2,22 @@ package com.birdsounds.identify.presentation
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.text.format.DateFormat
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.CubicBezierEasing
|
|
||||||
import androidx.compose.animation.core.Easing
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.material.icons.rounded.Mic
|
||||||
|
import androidx.compose.material.icons.rounded.MicOff
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -27,23 +26,25 @@ import androidx.compose.runtime.snapshots.SnapshotStateList
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.lerp
|
import androidx.wear.compose.foundation.curvedComposable
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.util.lerp
|
|
||||||
import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults.scalingParams
|
|
||||||
import androidx.wear.compose.foundation.lazy.ScalingParams
|
|
||||||
import androidx.wear.compose.foundation.lazy.items
|
import androidx.wear.compose.foundation.lazy.items
|
||||||
import androidx.wear.compose.material.Text
|
import androidx.wear.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.wear.compose.material.Icon
|
||||||
|
import androidx.wear.compose.material.TimeTextDefaults
|
||||||
|
import androidx.wear.compose.material.TimeText
|
||||||
|
import androidx.wear.compose.material.curvedText
|
||||||
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
import com.google.android.horologist.annotations.ExperimentalHorologistApi
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumn
|
import com.google.android.horologist.compose.layout.ScalingLazyColumn
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
|
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
|
||||||
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
|
|
||||||
import com.google.android.horologist.compose.layout.ScreenScaffold
|
import com.google.android.horologist.compose.layout.ScreenScaffold
|
||||||
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
|
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
fun interpolateColor(value: Float): Color {
|
fun interpolateColor(value: Float): Color {
|
||||||
@@ -63,88 +64,190 @@ fun interpolateColor(value: Float): Color {
|
|||||||
return Color(red.toInt(), green.toInt(), blue.toInt())
|
return Color(red.toInt(), green.toInt(), blue.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun FlashingText(
|
|
||||||
text: String,
|
|
||||||
color: Color,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
// Set up the flashing state
|
|
||||||
var isFlashing by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
// Create animation for the background color
|
|
||||||
val backgroundColor by animateColorAsState(
|
|
||||||
targetValue = if (isFlashing) Color.Cyan else Color.Transparent,
|
|
||||||
animationSpec = tween(durationMillis = 500)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Trigger the flash effect once
|
|
||||||
LaunchedEffect(key1 = true) {
|
|
||||||
isFlashing = true
|
|
||||||
delay(300)
|
|
||||||
isFlashing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display the text with animated background
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
color = color,
|
|
||||||
modifier = modifier
|
|
||||||
.background(color = backgroundColor)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val species_list_show = mutableStateListOf<MutableState<AScore>>()
|
||||||
|
|
||||||
@SuppressLint("UnrememberedMutableState")
|
@SuppressLint("UnrememberedMutableState")
|
||||||
@OptIn(ExperimentalHorologistApi::class)
|
@OptIn(ExperimentalHorologistApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SpeciesListView(context: Context,
|
fun SpeciesListView(
|
||||||
|
context: Context, appState: AppState,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val species_list_show = mutableStateListOf<MutableState<AScore>>()
|
|
||||||
for (i in 1..3)
|
for (i in 1..3) {
|
||||||
{
|
|
||||||
val hi = mutableStateOf(AScore("", 0.0F, 0L))
|
val hi = mutableStateOf(AScore("", 0.0F, 0L))
|
||||||
species_list_show.add(hi);
|
species_list_show.add(hi);
|
||||||
}
|
}
|
||||||
SpeciesList.setSpeciecsShow_list(species_list_show)
|
SpeciesList.setSpeciecsShow_list(species_list_show)
|
||||||
|
|
||||||
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
|
val species_show: SnapshotStateList<MutableState<AScore>> = remember { species_list_show }
|
||||||
|
|
||||||
// var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f)
|
// var sP = scalingParams( maxTransitionArea= 0.25f, minTransitionArea = 0.05f)
|
||||||
// val columnState = ScalingLazyColumnState(scalingParams = sP);
|
// val columnState = ScalingLazyColumnState(scalingParams = sP);
|
||||||
var columnState = rememberResponsiveColumnState(contentPadding = ScalingLazyColumnDefaults.padding(first = ScalingLazyColumnDefaults.ItemType.Text,
|
var columnState = rememberResponsiveColumnState(
|
||||||
last = ScalingLazyColumnDefaults.ItemType.Chip), verticalArrangement= Arrangement.spacedBy(
|
contentPadding = ScalingLazyColumnDefaults.padding(
|
||||||
|
first = ScalingLazyColumnDefaults.ItemType.Text,
|
||||||
|
last = ScalingLazyColumnDefaults.ItemType.Chip
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(
|
||||||
space = 1.dp,
|
space = 1.dp,
|
||||||
alignment = Alignment.Top,
|
alignment = Alignment.Top,
|
||||||
),)
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val text_counter = remember { mutableStateOf("Initial text") }
|
||||||
|
LaunchedEffect(key1 = true) {
|
||||||
|
while (true) {
|
||||||
|
// Update the text - you can put your custom logic here
|
||||||
|
// For example, showing current time:
|
||||||
|
var lag = ((Instant.now().toEpochMilli() - last_message_recv_tstamp) / 1000F).toInt();
|
||||||
|
if (last_message_recv_tstamp == 0L) {
|
||||||
|
text_counter.value = "No msg recv"
|
||||||
|
} else {
|
||||||
|
text_counter.value = "${lag}s"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay for 1 second
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ScreenScaffold(
|
||||||
|
scrollState = columnState
|
||||||
|
) {
|
||||||
|
TimeText(
|
||||||
|
timeSource =
|
||||||
|
TimeTextDefaults.timeSource(
|
||||||
|
DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm")
|
||||||
|
),
|
||||||
|
startCurvedContent = {
|
||||||
|
curvedComposable {
|
||||||
|
Icon(
|
||||||
|
if (appState == AppState.Recording) Icons.Rounded.Mic else Icons.Rounded.MicOff,
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
// tint = androidx.compose.ui.graphics.Color.Red
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// curvedText(
|
||||||
|
// text = if (appState == AppState.Recording) "" else "Inactive0",
|
||||||
|
//// textAlign = TextAlign.Center,
|
||||||
|
// )
|
||||||
|
},
|
||||||
|
endCurvedContent = {
|
||||||
|
|
||||||
|
curvedText(text = text_counter.value)//, textAlign = TextAlign.Center)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
|
||||||
ScreenScaffold(scrollState = columnState) {
|
|
||||||
ScalingLazyColumn(
|
ScalingLazyColumn(
|
||||||
columnState = columnState,
|
columnState = columnState,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
items(species_show) { aSpec ->
|
itemsIndexed(
|
||||||
if (aSpec.value.trigger)
|
species_show,
|
||||||
{
|
) { index, aSpec ->
|
||||||
|
var text_decor = TextDecoration.None
|
||||||
|
var text_style = FontStyle.Normal;
|
||||||
|
|
||||||
|
val currentName = aSpec.value.common_name
|
||||||
|
val currentScore = aSpec.value.score
|
||||||
|
val isNewEntry = aSpec.value.new_entry
|
||||||
|
val shouldTrigger = aSpec.value.trigger
|
||||||
|
|
||||||
|
var add_sep = false;
|
||||||
|
if (aSpec.value.new_entry) {
|
||||||
|
// add_sep = true;
|
||||||
|
text_style = FontStyle.Italic;
|
||||||
|
// aSpec.value.new_entry = false
|
||||||
|
}
|
||||||
|
Log.w(TAG, "Species " + currentName + " " + aSpec.value.new_entry.toString())
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(100);
|
||||||
|
aSpec.value.new_entry = false
|
||||||
|
}
|
||||||
|
// if (index == (SpeciesList.num_new_entries-1)) {
|
||||||
|
// add_sep = true;"
|
||||||
|
// }
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"Adding Separator: " + index.toString() + " " + SpeciesList.num_new_entries + " " + add_sep.toString()
|
||||||
|
)
|
||||||
|
aSpec.value.new_entry = false;
|
||||||
|
|
||||||
|
// var composables: List<@Composable () -> Unit> = listOf();
|
||||||
|
|
||||||
|
key(currentName, isNewEntry, shouldTrigger) {
|
||||||
|
if (shouldTrigger) {
|
||||||
Log.w(TAG, "Trigger " + aSpec.toString())
|
Log.w(TAG, "Trigger " + aSpec.toString())
|
||||||
FlashingText(text = aSpec.value.common_name, color = interpolateColor(aSpec.value.score))
|
FlashingText(
|
||||||
aSpec.value.trigger = false;
|
text = aSpec.value.common_name,
|
||||||
} else
|
fontStyle = text_style,
|
||||||
{
|
color = interpolateColor(aSpec.value.score),
|
||||||
Text(text = aSpec.value.common_name, color = interpolateColor(aSpec.value.score))
|
key = Instant.now().toEpochMilli(),
|
||||||
|
textDecor = text_decor,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(100);
|
||||||
|
aSpec.value.trigger = false
|
||||||
|
}
|
||||||
|
Log.e("TAG", "BLINKING")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
AutoSizedText(
|
||||||
|
text = aSpec.value.common_name,
|
||||||
|
color = interpolateColor(aSpec.value.score),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
textDecor = text_decor,
|
||||||
|
fontStyle = text_style,
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.e("TAG", "Not blinking")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (add_sep) {
|
||||||
|
// composables += {
|
||||||
|
AutoSizedText(
|
||||||
|
text = "-------------------",
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column {
|
||||||
|
// composables.forEach { composable ->
|
||||||
|
// Row {
|
||||||
|
// composable()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
}// Dynamically display the chips
|
}// Dynamically display the chips
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
// ScreenScaffold(scrollState = columnState) {
|
// ScreenScaffold(scrollState = columnState) {
|
||||||
// ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
|
// ScalingLazyColumn(columnState = columnState, modifier = Modifier.fillMaxSize()) {
|
||||||
// items(species_show) { aSpec -> Text(text = aSpec.value.common_name)
|
// items(species_show) { aSpec -> Text(text = aSpec.value.common_name)
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package com.birdsounds.identify.presentation
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import com.google.android.horologist.compose.layout.ScreenScaffold
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StartRecordingScreen(
|
|
||||||
appState: AppState,
|
|
||||||
onMicClicked: () -> Unit,
|
|
||||||
onNavClicked: () -> Unit
|
|
||||||
) {
|
|
||||||
ScreenScaffold {
|
|
||||||
val controlDashboardUiState = computeControlDashboardUiState(
|
|
||||||
appState = appState,
|
|
||||||
)
|
|
||||||
ControlDashboard(
|
|
||||||
controlDashboardUiState = controlDashboardUiState,
|
|
||||||
onMicClicked = onMicClicked,
|
|
||||||
onNavClicked = onNavClicked
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeControlDashboardUiState(
|
|
||||||
appState: AppState,
|
|
||||||
): ControlDashboardUiState =
|
|
||||||
when (appState) {
|
|
||||||
AppState.Ready -> ControlDashboardUiState(
|
|
||||||
micState = ControlDashboardButtonUiState(expanded = false, visible = true, enabled = true),
|
|
||||||
)
|
|
||||||
AppState.Recording -> ControlDashboardUiState(
|
|
||||||
micState = ControlDashboardButtonUiState(expanded = true, visible = true, enabled = true),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PlaybackStatePreviewProvider : CollectionPreviewParameterProvider<AppState>(
|
|
||||||
listOf(
|
|
||||||
AppState.Recording,
|
|
||||||
AppState.Ready
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
|
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
|
||||||
<item name="windowSplashScreenBackground">@android:color/black</item>
|
<item name="windowSplashScreenBackground">@android:color/black</item>
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
|
||||||
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item>
|
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user