田子悦 2 days ago
parent 87c22f77e6
commit ad929e15b1

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

@ -0,0 +1,703 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceStreaming">
<option name="deviceSelectionList">
<list>
<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="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="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="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>

@ -4,15 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="MovenetLightningTest">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="MovenetThunderTest">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="MovenetMultiPoseTest">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-06-03T12:40:50.122129Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=YPL0223227003230" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

@ -1,10 +1,11 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
compileSdkVersion 30
compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
@ -50,6 +51,12 @@ dependencies {
implementation 'org.tensorflow:tensorflow-lite-gpu:2.5.0'
implementation 'org.tensorflow:tensorflow-lite-support:0.3.0'
// Room
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "com.google.truth:truth:1.1.3"

@ -19,6 +19,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainTabActivity" />
<activity android:name=".MainActivity" />
<activity android:name=".GenderSelectionActivity" />
<activity android:name=".AgeSelectionActivity" />
@ -36,6 +37,7 @@
<activity
android:name=".SignupActivity"
android:exported="false" />
<activity android:name=".ExerciseDetailActivity" />
</application>
</manifest>

@ -0,0 +1,17 @@
package org.tensorflow.lite.examples.poseestimation
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R
class DataFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_data, container, false)
}
}

@ -0,0 +1,55 @@
package org.tensorflow.lite.examples.poseestimation
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import android.widget.Button
class ExerciseDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_exercise_detail)
// 找到布局中的控件
val backButton = findViewById<ImageButton>(R.id.back_button)
val exerciseImage = findViewById<ImageView>(R.id.exercise_detail_image)
val exerciseName = findViewById<TextView>(R.id.exercise_detail_name)
val exerciseDescription = findViewById<TextView>(R.id.exercise_detail_description)
val startTrainingButton = findViewById<Button>(R.id.start_training_button)
// 获取从 HomeFragment 传递过来的动作名称
val exerciseNameFromIntent = intent.getStringExtra("exercise_name")
// 设置页面内容
exerciseNameFromIntent?.let { name ->
exerciseName.text = name
// 根据动作名称设置对应的图片
val imageResId = when (name) {
"硬拉" -> R.drawable.deadlift
"深蹲" -> R.drawable.deep_squats
"平板支撑" -> R.drawable.plank
"引体向上" -> R.drawable.pull_up
"俯卧撑" -> R.drawable.push_up
else -> R.drawable.placeholder_image
}
exerciseImage.setImageResource(imageResId)
}
// 设置返回按钮点击事件
backButton.setOnClickListener {
onBackPressed() // 返回上一个Activity
}
// 设置开始训练按钮点击事件,跳转到姿态识别页面
startTrainingButton.setOnClickListener {
val intent = Intent(this, MainActivity::class.java)
// 传递当前动作名称给 MainActivity
intent.putExtra("current_exercise", exerciseNameFromIntent)
startActivity(intent)
}
}
}

