This commit is contained in:
2025-04-02 13:20:29 -04:00
parent 7ff235fb3f
commit e73d52d221
26 changed files with 1511 additions and 295 deletions

View File

@@ -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
View File

@@ -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>

View 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">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Android App.mobile.executor&quot;: &quot;Run&quot;,
&quot;Android App.wear.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;com.google.services.firebase.aqiPopupShown&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.17&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;
}
}</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>

View 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">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="2toSwb3riPZMH7ufWxT9aBYCKaO" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Android App.mobile.executor&quot;: &quot;Run&quot;,
&quot;Android App.wear.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;com.google.services.firebase.aqiPopupShown&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/isp/Seafile/Designs/Android/bird_sound_identify_wearos&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.17&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;
}
}</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>

View File

@@ -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" }

View File

@@ -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)

View File

@@ -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;

View File

@@ -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!!
) )
} }

View File

@@ -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>()

View File

@@ -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())

View File

@@ -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;
} }

View File

@@ -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] =

View File

@@ -7,13 +7,89 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> tools:context=".MainActivity">
<TextView <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:text="Hello World!" android:orientation="vertical">
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" <TextView
app:layout_constraintStart_toStartOf="parent" android:id="@+id/textView"
app:layout_constraintTop_toTopOf="parent" /> android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingTop="15dp"
android:text="Threshold:"
android:textSize="34sp" />
<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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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
)
}
}

View File

@@ -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
}
}

View File

@@ -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,42 +159,111 @@ 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,
enabled = true, horizontalAlignment = Alignment.CenterHorizontally,
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp), modifier = Modifier
shape = MaterialTheme.shapes.small, .fillMaxSize()
label = { .align(Alignment.Center)
Text(text = "View>") ) {
}) 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,
modifier = Modifier.scale(scale),
interactionSource = interactionSource,
contentPadding = PaddingValues(5.dp, 1.dp, 5.dp, 1.dp),
shape = MaterialTheme.shapes.small,
label = {
Text(text = "Show List")
},
)
StartRecordingScreen(
appState = mainState.appState,
onMicClicked = {
scope.launch {
mainState.onMicClicked()
}
},
onNavClicked = {
navController.navigate("species_list")
} }
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 {
mainState.onMicClicked()
}
},
modifier = Modifier.scale(scale),
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")
},
)
}
}
} }
} }
} }
} }

View File

@@ -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) {

View File

@@ -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)
} }
} }

View File

@@ -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.
}
}

View File

@@ -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

View File

@@ -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)
} }
} }

View File

@@ -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,124 +26,228 @@ 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 {
// Ensure that the input value is clamped between 0 and 1 // Ensure that the input value is clamped between 0 and 1
val clampedValue = value.coerceIn(0f, 1f) val clampedValue = value.coerceIn(0f, 1f)
// Red component: starts at 255 (white) and decreases to 0 // Red component: starts at 255 (white) and decreases to 0
val red = (255 * (1 - clampedValue)).toInt() val red = (255 * (1 - clampedValue)).toInt()
// Green component: stays at 255 (since both white and green have full green) // Green component: stays at 255 (since both white and green have full green)
val green = 255 val green = 255
// Blue component: starts at 255 (white) and decreases to 0 // Blue component: starts at 255 (white) and decreases to 0
val blue = (255 * (1 - clampedValue)).toInt() val blue = (255 * (1 - clampedValue)).toInt()
// Construct the color // Construct the 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(
space = 1.dp, first = ScalingLazyColumnDefaults.ItemType.Text,
alignment = Alignment.Top, last = ScalingLazyColumnDefaults.ItemType.Chip
),) ),
verticalArrangement = Arrangement.spacedBy(
space = 1.dp,
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) {
ScalingLazyColumn(
columnState = columnState,
modifier = Modifier.fillMaxWidth()
) {
items(species_show) { aSpec ->
if (aSpec.value.trigger)
{
Log.w(TAG,"Trigger "+aSpec.toString())
FlashingText(text = aSpec.value.common_name, color = interpolateColor(aSpec.value.score))
aSpec.value.trigger = false;
} else
{
Text(text = aSpec.value.common_name, color = interpolateColor(aSpec.value.score))
}
}
} // Dynamically display the chips
} }
} }
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)
) {
ScalingLazyColumn(
columnState = columnState,
modifier = Modifier.fillMaxWidth()
) {
itemsIndexed(
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())
FlashingText(
text = aSpec.value.common_name,
fontStyle = text_style,
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
}
}
}
}
// 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)

View File

@@ -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
)
)

View File

@@ -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>