Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
|
053930c06a | 1 week ago |
|
25410c9499 | 1 week ago |
@ -1,2 +1,2 @@
|
||||
#Wed Jun 04 10:47:17 CST 2025
|
||||
#Fri Apr 25 19:53:19 CST 2025
|
||||
gradle.version=8.5
|
||||
|
@ -1,2 +1,7 @@
|
||||
#Thu Jun 05 00:28:15 CST 2025
|
||||
java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home
|
||||
<<<<<<< HEAD
|
||||
#Mon May 26 14:33:47 GMT+08:00 2025
|
||||
java.home=D\:\\Android\\AS_INstall\\jbr
|
||||
=======
|
||||
#Sat May 24 23:39:46 CST 2025
|
||||
java.home=D\:\\Andr\\jbr
|
||||
>>>>>>> a11e684a7b53b2288bd2435ed63bc73bcd82db22
|
||||
|
@ -1 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidTestResultsUserPreferences">
|
||||
<option name="androidTestResultsTableState">
|
||||
<map>
|
||||
<entry key="975774096">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Medium_Phone_API_36" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1074918448">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Medium_Phone_API_36" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1125150954">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Medium_Phone_API_36" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -1,751 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceStreaming">
|
||||
<option name="deviceSelectionList">
|
||||
<list>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="Sony" />
|
||||
<option name="codename" value="A402SO" />
|
||||
<option name="id" value="A402SO" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Sony" />
|
||||
<option name="name" value="Xperia 10" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2520" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="27" />
|
||||
<option name="brand" value="DOCOMO" />
|
||||
<option name="codename" value="F01L" />
|
||||
<option name="id" value="F01L" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="FUJITSU" />
|
||||
<option name="name" value="F-01L" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1280" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OnePlus" />
|
||||
<option name="codename" value="OP535DL1" />
|
||||
<option name="id" value="OP535DL1" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="OnePlus" />
|
||||
<option name="name" value="CPH2409" />
|
||||
<option name="screenDensity" value="401" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2412" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OnePlus" />
|
||||
<option name="codename" value="OP5552L1" />
|
||||
<option name="id" value="OP5552L1" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="OnePlus" />
|
||||
<option name="name" value="CPH2415" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2412" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OPPO" />
|
||||
<option name="codename" value="OP573DL1" />
|
||||
<option name="id" value="OP573DL1" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="OPPO" />
|
||||
<option name="name" value="CPH2557" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="28" />
|
||||
<option name="brand" value="DOCOMO" />
|
||||
<option name="codename" value="SH-01L" />
|
||||
<option name="id" value="SH-01L" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="SHARP" />
|
||||
<option name="name" value="AQUOS sense2 SH-01L" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2160" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a15" />
|
||||
<option name="id" value="a15" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A15" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a15x" />
|
||||
<option name="id" value="a15x" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A15 5G" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a16x" />
|
||||
<option name="id" value="a16x" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A16 5G" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a35x" />
|
||||
<option name="id" value="a35x" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A35" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="31" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a51" />
|
||||
<option name="id" value="a51" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy A51" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="akita" />
|
||||
<option name="id" value="akita" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="arcfox" />
|
||||
<option name="id" value="arcfox" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="razr plus 2024" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="1272" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="austin" />
|
||||
<option name="id" value="austin" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g 5G (2022)" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="b0q" />
|
||||
<option name="id" value="b0q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S22 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3088" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="b6q" />
|
||||
<option name="id" value="b6q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Flip 6" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2640" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="32" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="bluejay" />
|
||||
<option name="id" value="bluejay" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 6a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="caiman" />
|
||||
<option name="id" value="caiman" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="960" />
|
||||
<option name="screenY" value="2142" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="caiman" />
|
||||
<option name="id" value="caiman" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="960" />
|
||||
<option name="screenY" value="2142" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="comet" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="comet" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro Fold" />
|
||||
<option name="screenDensity" value="390" />
|
||||
<option name="screenX" value="2076" />
|
||||
<option name="screenY" value="2152" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="comet" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="comet" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro Fold" />
|
||||
<option name="screenDensity" value="390" />
|
||||
<option name="screenX" value="2076" />
|
||||
<option name="screenY" value="2152" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="29" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="crownqlteue" />
|
||||
<option name="id" value="crownqlteue" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Note9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2220" />
|
||||
<option name="screenY" value="1080" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="dm2q" />
|
||||
<option name="id" value="dm2q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="S23 Plus" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="dm3q" />
|
||||
<option name="id" value="dm3q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S23 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3088" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="dubai" />
|
||||
<option name="id" value="dubai" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="edge 30" />
|
||||
<option name="screenDensity" value="405" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="e1q" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="e1q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S24" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="e3q" />
|
||||
<option name="id" value="e3q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S24 Ultra" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3120" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="eos" />
|
||||
<option name="id" value="eos" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Eos" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="384" />
|
||||
<option name="screenY" value="384" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix" />
|
||||
<option name="id" value="felix" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix" />
|
||||
<option name="id" value="felix" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix_camera" />
|
||||
<option name="id" value="felix_camera" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="fogona" />
|
||||
<option name="id" value="fogona" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g play - 2024" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="g0q" />
|
||||
<option name="id" value="g0q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-S906U1" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gta9pwifi" />
|
||||
<option name="id" value="gta9pwifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-X210" />
|
||||
<option name="screenDensity" value="240" />
|
||||
<option name="screenX" value="1200" />
|
||||
<option name="screenY" value="1920" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts7xllite" />
|
||||
<option name="id" value="gts7xllite" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-T738U" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts8uwifi" />
|
||||
<option name="formFactor" value="Tablet" />
|
||||
<option name="id" value="gts8uwifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S8 Ultra" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="1848" />
|
||||
<option name="screenY" value="2960" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts8wifi" />
|
||||
<option name="formFactor" value="Tablet" />
|
||||
<option name="id" value="gts8wifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S8" />
|
||||
<option name="screenDensity" value="274" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts9fe" />
|
||||
<option name="id" value="gts9fe" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S9 FE 5G" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="2304" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts9wifi" />
|
||||
<option name="id" value="gts9wifi" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-X710" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="husky" />
|
||||
<option name="id" value="husky" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8 Pro" />
|
||||
<option name="screenDensity" value="390" />
|
||||
<option name="screenX" value="1008" />
|
||||
<option name="screenY" value="2244" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="java" />
|
||||
<option name="id" value="java" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="G20" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="komodo" />
|
||||
<option name="id" value="komodo" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro XL" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1008" />
|
||||
<option name="screenY" value="2244" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="komodo" />
|
||||
<option name="id" value="komodo" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro XL" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1008" />
|
||||
<option name="screenY" value="2244" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="lynx" />
|
||||
<option name="id" value="lynx" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 7a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="maui" />
|
||||
<option name="id" value="maui" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g play - 2023" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="o1q" />
|
||||
<option name="id" value="o1q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S21" />
|
||||
<option name="screenDensity" value="421" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="31" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="oriole" />
|
||||
<option name="id" value="oriole" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 6" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="pa3q" />
|
||||
<option name="id" value="pa3q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S25 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3120" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="panther" />
|
||||
<option name="id" value="panther" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 7" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="q5q" />
|
||||
<option name="id" value="q5q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Z Fold5" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1812" />
|
||||
<option name="screenY" value="2176" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="q6q" />
|
||||
<option name="id" value="q6q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Z Fold6" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1856" />
|
||||
<option name="screenY" value="2160" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="r11" />
|
||||
<option name="formFactor" value="Wear OS" />
|
||||
<option name="id" value="r11" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Watch" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="384" />
|
||||
<option name="screenY" value="384" />
|
||||
<option name="type" value="WEAR_OS" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="r11q" />
|
||||
<option name="id" value="r11q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-S711U" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="redfin" />
|
||||
<option name="id" value="redfin" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 5" />
|
||||
<option name="screenDensity" value="440" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="shiba" />
|
||||
<option name="id" value="shiba" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="t2q" />
|
||||
<option name="id" value="t2q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S21 Plus" />
|
||||
<option name="screenDensity" value="394" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tangorpro" />
|
||||
<option name="formFactor" value="Tablet" />
|
||||
<option name="id" value="tangorpro" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Tablet" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tegu" />
|
||||
<option name="id" value="tegu" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tokay" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="tokay" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tokay" />
|
||||
<option name="default" value="true" />
|
||||
<option name="id" value="tokay" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="xcover7" />
|
||||
<option name="id" value="xcover7" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-G556B" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2408" />
|
||||
</PersistentDeviceSelectionData>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,123 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
@ -1,77 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
|
||||
|
||||
class DataFragment : Fragment() {
|
||||
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var adapter: VideoAnalysisAdapter
|
||||
private var currentUsername: String? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
android.util.Log.d("DataFragment", "onCreateView called")
|
||||
return inflater.inflate(R.layout.fragment_data, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
android.util.Log.d("DataFragment", "onViewCreated called")
|
||||
|
||||
// 获取当前登录用户名
|
||||
val prefs = requireContext().getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
|
||||
currentUsername = prefs.getString("username", null)?.trim()
|
||||
android.util.Log.d("DataFragment", "Current Username: $currentUsername")
|
||||
|
||||
if (currentUsername == null) {
|
||||
Toast.makeText(requireContext(), "未登录,无法查看数据", Toast.LENGTH_SHORT).show()
|
||||
android.util.Log.w("DataFragment", "Current username is null, cannot load data.")
|
||||
return
|
||||
}
|
||||
|
||||
recyclerView = view.findViewById(R.id.recycler_view_video_analysis_results)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
loadVideoAnalysisResults()
|
||||
}
|
||||
|
||||
private fun loadVideoAnalysisResults() {
|
||||
android.util.Log.d("DataFragment", "loadVideoAnalysisResults called for username: $currentUsername")
|
||||
currentUsername?.let { username ->
|
||||
lifecycleScope.launch {
|
||||
val db = AppDatabase.getDatabase(requireContext())
|
||||
val results = withContext(Dispatchers.IO) {
|
||||
db.videoAnalysisResultDao().getVideoAnalysisResultsByUsername(username).firstOrNull()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (results != null && results.isNotEmpty()) {
|
||||
android.util.Log.d("DataFragment", "Loaded ${results.size} video analysis results.")
|
||||
adapter = VideoAnalysisAdapter(results)
|
||||
recyclerView.adapter = adapter
|
||||
} else {
|
||||
// 没有数据,可以显示一个提示
|
||||
Toast.makeText(requireContext(), "暂无训练记录", Toast.LENGTH_SHORT).show()
|
||||
android.util.Log.d("DataFragment", "No video analysis results found for username: $username")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.tensorflow.lite.examples.poseestimation.R
|
||||
|
||||
class HomeFragment : Fragment() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// 设置硬拉卡片
|
||||
val deadliftCard = view.findViewById<View>(R.id.deadlift_card)
|
||||
val deadliftImage = deadliftCard.findViewById<ImageView>(R.id.exercise_image)
|
||||
val deadliftName = deadliftCard.findViewById<TextView>(R.id.exercise_name)
|
||||
deadliftImage.setImageResource(R.drawable.deadlift)
|
||||
deadliftName.text = "硬拉"
|
||||
deadliftCard.setOnClickListener {
|
||||
startExerciseDetail("硬拉")
|
||||
}
|
||||
|
||||
// 设置深蹲卡片
|
||||
val squatCard = view.findViewById<View>(R.id.squat_card)
|
||||
val squatImage = squatCard.findViewById<ImageView>(R.id.exercise_image)
|
||||
val squatName = squatCard.findViewById<TextView>(R.id.exercise_name)
|
||||
squatImage.setImageResource(R.drawable.deep_squats)
|
||||
squatName.text = "深蹲"
|
||||
squatCard.setOnClickListener {
|
||||
startExerciseDetail("深蹲")
|
||||
}
|
||||
|
||||
// 设置平板支撑卡片
|
||||
val plankCard = view.findViewById<View>(R.id.plank_card)
|
||||
val plankImage = plankCard.findViewById<ImageView>(R.id.exercise_image)
|
||||
val plankName = plankCard.findViewById<TextView>(R.id.exercise_name)
|
||||
plankImage.setImageResource(R.drawable.plank)
|
||||
plankName.text = "平板支撑"
|
||||
plankCard.setOnClickListener {
|
||||
startExerciseDetail("平板支撑")
|
||||
}
|
||||
|
||||
// 设置引体向上卡片
|
||||
val pullupCard = view.findViewById<View>(R.id.pullup_card)
|
||||
val pullupImage = pullupCard.findViewById<ImageView>(R.id.exercise_image)
|
||||
val pullupName = pullupCard.findViewById<TextView>(R.id.exercise_name)
|
||||
pullupImage.setImageResource(R.drawable.pull_up)
|
||||
pullupName.text = "引体向上"
|
||||
pullupCard.setOnClickListener {
|
||||
startExerciseDetail("引体向上")
|
||||
}
|
||||
|
||||
// 设置俯卧撑卡片
|
||||
val pushupCard = view.findViewById<View>(R.id.pushup_card)
|
||||
val pushupImage = pushupCard.findViewById<ImageView>(R.id.exercise_image)
|
||||
val pushupName = pushupCard.findViewById<TextView>(R.id.exercise_name)
|
||||
pushupImage.setImageResource(R.drawable.push_up)
|
||||
pushupName.text = "俯卧撑"
|
||||
pushupCard.setOnClickListener {
|
||||
startExerciseDetail("俯卧撑")
|
||||
}
|
||||
}
|
||||
|
||||
private fun startExerciseDetail(exerciseName: String) {
|
||||
val intent = Intent(requireContext(), ExerciseDetailActivity::class.java)
|
||||
intent.putExtra("exercise_name", exerciseName)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
@ -1,23 +1,18 @@
|
||||
package org.tensorflow.lite.examples.poseestimation
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
|
||||
class OnboardingAdapter(activity: FragmentActivity, private val username: String?) : FragmentStateAdapter(activity) {
|
||||
class OnboardingAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
|
||||
override fun getItemCount(): Int = 3
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val fragment = when (position) {
|
||||
return when (position) {
|
||||
0 -> Onboarding1Fragment()
|
||||
1 -> Onboarding2Fragment()
|
||||
2 -> Onboarding3Fragment()
|
||||
else -> Onboarding1Fragment()
|
||||
}
|
||||
fragment.arguments = Bundle().apply {
|
||||
putString("username", username)
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
}
|
@ -1,286 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.delay
|
||||
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
|
||||
import org.tensorflow.lite.examples.poseestimation.data.VideoAnalysisResult
|
||||
import org.tensorflow.lite.examples.poseestimation.ml.MoveNet
|
||||
import org.tensorflow.lite.examples.poseestimation.ml.PoseDetector
|
||||
import org.tensorflow.lite.examples.poseestimation.ml.ModelType
|
||||
import org.tensorflow.lite.examples.poseestimation.data.Device
|
||||
import org.tensorflow.lite.examples.poseestimation.data.Person
|
||||
import org.tensorflow.lite.examples.poseestimation.data.KeyPoint
|
||||
import org.tensorflow.lite.examples.poseestimation.evaluator.DeadliftEvaluator
|
||||
import org.tensorflow.lite.examples.poseestimation.evaluator.SquatEvaluator
|
||||
import org.tensorflow.lite.examples.poseestimation.evaluator.ExerciseEvaluator
|
||||
import org.tensorflow.lite.examples.poseestimation.evaluator.PlankEvaluator
|
||||
import org.tensorflow.lite.examples.poseestimation.evaluator.PullUpEvaluator
|
||||
import org.tensorflow.lite.examples.poseestimation.evaluator.PushUpEvaluator
|
||||
|
||||
class VideoAnalysisActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var tvExerciseName: TextView
|
||||
private lateinit var btnSelectVideo: Button
|
||||
private lateinit var tvAnalysisStatus: TextView
|
||||
private lateinit var progressBarAnalysis: ProgressBar
|
||||
|
||||
private var currentExerciseType: String? = null
|
||||
private var currentUsername: String? = null
|
||||
|
||||
private lateinit var poseDetector: PoseDetector
|
||||
|
||||
// 用于存储复制到内部存储后的视频URI
|
||||
private var internalVideoUri: Uri? = null
|
||||
|
||||
// Launcher for selecting video from local storage
|
||||
private val selectVideoLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val videoUri: Uri? = result.data?.data
|
||||
videoUri?.let {
|
||||
// 将视频复制到内部存储
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val copiedUri = copyVideoToInternalStorage(it)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (copiedUri != null) {
|
||||
internalVideoUri = copiedUri
|
||||
android.util.Log.d("VideoAnalysisActivity", "视频已复制到内部存储: $internalVideoUri")
|
||||
tvAnalysisStatus.text = "已选择视频: ${getFileName(it)},开始分析..."
|
||||
tvAnalysisStatus.visibility = View.VISIBLE
|
||||
progressBarAnalysis.visibility = View.VISIBLE
|
||||
progressBarAnalysis.progress = 0
|
||||
startVideoAnalysis(internalVideoUri!!, currentExerciseType)
|
||||
} else {
|
||||
Toast.makeText(this@VideoAnalysisActivity, "视频复制失败,请重试", Toast.LENGTH_SHORT).show()
|
||||
tvAnalysisStatus.text = "视频复制失败"
|
||||
tvAnalysisStatus.visibility = View.VISIBLE
|
||||
progressBarAnalysis.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: run {
|
||||
Toast.makeText(this, "未选择视频", Toast.LENGTH_SHORT).show()
|
||||
tvAnalysisStatus.text = "未选择视频"
|
||||
tvAnalysisStatus.visibility = View.VISIBLE
|
||||
progressBarAnalysis.visibility = View.GONE
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "取消选择视频", Toast.LENGTH_SHORT).show()
|
||||
tvAnalysisStatus.text = "取消选择视频"
|
||||
tvAnalysisStatus.visibility = View.VISIBLE
|
||||
progressBarAnalysis.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_video_analysis)
|
||||
|
||||
// 获取当前登录用户名 (假设用SharedPreferences存储)
|
||||
val prefs = getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
|
||||
currentUsername = prefs.getString("username", null)?.trim()
|
||||
|
||||
if (currentUsername == null) {
|
||||
Toast.makeText(this, "未登录,请重新登录", Toast.LENGTH_SHORT).show()
|
||||
startActivity(Intent(this, LoginActivity::class.java))
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
// Get exercise name from intent
|
||||
currentExerciseType = intent.getStringExtra("current_exercise")
|
||||
|
||||
// Initialize views
|
||||
tvExerciseName = findViewById(R.id.tv_exercise_name)
|
||||
btnSelectVideo = findViewById(R.id.btn_select_video)
|
||||
tvAnalysisStatus = findViewById(R.id.tv_analysis_status)
|
||||
progressBarAnalysis = findViewById(R.id.progress_bar_analysis)
|
||||
|
||||
// Initialize PoseDetector (using MoveNet Lightning by default)
|
||||
poseDetector = MoveNet.create(this, Device.CPU, ModelType.Lightning)
|
||||
|
||||
// Set exercise name
|
||||
tvExerciseName.text = currentExerciseType ?: "未知动作"
|
||||
|
||||
// Set click listener for video selection button
|
||||
btnSelectVideo.setOnClickListener {
|
||||
openVideoPicker()
|
||||
}
|
||||
}
|
||||
|
||||
// Override onDestroy to close the poseDetector
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
poseDetector.close()
|
||||
}
|
||||
|
||||
private fun openVideoPicker() {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "video/*"
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
selectVideoLauncher.launch(Intent.createChooser(intent, "选择视频"))
|
||||
}
|
||||
|
||||
// Helper function to get file name from Uri
|
||||
private fun getFileName(uri: Uri): String {
|
||||
var result: String? = null
|
||||
if (uri.scheme == "content") {
|
||||
val cursor = contentResolver.query(uri, null, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (nameIndex != -1) {
|
||||
result = it.getString(nameIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.path
|
||||
val cut = result?.lastIndexOf('/')
|
||||
if (cut != -1) {
|
||||
result = result?.substring(cut!! + 1)
|
||||
}
|
||||
}
|
||||
return result ?: "未知文件"
|
||||
}
|
||||
|
||||
// 将视频复制到应用内部存储并返回新URI
|
||||
private fun copyVideoToInternalStorage(uri: Uri): Uri? {
|
||||
return try {
|
||||
val inputStream = contentResolver.openInputStream(uri)
|
||||
val outputFileName = "video_${System.currentTimeMillis()}.mp4"
|
||||
val outputFile = java.io.File(filesDir, outputFileName)
|
||||
val outputStream = outputFile.outputStream()
|
||||
|
||||
inputStream?.copyTo(outputStream)
|
||||
inputStream?.close()
|
||||
outputStream.close()
|
||||
android.util.Log.d("VideoAnalysisActivity", "视频已复制到: ${outputFile.absolutePath}")
|
||||
Uri.fromFile(outputFile)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("VideoAnalysisActivity", "复制视频到内部存储出错: ${e.message}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder for video analysis logic
|
||||
private fun startVideoAnalysis(videoUri: Uri, exerciseType: String?) {
|
||||
val db = AppDatabase.getDatabase(applicationContext)
|
||||
val videoAnalysisResultDao = db.videoAnalysisResultDao()
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
withContext(Dispatchers.Main) {
|
||||
tvAnalysisStatus.text = "正在初始化姿态识别模型..."
|
||||
btnSelectVideo.isEnabled = false // Disable button during analysis
|
||||
}
|
||||
|
||||
val retriever = MediaMetadataRetriever()
|
||||
retriever.setDataSource(applicationContext, videoUri)
|
||||
val durationMs = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0
|
||||
|
||||
// Process frames at a reasonable interval, e.g., 10 frames per second for analysis
|
||||
val frameRate = 10 // frames per second
|
||||
val frameIntervalUs = 1_000_000L / frameRate // interval in microseconds
|
||||
|
||||
var currentFrameTimeUs = 0L
|
||||
|
||||
// Initialize evaluator based on exercise type
|
||||
val evaluator: ExerciseEvaluator? = when (exerciseType) {
|
||||
"硬拉" -> DeadliftEvaluator()
|
||||
"深蹲" -> SquatEvaluator()
|
||||
"平板支撑" -> PlankEvaluator()
|
||||
"引体向上" -> PullUpEvaluator()
|
||||
"俯卧撑" -> PushUpEvaluator()
|
||||
// TODO: Add evaluators for other exercises
|
||||
else -> null
|
||||
}
|
||||
|
||||
while (currentFrameTimeUs <= durationMs * 1000) { // durationMs is in milliseconds, getFrameAtTime expects microseconds
|
||||
val bitmap = retriever.getFrameAtTime(currentFrameTimeUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
|
||||
|
||||
bitmap?.let { frameBitmap ->
|
||||
val persons = poseDetector.estimatePoses(frameBitmap)
|
||||
val person = persons.firstOrNull()
|
||||
|
||||
person?.let { p ->
|
||||
if (p.score > 0.3) { // Only consider poses with reasonable confidence
|
||||
// Pass keypoints to the evaluator
|
||||
evaluator?.evaluateFrame(p.keyPoints)
|
||||
}
|
||||
}
|
||||
frameBitmap.recycle() // Recycle bitmap to free memory
|
||||
}
|
||||
|
||||
val progress = ((currentFrameTimeUs.toFloat() / (durationMs * 1000)) * 100).toInt()
|
||||
withContext(Dispatchers.Main) {
|
||||
progressBarAnalysis.progress = progress
|
||||
tvAnalysisStatus.text = "正在分析视频帧: ${currentFrameTimeUs / 1000}ms / ${durationMs}ms"
|
||||
}
|
||||
|
||||
currentFrameTimeUs += frameIntervalUs
|
||||
}
|
||||
retriever.release()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
tvAnalysisStatus.text = "姿态识别完成,正在评估..."
|
||||
}
|
||||
|
||||
// --- Pose Evaluation and Scoring ---
|
||||
|
||||
val finalScore: Float
|
||||
val evaluation: String
|
||||
|
||||
if (evaluator != null) {
|
||||
finalScore = evaluator.getFinalScore()
|
||||
evaluation = evaluator.getFinalEvaluation(finalScore)
|
||||
} else {
|
||||
finalScore = 0f
|
||||
evaluation = "暂不支持该动作的详细评估。"
|
||||
}
|
||||
|
||||
// Store result in database
|
||||
val result = VideoAnalysisResult(
|
||||
username = currentUsername ?: "未知用户",
|
||||
exerciseType = exerciseType ?: "未知运动",
|
||||
videoUri = videoUri.toString(), // 使用已经复制到内部存储的URI
|
||||
score = finalScore,
|
||||
evaluation = evaluation,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
videoAnalysisResultDao.insertVideoAnalysisResult(result)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
tvAnalysisStatus.text = "分析完成!正在显示结果..."
|
||||
progressBarAnalysis.visibility = View.GONE
|
||||
btnSelectVideo.isEnabled = true // Re-enable button
|
||||
|
||||
val intent = Intent(this@VideoAnalysisActivity, VideoAnalysisResultActivity::class.java).apply {
|
||||
putExtra("exercise_name", currentExerciseType)
|
||||
putExtra("score", finalScore)
|
||||
putExtra("evaluation", evaluation)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish() // Finish this activity so user can't go back to it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import androidx.core.content.FileProvider
|
||||
import org.tensorflow.lite.examples.poseestimation.data.VideoAnalysisResult
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class VideoAnalysisAdapter(private val results: List<VideoAnalysisResult>) : RecyclerView.Adapter<VideoAnalysisAdapter.VideoAnalysisViewHolder>() {
|
||||
|
||||
class VideoAnalysisViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val tvExerciseType: TextView = itemView.findViewById(R.id.tv_exercise_type)
|
||||
val tvScore: TextView = itemView.findViewById(R.id.tv_score)
|
||||
val tvEvaluation: TextView = itemView.findViewById(R.id.tv_evaluation)
|
||||
val videoThumbnail: ImageView = itemView.findViewById(R.id.video_thumbnail)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoAnalysisViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_video_analysis_card, parent, false)
|
||||
return VideoAnalysisViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VideoAnalysisViewHolder, position: Int) {
|
||||
val result = results[position]
|
||||
holder.tvExerciseType.text = "运动类型:${result.exerciseType}"
|
||||
holder.tvScore.text = "${result.score.toInt()} 分"
|
||||
holder.tvEvaluation.text = "评价:${result.evaluation}"
|
||||
|
||||
Glide.with(holder.itemView.context)
|
||||
.load(Uri.parse(result.videoUri))
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.error(R.drawable.placeholder_image)
|
||||
.into(holder.videoThumbnail)
|
||||
|
||||
android.util.Log.d("VideoAnalysisAdapter", "Loading video thumbnail for URI: ${result.videoUri}")
|
||||
|
||||
holder.videoThumbnail.setOnClickListener {
|
||||
try {
|
||||
val videoFile = File(Uri.parse(result.videoUri).path)
|
||||
val context = holder.itemView.context
|
||||
|
||||
val contentUri: Uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
videoFile
|
||||
)
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(contentUri, "video/*")
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("VideoAnalysisAdapter", "无法播放视频: ${result.videoUri}, 错误: ${e.message}", e)
|
||||
Toast.makeText(holder.itemView.context, "无法播放视频,请检查文件或权限", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = results.size
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class VideoAnalysisResultActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_video_analysis_result)
|
||||
|
||||
val tvExerciseName = findViewById<TextView>(R.id.tv_result_exercise_name)
|
||||
val tvScore = findViewById<TextView>(R.id.tv_result_score)
|
||||
val tvEvaluation = findViewById<TextView>(R.id.tv_result_evaluation)
|
||||
|
||||
// 从 Intent 中获取数据
|
||||
val exerciseName = intent.getStringExtra("exercise_name")
|
||||
val score = intent.getFloatExtra("score", 0f)
|
||||
val evaluation = intent.getStringExtra("evaluation")
|
||||
|
||||
// 设置数据显示
|
||||
tvExerciseName.text = exerciseName ?: "未知动作"
|
||||
tvScore.text = String.format("%.1f", score) // 格式化得分,保留一位小数
|
||||
tvEvaluation.text = evaluation ?: "暂无评价"
|
||||
|
||||
// 设置标题 (可选)
|
||||
supportActionBar?.title = "分析结果"
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.auth;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
public class DatabaseHelper extends SQLiteOpenHelper{
|
||||
|
||||
// 数据库信息
|
||||
private static final String DATABASE_NAME = "user_database";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
|
||||
// 表名和列名
|
||||
public static final String TABLE_USERS = "users";
|
||||
public static final String COLUMN_ID = "_id";
|
||||
public static final String COLUMN_USERNAME = "username";
|
||||
public static final String COLUMN_PASSWORD_HASH = "password_hash";
|
||||
public static final String COLUMN_AGE = "age";
|
||||
public static final String COLUMN_WEIGHT = "weight";
|
||||
public static final String COLUMN_GENDER = "gender";
|
||||
public static final String COLUMN_HEIGHT = "height";
|
||||
|
||||
// 创建表的SQL语句
|
||||
private static final String CREATE_TABLE_USERS =
|
||||
"CREATE TABLE " + TABLE_USERS + "("
|
||||
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
+ COLUMN_USERNAME + " TEXT UNIQUE NOT NULL,"
|
||||
+ COLUMN_PASSWORD_HASH + " TEXT NOT NULL"
|
||||
// + COLUMN_AGE + " INTEGER,"
|
||||
// + COLUMN_WEIGHT + " REAL,"
|
||||
// + COLUMN_GENDER + " TEXT,"
|
||||
// + COLUMN_HEIGHT + " REAL"
|
||||
+ ")";
|
||||
|
||||
// 构造函数
|
||||
public DatabaseHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_TABLE_USERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.auth;
|
||||
|
||||
import java.io.Serializable;
|
||||
public class User implements Serializable{
|
||||
private long id;
|
||||
private String username;
|
||||
private String passwordHash;
|
||||
// private int age;
|
||||
// private float weight;
|
||||
// private String gender;
|
||||
// private float height;
|
||||
|
||||
// 构造函数、Getter和Setter方法
|
||||
public User() {}
|
||||
|
||||
public User(String username, String passwordHash) {
|
||||
this.username = username;
|
||||
this.passwordHash = passwordHash;
|
||||
// this.age = age;
|
||||
// this.weight = weight;
|
||||
// this.gender = gender;
|
||||
// this.height = height;
|
||||
}
|
||||
|
||||
// Getter和Setter方法
|
||||
public long getId() { return id; }
|
||||
public void setId(long id) { this.id = id; }
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getPasswordHash() { return passwordHash; }
|
||||
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
|
||||
|
||||
// public int getAge() { return age; }
|
||||
// public void setAge(int age) { this.age = age; }
|
||||
//
|
||||
// public float getWeight() { return weight; }
|
||||
// public void setWeight(float weight) { this.weight = weight; }
|
||||
//
|
||||
// public String getGender() { return gender; }
|
||||
// public void setGender(String gender) { this.gender = gender; }
|
||||
//
|
||||
// public float getHeight() { return height; }
|
||||
// public void setHeight(float height) { this.height = height; }
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.auth;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import org.tensorflow.lite.examples.poseestimation.auth.User;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
public class UserDao {
|
||||
private DatabaseHelper databaseHelper;
|
||||
|
||||
public UserDao(Context context) {
|
||||
databaseHelper = new DatabaseHelper(context);
|
||||
}
|
||||
|
||||
// 添加用户
|
||||
public long addUser(User user) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(DatabaseHelper.COLUMN_USERNAME, user.getUsername());
|
||||
values.put(DatabaseHelper.COLUMN_PASSWORD_HASH, user.getPasswordHash());
|
||||
// values.put(DatabaseHelper.COLUMN_AGE, user.getAge());
|
||||
// values.put(DatabaseHelper.COLUMN_WEIGHT, user.getWeight());
|
||||
// values.put(DatabaseHelper.COLUMN_GENDER, user.getGender());
|
||||
// values.put(DatabaseHelper.COLUMN_HEIGHT, user.getHeight());
|
||||
|
||||
long id = db.insert(DatabaseHelper.TABLE_USERS, null, values);
|
||||
db.close();
|
||||
return id;
|
||||
}
|
||||
|
||||
// 根据用户名获取用户
|
||||
public User getUserByUsername(String username) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
|
||||
String[] projection = {
|
||||
DatabaseHelper.COLUMN_ID,
|
||||
DatabaseHelper.COLUMN_USERNAME,
|
||||
DatabaseHelper.COLUMN_PASSWORD_HASH,
|
||||
// DatabaseHelper.COLUMN_AGE,
|
||||
// DatabaseHelper.COLUMN_WEIGHT,
|
||||
// DatabaseHelper.COLUMN_GENDER,
|
||||
// DatabaseHelper.COLUMN_HEIGHT
|
||||
};
|
||||
|
||||
String selection = DatabaseHelper.COLUMN_USERNAME + " = ?";
|
||||
String[] selectionArgs = { username };
|
||||
|
||||
Cursor cursor = db.query(
|
||||
DatabaseHelper.TABLE_USERS,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
User user = null;
|
||||
if (cursor.moveToFirst()) {
|
||||
user = new User();
|
||||
user.setId(cursor.getLong(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID)));
|
||||
user.setUsername(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_USERNAME)));
|
||||
user.setPasswordHash(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_PASSWORD_HASH)));
|
||||
// user.setAge(cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_AGE)));
|
||||
// user.setWeight(cursor.getFloat(cursor.getColumnIndex(DatabaseHelper.COLUMN_WEIGHT)));
|
||||
// user.setGender(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_GENDER)));
|
||||
// user.setHeight(cursor.getFloat(cursor.getColumnIndex(DatabaseHelper.COLUMN_HEIGHT)));
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
db.close();
|
||||
return user;
|
||||
}
|
||||
|
||||
// 检查用户名是否存在
|
||||
public boolean checkUsernameExists(String username) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
|
||||
String[] projection = { DatabaseHelper.COLUMN_USERNAME };
|
||||
String selection = DatabaseHelper.COLUMN_USERNAME + " = ?";
|
||||
String[] selectionArgs = { username };
|
||||
|
||||
Cursor cursor = db.query(
|
||||
DatabaseHelper.TABLE_USERS,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
boolean exists = cursor.moveToFirst();
|
||||
cursor.close();
|
||||
db.close();
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
// 获取所有用户
|
||||
public List<User> getAllUsers() {
|
||||
List<User> userList = new ArrayList<>();
|
||||
String selectQuery = "SELECT * FROM " + DatabaseHelper.TABLE_USERS;
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = db.rawQuery(selectQuery, null);
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
User user = new User();
|
||||
user.setId(cursor.getLong(cursor.getColumnIndex(DatabaseHelper.COLUMN_ID)));
|
||||
user.setUsername(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_USERNAME)));
|
||||
user.setPasswordHash(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_PASSWORD_HASH)));
|
||||
// user.setAge(cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_AGE)));
|
||||
// user.setWeight(cursor.getFloat(cursor.getColumnIndex(DatabaseHelper.COLUMN_WEIGHT)));
|
||||
// user.setGender(cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_GENDER)));
|
||||
// user.setHeight(cursor.getFloat(cursor.getColumnIndex(DatabaseHelper.COLUMN_HEIGHT)));
|
||||
|
||||
userList.add(user);
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
db.close();
|
||||
return userList;
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
@Database(entities = [User::class, UserProfile::class, VideoAnalysisResult::class], version = 6, exportSchema = false)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun userDao(): UserDao
|
||||
abstract fun userProfileDao(): UserProfileDao
|
||||
abstract fun videoAnalysisResultDao(): VideoAnalysisResultDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: AppDatabase? = null
|
||||
|
||||
private val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS `video_analysis_results` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `username` TEXT NOT NULL, `exerciseType` TEXT NOT NULL, `videoUri` TEXT NOT NULL, `score` REAL NOT NULL, `evaluation` TEXT NOT NULL, `timestamp` INTEGER NOT NULL)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val MIGRATION_5_6 = object : Migration(5, 6) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE user_profiles ADD COLUMN avatarUri TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
fun getDatabase(context: Context): AppDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
AppDatabase::class.java,
|
||||
"app_database"
|
||||
)
|
||||
.addMigrations(MIGRATION_4_5, MIGRATION_5_6)
|
||||
.build()
|
||||
INSTANCE = instance
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.data
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "users")
|
||||
data class User(
|
||||
@PrimaryKey
|
||||
val username: String,
|
||||
val password: String,
|
||||
val signature: String? = null,
|
||||
val avatarUri: String? = null
|
||||
)
|
@ -1,27 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface UserDao {
|
||||
@Insert
|
||||
fun insertUser(user: User)
|
||||
|
||||
@Query("SELECT * FROM users WHERE username = :username AND password = :password")
|
||||
fun getUser(username: String, password: String): Flow<User?>
|
||||
|
||||
@Query("SELECT * FROM users WHERE username = :username")
|
||||
fun getUserByUsername(username: String): Flow<User?>
|
||||
|
||||
@Query("UPDATE users SET signature = :signature WHERE username = :username")
|
||||
fun updateSignature(username: String, signature: String)
|
||||
|
||||
@Query("SELECT signature FROM users WHERE username = :username")
|
||||
fun getSignature(username: String): Flow<String?>
|
||||
|
||||
@Query("UPDATE users SET avatarUri = :avatarUri WHERE username = :username")
|
||||
fun updateAvatarUri(username: String, avatarUri: String?)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface UserProfileDao {
|
||||
@Insert
|
||||
fun insertUserProfile(profile: UserProfile): Long
|
||||
|
||||
@Update
|
||||
fun updateUserProfile(profile: UserProfile): Int
|
||||
|
||||
@Query("SELECT * FROM user_profiles WHERE username = :username")
|
||||
fun getUserProfileByUsername(username: String): Flow<UserProfile?>
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.data
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "video_analysis_results")
|
||||
data class VideoAnalysisResult(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val username: String,
|
||||
val exerciseType: String,
|
||||
val videoUri: String,
|
||||
val score: Float, // Simplified score for now
|
||||
val evaluation: String,
|
||||
val timestamp: Long // Timestamp for when the analysis was performed
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface VideoAnalysisResultDao {
|
||||
@Insert
|
||||
fun insertVideoAnalysisResult(result: VideoAnalysisResult): Long
|
||||
|
||||
@Query("SELECT * FROM video_analysis_results WHERE username = :username ORDER BY timestamp DESC")
|
||||
fun getVideoAnalysisResultsByUsername(username: String): Flow<List<VideoAnalysisResult>>
|
||||
|
||||
// Optional: Get results for a specific exercise type
|
||||
@Query("SELECT * FROM video_analysis_results WHERE username = :username AND exerciseType = :exerciseType ORDER BY timestamp DESC")
|
||||
fun getVideoAnalysisResultsByUsernameAndExerciseType(username: String, exerciseType: String): Flow<List<VideoAnalysisResult>>
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.evaluator
|
||||
|
||||
import org.tensorflow.lite.examples.poseestimation.data.KeyPoint
|
||||
|
||||
interface ExerciseEvaluator {
|
||||
fun evaluateFrame(keyPoints: List<KeyPoint>)
|
||||
fun getFinalScore(): Float
|
||||
fun getFinalEvaluation(finalScore: Float): String
|
||||
}
|
@ -1,237 +0,0 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.evaluator
|
||||
|
||||
import org.tensorflow.lite.examples.poseestimation.data.KeyPoint
|
||||
import org.tensorflow.lite.examples.poseestimation.data.BodyPart
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
// 定义俯卧撑动作的阶段
|
||||
enum class PushUpState {
|
||||
START, // 起始姿态(手臂伸直)
|
||||
DESCENT, // 下降过程
|
||||
BOTTOM, // 底部姿态(胸部接近地面)
|
||||
ASCENT, // 上推过程
|
||||
LOCKOUT // 恢复到起始姿态
|
||||
}
|
||||
|
||||
class PushUpEvaluator : ExerciseEvaluator {
|
||||
|
||||
private var currentState: PushUpState = PushUpState.START
|
||||
private var totalScore: Float = 0f
|
||||
private var frameCount: Int = 0
|
||||
private var evaluationMessages: MutableList<EvaluationMessage> = mutableListOf()
|
||||
private var repCount: Int = 0
|
||||
private var isRepCounting: Boolean = false
|
||||
|
||||
// 俯卧撑关键角度阈值 (示例值,需要根据实际模型和标准进行调整)
|
||||
private val ELBOW_ANGLE_START_MIN = 170f // 起始姿态(手臂伸直,放宽要求)
|
||||
private val ELBOW_ANGLE_BOTTOM_MAX = 100f // 底部姿态(手肘弯曲程度,放宽要求)
|
||||
private val HIP_SHOULDER_ANKLE_MIN = 160f // 身体直线(髋-肩-踝)
|
||||
private val HIP_SHOULDER_ANKLE_MAX = 185f // 允许略微的弧度
|
||||
|
||||
// Helper function to calculate angle between three keypoints (A-B-C)
|
||||
// B is the vertex of the angle
|
||||
private fun calculateAngle(A: KeyPoint, B: KeyPoint, C: KeyPoint): Float {
|
||||
return B.abttPoints(A, B, C)
|
||||
}
|
||||
|
||||
override fun evaluateFrame(keyPoints: List<KeyPoint>) {
|
||||
if (keyPoints.isEmpty()) {
|
||||
evaluationMessages.add(EvaluationMessage("未检测到关键点,无法评估。", true))
|
||||
return
|
||||
}
|
||||
|
||||
frameCount++
|
||||
|
||||
// 获取俯卧撑所需关键点
|
||||
val leftShoulder = keyPoints.find { it.bodyPart == BodyPart.LEFT_SHOULDER }
|
||||
val rightShoulder = keyPoints.find { it.bodyPart == BodyPart.RIGHT_SHOULDER }
|
||||
val leftElbow = keyPoints.find { it.bodyPart == BodyPart.LEFT_ELBOW }
|
||||
val rightElbow = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ELBOW }
|
||||
val leftWrist = keyPoints.find { it.bodyPart == BodyPart.LEFT_WRIST }
|
||||
val rightWrist = keyPoints.find { it.bodyPart == BodyPart.RIGHT_WRIST }
|
||||
val leftHip = keyPoints.find { it.bodyPart == BodyPart.LEFT_HIP }
|
||||
val rightHip = keyPoints.find { it.bodyPart == BodyPart.RIGHT_HIP }
|
||||
val leftAnkle = keyPoints.find { it.bodyPart == BodyPart.LEFT_ANKLE }
|
||||
val rightAnkle = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ANKLE }
|
||||
|
||||
// 确保所有关键点都存在
|
||||
if (leftShoulder == null || rightShoulder == null || leftElbow == null || rightElbow == null ||
|
||||
leftWrist == null || rightWrist == null || leftHip == null || rightHip == null ||
|
||||
leftAnkle == null || rightAnkle == null) {
|
||||
evaluationMessages.add(EvaluationMessage("关键点缺失,评估可能不准确。", true))
|
||||
return
|
||||
}
|
||||
|
||||
// 计算左右平均关键点,提高稳定性
|
||||
val midShoulder = KeyPoint(
|
||||
BodyPart.NOSE,
|
||||
android.graphics.PointF(
|
||||
(leftShoulder.coordinate.x + rightShoulder.coordinate.x) / 2,
|
||||
(leftShoulder.coordinate.y + rightShoulder.coordinate.y) / 2
|
||||
),
|
||||
(leftShoulder.score + rightShoulder.score) / 2
|
||||
)
|
||||
val midElbow = KeyPoint(
|
||||
BodyPart.NOSE,
|
||||
android.graphics.PointF(
|
||||
(leftElbow.coordinate.x + rightElbow.coordinate.x) / 2,
|
||||
(leftElbow.coordinate.y + rightElbow.coordinate.y) / 2
|
||||
),
|
||||
(leftElbow.score + rightElbow.score) / 2
|
||||
)
|
||||
val midWrist = KeyPoint(
|
||||
BodyPart.NOSE,
|
||||
android.graphics.PointF(
|
||||
(leftWrist.coordinate.x + rightWrist.coordinate.x) / 2,
|
||||
(leftWrist.coordinate.y + rightWrist.coordinate.y) / 2
|
||||
),
|
||||
(leftWrist.score + rightWrist.score) / 2
|
||||
)
|
||||
val midHip = KeyPoint(
|
||||
BodyPart.NOSE,
|
||||
android.graphics.PointF(
|
||||
(leftHip.coordinate.x + rightHip.coordinate.x) / 2,
|
||||
(leftHip.coordinate.y + rightHip.coordinate.y) / 2
|
||||
),
|
||||
(leftHip.score + rightHip.score) / 2
|
||||
)
|
||||
val midAnkle = KeyPoint(
|
||||
BodyPart.NOSE,
|
||||
android.graphics.PointF(
|
||||
(leftAnkle.coordinate.x + rightAnkle.coordinate.x) / 2,
|
||||
(leftAnkle.coordinate.y + rightAnkle.coordinate.y) / 2
|
||||
),
|
||||
(leftAnkle.score + rightAnkle.score) / 2
|
||||
)
|
||||
|
||||
// 计算核心角度
|
||||
val elbowAngle = calculateAngle(midShoulder, midElbow, midWrist) // 肩-肘-腕
|
||||
val bodyLineAngle = calculateAngle(midShoulder, midHip, midAnkle) // 肩-髋-踝
|
||||
|
||||
var frameScore = 0f
|
||||
var frameEvaluation = ""
|
||||
var isBodyStraight = true
|
||||
|
||||
// 检查身体是否呈直线
|
||||
if (bodyLineAngle !in HIP_SHOULDER_ANKLE_MIN..HIP_SHOULDER_ANKLE_MAX) {
|
||||
isBodyStraight = false
|
||||
if (midHip.coordinate.y < midShoulder.coordinate.y) { // 臀部过高
|
||||
evaluationMessages.add(EvaluationMessage("臀部抬得太高了,尝试放低臀部,保持身体呈一条直线。", true))
|
||||
} else if (midHip.coordinate.y > midAnkle.coordinate.y) { // 臀部下沉
|
||||
evaluationMessages.add(EvaluationMessage("臀部下沉了,收紧核心,将臀部向上抬起,保持身体的平直。", true))
|
||||
} else {
|
||||
evaluationMessages.add(EvaluationMessage("身体不够平直,请调整髋部位置。", true))
|
||||
}
|
||||
}
|
||||
|
||||
when (currentState) {
|
||||
PushUpState.START -> {
|
||||
// 评估起始姿态 (手臂伸直,身体呈直线)
|
||||
if (elbowAngle > ELBOW_ANGLE_START_MIN - 10 && isBodyStraight) { // 稍微放宽手臂伸直要求
|
||||
frameEvaluation = "起始姿态良好,手臂基本伸直,身体呈一条直线。"
|
||||
isRepCounting = false
|
||||
currentState = PushUpState.DESCENT
|
||||
frameScore = 200f // 提高分数
|
||||
} else {
|
||||
if (elbowAngle < ELBOW_ANGLE_START_MIN - 10) evaluationMessages.add(EvaluationMessage("手臂未完全伸直,请确保回到起始位置。", true))
|
||||
if (!isBodyStraight) evaluationMessages.add(EvaluationMessage("身体未保持一条直线,请调整核心,不要塌腰或撅臀。", true))
|
||||
frameEvaluation = "请调整至正确起始姿态。"
|
||||
}
|
||||
}
|
||||
PushUpState.DESCENT -> {
|
||||
// 评估下降过程
|
||||
if (elbowAngle < ELBOW_ANGLE_START_MIN - 15) { // 手肘开始弯曲
|
||||
if (isBodyStraight) {
|
||||
frameEvaluation = "下降得很稳,保持身体平直。"
|
||||
currentState = PushUpState.BOTTOM
|
||||
frameScore = 400f // 提高分数
|
||||
} else {
|
||||
evaluationMessages.add(EvaluationMessage("下降时身体不够平直,请调整核心,保持身体直线。", true))
|
||||
frameEvaluation = "下降中,注意保持身体直线。"
|
||||
}
|
||||
} else {
|
||||
frameEvaluation = "继续下降,感受胸部拉伸。"
|
||||
}
|
||||
}
|
||||
PushUpState.BOTTOM -> {
|
||||
// 评估底部姿态 (胸部接近地面,手肘弯曲到位,身体呈直线)
|
||||
if (elbowAngle < ELBOW_ANGLE_BOTTOM_MAX + 10 && isBodyStraight) { // 放宽手肘弯曲
|
||||
frameEvaluation = "底部姿态完美,胸部接近地面!"
|
||||
currentState = PushUpState.ASCENT
|
||||
frameScore = 600f // 提高分数
|
||||
} else {
|
||||
if (elbowAngle > ELBOW_ANGLE_BOTTOM_MAX + 10) evaluationMessages.add(EvaluationMessage("下降不够深,请尝试让胸部更接近地面。", true))
|
||||
if (!isBodyStraight) evaluationMessages.add(EvaluationMessage("底部姿态身体不直,请调整。", true))
|
||||
frameEvaluation = "底部姿态可改进。"
|
||||
}
|
||||
}
|
||||
PushUpState.ASCENT -> {
|
||||
// 评估上推过程
|
||||
if (elbowAngle > ELBOW_ANGLE_BOTTOM_MAX + 15) { // 手肘开始伸直
|
||||
if (isBodyStraight) {
|
||||
frameEvaluation = "上推有力,身体保持平直。"
|
||||
currentState = PushUpState.LOCKOUT
|
||||
frameScore = 400f // 提高分数
|
||||
} else {
|
||||
evaluationMessages.add(EvaluationMessage("上推时身体不够平直,请调整核心,保持身体直线。", true))
|
||||
frameEvaluation = "上推中,注意保持身体直线。"
|
||||
}
|
||||
} else {
|
||||
frameEvaluation = "继续上推,感受胸部和手臂发力。"
|
||||
}
|
||||
}
|
||||
PushUpState.LOCKOUT -> {
|
||||
// 评估完全推起(锁定)姿态
|
||||
if (elbowAngle > ELBOW_ANGLE_START_MIN - 15 && isBodyStraight) { // 稍微放宽手臂伸直要求
|
||||
frameEvaluation = "完美完成一次俯卧撑!手臂基本伸直,身体呈一条直线。"
|
||||
if (!isRepCounting) {
|
||||
repCount++
|
||||
isRepCounting = true
|
||||
evaluationMessages.add(EvaluationMessage("恭喜你,又完成了一次俯卧撑!目前累计完成了 $repCount 次,继续保持!"))
|
||||
}
|
||||
currentState = PushUpState.START // 完成一次后回到起始状态,准备下一次
|
||||
frameScore = 200f // 提高分数
|
||||
} else {
|
||||
if (elbowAngle < ELBOW_ANGLE_START_MIN - 15) evaluationMessages.add(EvaluationMessage("手臂未完全伸直,请确保回到起始位置。", true))
|
||||
if (!isBodyStraight) evaluationMessages.add(EvaluationMessage("锁定姿态身体不直,请调整。", true))
|
||||
frameEvaluation = "请完成锁定:手臂基本伸直,身体保持平直。"
|
||||
}
|
||||
}
|
||||
}
|
||||
totalScore += frameScore
|
||||
evaluationMessages.add(EvaluationMessage(frameEvaluation))
|
||||
}
|
||||
|
||||
override fun getFinalScore(): Float {
|
||||
val rawScore = if (frameCount > 0) totalScore / frameCount else 0f
|
||||
val roundedScore = (rawScore / 10.0f).roundToInt() * 10.0f
|
||||
return roundedScore.coerceIn(0f, 100f)
|
||||
}
|
||||
|
||||
override fun getFinalEvaluation(finalScore: Float): String {
|
||||
val uniqueMessages = evaluationMessages.map { it.message }.distinct().joinToString("\n")
|
||||
val errors = evaluationMessages.filter { it.isError }.map { it.message }.distinct()
|
||||
|
||||
val overallEvaluationBuilder = StringBuilder()
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
when (finalScore.toInt()) {
|
||||
in 90..100 -> overallEvaluationBuilder.append("太棒了!你的俯卧撑动作几乎完美无瑕,姿态标准,力量十足!继续保持!")
|
||||
in 70..89 -> overallEvaluationBuilder.append("非常不错的俯卧撑!动作基本流畅,姿态也比较到位,再稍加注意细节就能更完美!")
|
||||
in 50..69 -> overallEvaluationBuilder.append("俯卧撑动作有进步空间哦!虽然有些地方做得不错,但还需要多练习,让姿态更稳定、发力更集中。")
|
||||
in 30..49 -> overallEvaluationBuilder.append("本次俯卧撑需要更多练习。动作中存在一些明显的姿态问题,这会影响训练效果和安全性。")
|
||||
else -> overallEvaluationBuilder.append("俯卧撑动作仍需大量改进。请务必仔细对照标准,从基础开始练习,避免受伤。")
|
||||
}
|
||||
} else {
|
||||
overallEvaluationBuilder.append("本次俯卧撑分析完成!发现了一些可以改进的地方:\n")
|
||||
overallEvaluationBuilder.append(errors.joinToString("\n"))
|
||||
overallEvaluationBuilder.append("\n总次数:$repCount")
|
||||
}
|
||||
|
||||
overallEvaluationBuilder.append("\n\n以下是本次训练的详细分析过程,希望能帮助你更好地理解和改进:\n")
|
||||
overallEvaluationBuilder.append(uniqueMessages)
|
||||
|
||||
return overallEvaluationBuilder.toString()
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.tensorflow.lite.examples.poseestimation.utils;
|
||||
|
||||
import android.util.Base64;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
public class PasswordUtils {
|
||||
private static final String ALGORITHM = "SHA-256";
|
||||
private static final String SECRET_KEY = "YourSecretKey123"; // 实际应用中应存储在安全位置
|
||||
|
||||
// 生成密码哈希
|
||||
public static String hashPassword(String password) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance(ALGORITHM);
|
||||
byte[] encodedHash = digest.digest(password.getBytes());
|
||||
|
||||
StringBuilder hexString = new StringBuilder(2 * encodedHash.length);
|
||||
for (byte b : encodedHash) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
public static boolean verifyPassword(String password, String hashedPassword) {
|
||||
String hashedInput = hashPassword(password);
|
||||
return hashedInput != null && hashedInput.equals(hashedPassword);
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#222" />
|
||||
<stroke android:width="4dp" android:color="#FFA500" />
|
||||
</shape>
|
Before Width: | Height: | Size: 356 B |
Before Width: | Height: | Size: 231 B |
Before Width: | Height: | Size: 204 B |
Before Width: | Height: | Size: 391 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 251 B |
Before Width: | Height: | Size: 252 B |
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#555555"/>
|
||||
<size android:width="100dp" android:height="100dp"/>
|
||||
</shape>
|
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 774 KiB |
Before Width: | Height: | Size: 753 KiB |
Before Width: | Height: | Size: 260 B |
Before Width: | Height: | Size: 270 B |
@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#1C1C1E"
|
||||
tools:context=".ExerciseDetailActivity">
|
||||
|
||||
<!-- 顶部的图片区域 -->
|
||||
<ImageView
|
||||
android:id="@+id/exercise_detail_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="250dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder_image" /> <!-- 这里的图片可以根据具体动作设置 -->
|
||||
|
||||
<!-- 返回按钮 -->
|
||||
<ImageButton
|
||||
android:id="@+id/back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_back" /> <!-- 需要一个返回图标资源 -->
|
||||
|
||||
<!-- 动作名称 -->
|
||||
<TextView
|
||||
android:id="@+id/exercise_detail_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/exercise_detail_image"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold"
|
||||
android:text="动作名称" />
|
||||
|
||||
<!-- 动作描述/注意事项 -->
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/exercise_detail_name"
|
||||
android:layout_above="@id/start_training_button"
|
||||
android:layout_marginTop="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exercise_detail_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#CCCCCC"
|
||||
android:textSize="18sp"
|
||||
android:text="这里是动作的详细描述和注意事项..." />
|
||||
</ScrollView>
|
||||
|
||||
<!-- 开始训练按钮 -->
|
||||
<Button
|
||||
android:id="@+id/start_training_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:backgroundTint="#A020F0"
|
||||
android:text="开始训练"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
</RelativeLayout>
|
@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".MainTabActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="#222222"
|
||||
app:titleTextColor="#FFFFFF">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toolbar_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="形动力"
|
||||
android:textColor="#FFBB86FC"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="56dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_nav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="horizontal"
|
||||
android:background="#222222">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nav_home"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/home1"
|
||||
android:scaleType="centerInside" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nav_data"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/data2"
|
||||
android:scaleType="centerInside" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nav_setting"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/setting2"
|
||||
android:scaleType="centerInside" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#1C1C1E"
|
||||
tools:context=".VideoAnalysisActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_exercise_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="动作名称"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_select_video"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="选择本地视频"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#A020F0"
|
||||
android:textSize="18sp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_analysis_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/btn_select_video"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="24dp"
|
||||
android:textColor="#CCCCCC"
|
||||
android:textSize="16sp"
|
||||
android:text="请选择视频进行分析..."
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar_analysis"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/tv_analysis_status"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:progressTint="#A020F0"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#1C1C1E"
|
||||
tools:context=".VideoAnalysisResultActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_result_exercise_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="动作名称"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_result_score_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="最终得分: "
|
||||
android:textColor="#CCCCCC"
|
||||
android:textSize="18sp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_result_score"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="N/A"
|
||||
android:textColor="#A020F0"
|
||||
android:textSize="36sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="24dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_result_evaluation_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="详细评价: "
|
||||
android:textColor="#CCCCCC"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_result_evaluation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="分析中..."
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:lineSpacingExtra="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#1C1C1E"
|
||||
tools:context=".DataFragment">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view_video_analysis_results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false"
|
||||
android:background="#1C1C1E"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#1C1C1E">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- 硬拉卡片 -->
|
||||
<include
|
||||
android:id="@+id/deadlift_card"
|
||||
layout="@layout/item_exercise_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- 深蹲卡片 -->
|
||||
<include
|
||||
android:id="@+id/squat_card"
|
||||
layout="@layout/item_exercise_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- 平板支撑卡片 -->
|
||||
<include
|
||||
android:id="@+id/plank_card"
|
||||
layout="@layout/item_exercise_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- 引体向上卡片 -->
|
||||
<include
|
||||
android:id="@+id/pullup_card"
|
||||
layout="@layout/item_exercise_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- 俯卧撑卡片 -->
|
||||
<include
|
||||
android:id="@+id/pushup_card"
|
||||
layout="@layout/item_exercise_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="#333333">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/exercise_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exercise_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/exercise_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textColor="#AAAAAA"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp"
|
||||
app:cardBackgroundColor="#222222">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/video_thumbnail"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder_image"
|
||||
android:background="#000000"
|
||||
android:contentDescription="视频缩略图"
|
||||
android:clickable="true"
|
||||
android:focusable="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_exercise_type"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="运动类型:硬拉"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_evaluation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:text="评价:动作完成度很高,但下放深度不足。请注意控制节奏。"
|
||||
android:textColor="#AAAAAA"
|
||||
android:textSize="13sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_score"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="85 分"
|
||||
android:textColor="#FFBB86FC"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center_vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="my_videos" path="."/>
|
||||
</paths>
|