@ -8,6 +8,11 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.Dispatchers
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
import org.tensorflow.lite.examples.poseestimation.data.UserProfile
import kotlin.math.abs
class HeightSelectionActivity : AppCompatActivity() {
@ -22,6 +27,7 @@ class HeightSelectionActivity : AppCompatActivity() {
private var selectedGender: String? = null
private var selectedAge: Int = 0
private var selectedWeight: Int = 0
private var username: String? = null
private var currentHeight = 167
private val minHeight = 100
@ -37,6 +43,7 @@ class HeightSelectionActivity : AppCompatActivity() {
selectedGender = intent.getStringExtra("selected_gender")
selectedAge = intent.getIntExtra("selected_age", 0)
selectedWeight = intent.getIntExtra("selected_weight", 0)
username = intent.getStringExtra("username")
selectedHeightText = findViewById(R.id.selectedHeightText)
heightUnit = findViewById(R.id.heightUnit)
@ -84,11 +91,25 @@ class HeightSelectionActivity : AppCompatActivity() {
private fun setupClickListeners() {
nextButton.setOnClickListener {
// 保存用户个人信息到数据库
val db = AppDatabase.getDatabase(this)
val gender = selectedGender ?: ""
val age = selectedAge
val weight = selectedWeight
val height = currentHeight
val user = username ?: ""
lifecycleScope.launch(Dispatchers.IO) {
try {
val profile = UserProfile(user, gender, age, weight, height)
db.userProfileDao().insertUserProfile(profile)
} catch (_: Exception) {}
}
val intent = Intent(this, LoginActivity::class.java)
intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge)
intent.putExtra("selected_weight", selectedWeight)
intent.putExtra("selected_height", currentHeight)
intent.putExtra("username", username)
startActivity(intent)
finish()
}
@ -98,6 +119,7 @@ class HeightSelectionActivity : AppCompatActivity() {
intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge)
intent.putExtra("selected_weight", selectedWeight)
intent.putExtra("username", username)
startActivity(intent)
finish()
}

@ -0,0 +1,80 @@
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)
}
}

@ -4,10 +4,13 @@ import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.textfield.TextInputEditText
import android.widget.Button
import android.widget.TextView
import android.widget.EditText
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -27,13 +30,16 @@ class LoginActivity : AppCompatActivity() {
val forgotPassword = findViewById<TextView>(R.id.forgotPassword)
val signupTab = findViewById<TextView>(R.id.tabSignup)
// 获取数据库实例
val db = AppDatabase.getDatabase(this)
// 登录按钮点击事件
loginBtn.setOnClickListener {
val email = emailEdit.text.toString().trim()
val username = emailEdit.text.toString().trim()
val password = passwordEdit.text.toString().trim()
// 表单验证
if (email.isEmpty()) {
if (username.isEmpty()) {
emailEdit.error = "请输入用户名"
return@setOnClickListener
}
@ -42,15 +48,30 @@ class LoginActivity : AppCompatActivity() {
return@setOnClickListener
}
// TODO: 这里添加实际的登录验证逻辑
// 目前仅做演示,直接跳转到主页面
val intent = Intent(this, MainActivity::class.java)
intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge)
intent.putExtra("selected_weight", selectedWeight)
intent.putExtra("selected_height", selectedHeight)
startActivity(intent)
finish()
// 在协程中执行数据库操作
lifecycleScope.launch {
try {
val user = db.userDao().getUser(username, password).first()
if (user != null) {
// 登录成功
val intent = Intent(this@LoginActivity, org.tensorflow.lite.examples.poseestimation.MainTabActivity::class.java)
intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge)
intent.putExtra("selected_weight", selectedWeight)
intent.putExtra("selected_height", selectedHeight)
startActivity(intent)
finish()
} else {
runOnUiThread {
Toast.makeText(this@LoginActivity, "用户名或密码错误", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
runOnUiThread {
Toast.makeText(this@LoginActivity, "登录失败:${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
// 忘记密码点击事件

@ -48,6 +48,9 @@ import org.tensorflow.lite.examples.poseestimation.ml.PoseClassifier
import org.tensorflow.lite.examples.poseestimation.ml.PoseNet
import org.tensorflow.lite.examples.poseestimation.ml.TrackerType
import org.tensorflow.lite.examples.poseestimation.ml.Type
import org.tensorflow.lite.examples.poseestimation.data.Angle
import org.tensorflow.lite.examples.poseestimation.data.AngleDifference
import org.tensorflow.lite.examples.poseestimation.data.Person
class MainActivity : AppCompatActivity() {
companion object {
@ -141,11 +144,17 @@ class MainActivity : AppCompatActivity() {
isPoseClassifier()
}
private lateinit var tvPoseAdvice: TextView
private var currentExerciseType: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// keep screen on while app is running
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
currentExerciseType = intent.getStringExtra("current_exercise")
tvScore = findViewById(R.id.tvScore)
tvFPS = findViewById(R.id.tvFps)
spnModel = findViewById(R.id.spnModel)
@ -158,6 +167,8 @@ class MainActivity : AppCompatActivity() {
tvClassificationValue3 = findViewById(R.id.tvClassificationValue3)
swClassification = findViewById(R.id.swPoseClassification)
vClassificationOption = findViewById(R.id.vClassificationOption)
tvPoseAdvice = findViewById(R.id.tv_pose_advice)
initSpinner()
spnModel.setSelection(modelPos)
swClassification.setOnCheckedChangeListener(setClassificationListener)
@ -203,7 +214,8 @@ class MainActivity : AppCompatActivity() {
override fun onDetectedInfo(
personScore: Float?,
poseLabels: List<Pair<String, Float>>?
poseLabels: List<Pair<String, Float>>?,
persons: List<Person>
) {
tvScore.text = getString(R.string.tfe_pe_tv_score, personScore ?: 0f)
poseLabels?.sortedByDescending { it.second }?.let {
@ -220,6 +232,29 @@ class MainActivity : AppCompatActivity() {
convertPoseLabels(if (it.size >= 3) it[2] else null)
)
}
if (persons.isNotEmpty() && currentExerciseType != null) {
val angleObj = Angle()
val angleDifference = AngleDifference(angleObj)
val personKeyPoints = persons[0].keyPoints
val advice = when (currentExerciseType) {
"硬拉" -> angleDifference.calculateAngleDifference(personKeyPoints, "2")
"深蹲" -> angleDifference.calculateAngleDifference(personKeyPoints, "4")
"平板支撑" -> angleDifference.calculateAngleDifference(personKeyPoints, "5")
"引体向上" -> angleDifference.calculateAngleDifference(personKeyPoints, "1")
"俯卧撑" -> angleDifference.calculateAngleDifference(personKeyPoints, "3")
else -> "未知动作类型,无法给出建议"
}
runOnUiThread {
tvPoseAdvice.text = advice
}
} else if (currentExerciseType == null) {
runOnUiThread {
tvPoseAdvice.text = "未指定动作类型,无法给出建议"
}
}
}
}).apply {

@ -0,0 +1,72 @@
package org.tensorflow.lite.examples.poseestimation
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R
class MainTabActivity : AppCompatActivity() {
private lateinit var navHome: ImageView
private lateinit var navData: ImageView
private lateinit var navSetting: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_tab)
navHome = findViewById(R.id.nav_home)
navData = findViewById(R.id.nav_data)
navSetting = findViewById(R.id.nav_setting)
// 底部导航栏跳转逻辑:
// 1. 在 onCreate 中初始化三个导航按钮navHome、navData、navSetting
// 2. 默认显示 HomeFragment并更新导航图标
// 3. 为每个导航按钮设置点击事件,点击时调用 switchFragment 切换对应的 Fragment并调用 updateNavIcons 更新图标状态
// 4. switchFragment 方法使用 supportFragmentManager 的 beginTransaction 替换 fragment_container 中的 Fragment
// 5. updateNavIcons 方法根据当前选中的导航项0、1、2更新三个导航按钮的图标资源
// 默认显示HomeFragment
switchFragment(HomeFragment())
updateNavIcons(0)
navHome.setOnClickListener {
switchFragment(HomeFragment())
updateNavIcons(0)
}
navData.setOnClickListener {
switchFragment(DataFragment())
updateNavIcons(1)
}
navSetting.setOnClickListener {
switchFragment(SettingFragment())
updateNavIcons(2)
}
}
private fun switchFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
}
private fun updateNavIcons(selected: Int) {
when (selected) {
0 -> {
navHome.setImageResource(R.drawable.home1)
navData.setImageResource(R.drawable.data2)
navSetting.setImageResource(R.drawable.setting2)
}
1 -> {
navHome.setImageResource(R.drawable.home2)
navData.setImageResource(R.drawable.data1)
navSetting.setImageResource(R.drawable.setting2)
}
2 -> {
navHome.setImageResource(R.drawable.home2)
navData.setImageResource(R.drawable.data2)
navSetting.setImageResource(R.drawable.setting1)
}
}
}
}

@ -0,0 +1,17 @@
package org.tensorflow.lite.examples.poseestimation
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R
class SettingFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_setting, container, false)
}
}

@ -1,11 +1,18 @@
package org.tensorflow.lite.examples.poseestimation
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
import org.tensorflow.lite.examples.poseestimation.data.User
class SignupActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -19,6 +26,9 @@ class SignupActivity : AppCompatActivity() {
val signupBtn = findViewById<Button>(R.id.btnSignup)
val loginTab = findViewById<TextView>(R.id.tabLogin)
// 获取数据库实例
val db = AppDatabase.getDatabase(this)
// 注册按钮点击事件
signupBtn.setOnClickListener {
val username = usernameEdit.text.toString().trim()
@ -46,9 +56,35 @@ class SignupActivity : AppCompatActivity() {
return@setOnClickListener
}
// TODO: 这里添加实际的注册逻辑
Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show()
finish()
// 在IO线程中执行数据库操作
lifecycleScope.launch(Dispatchers.IO) {
try {
// 检查用户名是否已存在
val existingUser = db.userDao().getUserByUsername(username).first()
if (existingUser != null) {
runOnUiThread {
Toast.makeText(this@SignupActivity, "用户名已存在", Toast.LENGTH_SHORT).show()
}
return@launch
}
// 创建新用户
val newUser = User(username, password)
db.userDao().insertUser(newUser)
runOnUiThread {
Toast.makeText(this@SignupActivity, "注册成功", Toast.LENGTH_SHORT).show()
// 注册成功后跳转到OnboardingActivity
val intent = Intent(this@SignupActivity, OnboardingActivity::class.java)
startActivity(intent)
finish() // 结束注册界面
}
} catch (e: Exception) {
runOnUiThread {
Toast.makeText(this@SignupActivity, "注册失败:${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
// Login Tab点击事件

@ -41,9 +41,9 @@ class SplashActivity : AppCompatActivity() {
vector2.layoutParams = params2
}
// 2秒后跳转到引导
// 2秒后跳转到登录
Handler(Looper.getMainLooper()).postDelayed({
startActivity(Intent(this, OnboardingActivity::class.java))
startActivity(Intent(this, LoginActivity::class.java))
finish()
}, 2000)
}

@ -264,7 +264,7 @@ class CameraSource(
// if the model returns only one item, show that item's score.
if (persons.isNotEmpty()) {
listener?.onDetectedInfo(persons[0].score, classificationResult)
listener?.onDetectedInfo(persons[0].score, classificationResult, persons)
}
visualize(persons, bitmap)
}
@ -322,6 +322,10 @@ class CameraSource(
interface CameraSourceListener {
fun onFPSListener(fps: Int)
fun onDetectedInfo(personScore: Float?, poseLabels: List<Pair<String, Float>>?)
fun onDetectedInfo(
personScore: Float?,
poseLabels: List<Pair<String, Float>>?,
persons: List<Person>
)
}
}

@ -0,0 +1,31 @@
package org.tensorflow.lite.examples.poseestimation.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [User::class, UserProfile::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun userProfileDao(): UserProfileDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
}

@ -0,0 +1,11 @@
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
)

@ -0,0 +1,18 @@
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?>
}

@ -0,0 +1,13 @@
package org.tensorflow.lite.examples.poseestimation.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "user_profiles")
data class UserProfile(
@PrimaryKey val username: String, // 与User表关联
val gender: String,
val age: Int,
val weight: Int,
val height: Int
)

@ -0,0 +1,15 @@
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 UserProfileDao {
@Insert
fun insertUserProfile(profile: UserProfile)
@Query("SELECT * FROM user_profiles WHERE username = :username")
fun getUserProfileByUsername(username: String): Flow<UserProfile?>
}

@ -1,5 +1,10 @@
package org.tensorflow.lite.examples.poseestimation.data
import android.graphics.PointF
import kotlin.math.abs
import kotlin.math.acos
import kotlin.math.atan2
import kotlin.math.sqrt
class Angle {
@ -72,9 +77,37 @@ class Angle {
class AngleDifference(private val angleObj: Angle) {
// 假设getAngles()方法已经实现并能返回一个List<Double>
fun getAngles(): List<Double> {
return listOf(100.0, 95.0, 180.0, 85.0, 90.0, 75.0, 160.0, 170.0)
// 根据关键点计算关节角度
fun getAngles(keyPoints: List<KeyPoint>): List<Double> {
val angles = mutableListOf<Double>()
// 定义需要计算角度的关节及其组成关键点
// 顺序对应 BodyParts: 左肩, 右肩, 左肘, 右肘, 左髋, 右髋, 左膝, 右膝
val angleJoints = listOf(
Triple(BodyPart.LEFT_ELBOW, BodyPart.LEFT_SHOULDER, BodyPart.RIGHT_SHOULDER), // 左肩角度
Triple(BodyPart.RIGHT_ELBOW, BodyPart.RIGHT_SHOULDER, BodyPart.LEFT_SHOULDER), // 右肩角度
Triple(BodyPart.LEFT_SHOULDER, BodyPart.LEFT_ELBOW, BodyPart.LEFT_WRIST), // 左肘角度
Triple(BodyPart.RIGHT_SHOULDER, BodyPart.RIGHT_ELBOW, BodyPart.RIGHT_WRIST), // 右肘角度
Triple(BodyPart.LEFT_SHOULDER, BodyPart.LEFT_HIP, BodyPart.RIGHT_HIP), // 左髋角度
Triple(BodyPart.RIGHT_SHOULDER, BodyPart.RIGHT_HIP, BodyPart.LEFT_HIP), // 右髋角度
Triple(BodyPart.LEFT_HIP, BodyPart.LEFT_KNEE, BodyPart.LEFT_ANKLE), // 左膝角度
Triple(BodyPart.RIGHT_HIP, BodyPart.RIGHT_KNEE, BodyPart.RIGHT_ANKLE) // 右膝角度
)
for (joint in angleJoints) {
val p1 = keyPoints.find { it.bodyPart == joint.first }
val p2 = keyPoints.find { it.bodyPart == joint.second }
val p3 = keyPoints.find { it.bodyPart == joint.third }
// 确保三个关键点都检测到且置信度较高
if (p1 != null && p2 != null && p3 != null && p1.score > 0.2 && p2.score > 0.2 && p3.score > 0.2) { // 假设置信度阈值为0.2
angles.add(p2.abttPoints(p1, p2, p3).toDouble())
} else {
angles.add(0.0) // 如果关键点未检测到或置信度低角度设为0或NaN具体取决于你如何处理无效数据
}
}
return angles
}
// 根据不同的 listType 判断标准
@ -90,32 +123,24 @@ class AngleDifference(private val angleObj: Angle) {
}
// 计算角度差并根据角度值判断是否大于10返回相应的语句
fun calculateAngleDifference(listType: String): String {
// 根据传入的参数选择不同的角度列表
val selectedList = when (listType) {
"1" -> angleObj.standList() // 选择站立姿势列表
"2" -> angleObj.deadliftList() // 选择硬拉列表
"3" -> angleObj.pushUpList() // 选择俯卧撑列表
"4" -> angleObj.squatList() // 选择深蹲列表
"5" -> angleObj.plankList() // 选择平板支撑列表
else -> throw IllegalArgumentException("未知的列表类型: $listType") // 处理无效的输入
}
// 获取另一个类中的角度列表
val angles = getAngles()
// 获取当前listType对应的标准列表
fun calculateAngleDifference(keyPoints: List<KeyPoint>, listType: String): String {
// 根据传入的参数选择不同的角度列表 (标准角度)
val standardList = getStandardForListType(listType)
// 获取从关键点计算出的角度列表
val angles = getAngles(keyPoints)
// 确保两个列表的大小一致
if (selectedList.size != angles.size || selectedList.size != standardList.size) {
throw IllegalArgumentException("三个列表的大小不一致!")
if (standardList.size != angles.size) {
// throw IllegalArgumentException("标准列表和计算出的角度列表大小不一致!")
// 如果大小不一致,可能是关键点没有全部检测到,返回一个提示信息而不是崩溃
return "关键点检测不完整,无法给出建议。"
}
// 计算差值并检查条件
val differenceList = mutableListOf<Double>()
for (i in selectedList.indices) {
differenceList.add((selectedList[i] - angles[i]).toDouble())
for (i in standardList.indices) {
differenceList.add((standardList[i] - angles[i]))
}
// 根据listType的不同提供不同标准的判断
@ -131,21 +156,16 @@ class AngleDifference(private val angleObj: Angle) {
for (i in absoluteValues.indices) {
val standard = standardList[i]
val calculatedAngle = angles[i]
val absoluteDifference = absoluteValues[i]
if (absoluteDifference > 10) {
// 仅对检测到且置信度较高的关键点进行角度判断
// 这里假设 getAngles 中已经处理了置信度低的,如果 getAngles 返回0.0表示无效,则跳过
if (calculatedAngle != 0.0 && absoluteDifference > 10) { // 角度差大于10度给出建议
// 根据角度差的正负判断提示语
val direction = if (differenceList[i] > 0) "增大" else "减小"
// 根据listType提供不同的标准和建议
warningMessages.add(
when (listType) {
"1" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"2" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"3" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"4" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
"5" -> "${bodyParts[i]}的角度差值超出标准(${standard}°),请$direction 相应位置的角度!"
else -> "未知类型的角度差值超出标准,请检查!"
}
)
warningMessages.add("${bodyParts[i]}角度(${String.format("%.1f", calculatedAngle)}°)与标准(${standard}°)相差过大,请注意调整!")
}
}

@ -310,7 +310,7 @@ class PoseNet(private val interpreter: Interpreter, private var gpuDelegate: Gpu
}
// 提供外部访问 angles 的方法
fun getAngles(): List<Float> {
val getAngles: List<Float> get(){
return angles.toList() // 返回 angles 的副本
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

@ -0,0 +1,5 @@
<?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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

@ -0,0 +1,76 @@
<?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="16sp"
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>

@ -10,6 +10,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 用于显示姿态建议的 TextView -->
<TextView
android:id="@+id/tv_pose_advice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:gravity="center_horizontal"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:text="姿态建议将显示在这里"
android:background="#80000000" /> <!-- 半透明黑色背景 -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"

@ -0,0 +1,47 @@
<?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"
tools:context=".MainTabActivity">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="56dp" />
<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>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1C1E">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="数据页面"
android:textColor="#FFF"
android:textSize="24sp"
android:layout_gravity="center"/>
</FrameLayout>

@ -0,0 +1,54 @@
<?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>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1C1E">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置页面"
android:textColor="#FFF"
android:textSize="24sp"
android:layout_gravity="center"/>
</FrameLayout>

@ -0,0 +1,40 @@
<?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">
<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="#000000"
android:textSize="18sp" />
<TextView
android:id="@+id/exercise_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textColor="#666666"
android:textSize="14sp"
android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>

@ -5,4 +5,4 @@
# For customization when using a Version Control System, please read the
# header note.
#Sat May 24 23:39:55 CST 2025
sdk.dir=C\:\\Users\\26891\\AppData\\Local\\Android\\Sdk
sdk.dir=/Users/ziyue/Library/Android/sdk

Loading…
Cancel
Save