diff --git a/src/FruitandVegetableGuide/.idea/dbnavigator.xml b/src/FruitandVegetableGuide/.idea/dbnavigator.xml index 2f23ff3..00acff4 100644 --- a/src/FruitandVegetableGuide/.idea/dbnavigator.xml +++ b/src/FruitandVegetableGuide/.idea/dbnavigator.xml @@ -1,8 +1,17 @@ + + + + + + + + + diff --git a/src/FruitandVegetableGuide/.idea/deploymentTargetDropDown.xml b/src/FruitandVegetableGuide/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..60e9d58 --- /dev/null +++ b/src/FruitandVegetableGuide/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/build.gradle b/src/FruitandVegetableGuide/app/build.gradle index d300838..11eafab 100644 --- a/src/FruitandVegetableGuide/app/build.gradle +++ b/src/FruitandVegetableGuide/app/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId "com.example.fruitandvegetableguide" - minSdk 21 + minSdk 24 targetSdk 34 versionCode 1 versionName "1.0" @@ -47,6 +47,14 @@ android { } dependencies { + //用于compose权限的使用 + implementation("com.google.accompanist:accompanist-permissions:0.31.0-alpha") +//闪光 + implementation("com.google.accompanist:accompanist-placeholder-material:0.31.0-alpha") + implementation("io.coil-kt:coil-compose:2.2.2") + //gson + implementation 'com.google.code.gson:gson:2.10.1' + implementation("androidx.navigation:navigation-compose:2.5.3") implementation "androidx.appcompat:appcompat:1.6.1" @@ -54,6 +62,8 @@ dependencies { implementation "com.google.android.material:material:1.9.0" implementation 'androidx.core:core-ktx:+' implementation 'androidx.core:core-ktx:+' + implementation 'androidx.core:core-ktx:+' + implementation 'androidx.core:core-ktx:+' def composeBom = platform('androidx.compose:compose-bom:2023.08.00') implementation(composeBom) @@ -78,16 +88,3 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } -// implementation("androidx.compose.material:material") -// implementation("androidx.compose.foundation:foundation") -// implementation("androidx.compose.ui:ui") -// implementation("androidx.compose.material:material-icons-core") -// implementation("androidx.compose.material3:material3-window-size-class") -// implementation("androidx.compose.runtime:runtime-livedata") -// implementation("androidx.compose.runtime:runtime-rxjava2") -// implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0') -// implementation platform('androidx.compose:compose-bom:2022.10.00') -// implementation 'androidx.compose.ui:ui-graphics' -// implementation 'androidx.appcompat:appcompat:1.4.1' -// implementation 'com.google.android.material:material:1.5.0' -// androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00') diff --git a/src/FruitandVegetableGuide/app/release/app-release.apk b/src/FruitandVegetableGuide/app/release/app-release.apk new file mode 100644 index 0000000..d4235d6 Binary files /dev/null and b/src/FruitandVegetableGuide/app/release/app-release.apk differ diff --git a/src/FruitandVegetableGuide/app/release/output-metadata.json b/src/FruitandVegetableGuide/app/release/output-metadata.json new file mode 100644 index 0000000..7c5201b --- /dev/null +++ b/src/FruitandVegetableGuide/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.example.fruitandvegetableguide", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/AndroidManifest.xml b/src/FruitandVegetableGuide/app/src/main/AndroidManifest.xml index 097e5ee..71907d5 100644 --- a/src/FruitandVegetableGuide/app/src/main/AndroidManifest.xml +++ b/src/FruitandVegetableGuide/app/src/main/AndroidManifest.xml @@ -2,7 +2,24 @@ + + + + + + + + + + + + + + + + + navController.navigateSingleTopTo("${Search.route}/${search}") }, + onClickToUser = { navController.navigateSingleTopTo("${User.route}/${userid}") }, onClickToPhotograph = { navController.navigateSingleTopTo(Photograph.route) }, onClickToGuide = { navController.navigateSingleTopTo(Guide.route) }, - onClickToCommunity = { navController.navigateSingleTopTo(Community.route) }, - onClickToGuideDetail = { navController.navigateSingleTopTo(GuideDetail.route) }, - onClickToPostDetail = { navController.navigateSingleTopTo(PostDetail.route) } + onClickToCommunity = { navController.navigateSingleTopTo("${Community.route}/${userid}") }, + onClickToGuideDetail = { guideId -> navController.navigateSingleTopTo("${GuideDetail.route}/${guideId}") }, + onClickToPostDetail = { postId -> navController.navigateSingleTopTo("${PostDetail.route}/${postId}") } ) } //搜索页路由 - composable(route = Search.route) { + composable( + route = "${Search.route}/{search}", + arguments = listOf( + navArgument("search") { + type = NavType.StringType + nullable = false + } + ) + ) { + val search = it.arguments?.getString("search") SearchScreen( + search = search, onClickBack = { navController.popBackStack() }, - onClickToGuideDetail = { navController.navigateSingleTopTo(GuideDetail.route) }, - onClickToPostDetail = { navController.navigateSingleTopTo(PostDetail.route) }, + onClickToGuideDetail = { guideId -> navController.navigateSingleTopTo("${GuideDetail.route}/${guideId}") }, + onClickToPostDetail = { postId -> navController.navigateSingleTopTo("${PostDetail.route}/${postId}") }, ) } //指南详情页路由 - composable(route = GuideDetail.route) { - GuideDetailScreen(onClickBack = { navController.popBackStack() }) + composable( + route = "${GuideDetail.route}/{guideId}", + arguments = listOf( + navArgument("guideId") { + type = NavType.IntType + nullable = false + } + ) + ) { + val guideId = it.arguments?.getInt("guideId") + GuideDetailScreen( + guideId = guideId, + onClickBack = { navController.popBackStack() }) } //帖子详情页路由 - composable(route = PostDetail.route) { - PostDetailScreen(onClickBack = { navController.popBackStack() }) + composable( + route = "${PostDetail.route}/{postId}", + arguments = listOf( + navArgument("postId") { + type = NavType.IntType + nullable = false + } + ) + ) { + val postId = it.arguments?.getInt("postId") //接收post的属性列表 + PostDetailScreen( + postId = postId, + onClickBack = { navController.popBackStack() } + ) } //用户页路由 - composable(route = User.route) { + composable( + route = "${User.route}/{userid}", + arguments = listOf( + navArgument("userid") { + type = NavType.IntType + nullable = false + } + )) { + val userid = it.arguments?.getInt("userid")//接收userid UserScreen( + userid = userid, onClickToMain = { navController.popBackStack() }, - onClickToMyPost = { navController.navigateSingleTopTo(MyPost.route) }, + onClickToMyPost = { navController.navigateSingleTopTo("${MyPost.route}/${userid}") }, onClickToPhotograph = { navController.navigateSingleTopTo(Photograph.route) } ) } //我的帖子页面路由 - composable(route = MyPost.route) { - MyPostScreen(onClickBack = { navController.popBackStack() }) + composable(route = "${MyPost.route}/{userid}", + arguments = listOf( + navArgument("userid") { + type = NavType.IntType + nullable = false + } + )) { + val userid = it.arguments?.getInt("userid")//接收userid + MyPostScreen(userid = userid, onClickBack = { navController.popBackStack() }) } //拍照页路由 composable(route = Photograph.route) { - PhotographScreen(onClickToRecognizeResult = { - navController.navigateSingleTopTo( - RecognizeResult.route - ) - }) + PhotographScreen( + onClickBack = { navController.popBackStack() }, + onClickToRecognizeResult = { localImgPath -> + navController.navigateSingleTopTo( + "${RecognizeResult.route}/${localImgPath}" + ) + }) } //识别结果页路由 - composable(route = RecognizeResult.route) { - RecognizeResultScreen(onClickBack = { navController.popBackStack() }) + composable( + route = "${RecognizeResult.route}/{localImgPath}", + arguments = listOf( + navArgument("localImgPath") { + type = NavType.StringType + nullable = false + }) + ) { + val localImgPath = it.arguments?.getString("localImgPath") + RecognizeResultScreen( + transPath = localImgPath, + onClickBack = { navController.popBackStack() }) } //指南页路由 composable(route = Guide.route) { GuideScreen( - onClickToSearch = { navController.navigateSingleTopTo(Search.route) }, - onClickToGuideDetail = { navController.navigateSingleTopTo(GuideDetail.route) }, + onClickToSearch = { search -> navController.navigateSingleTopTo("${Search.route}/${search}") }, + onClickToGuideDetail = { guideId -> navController.navigateSingleTopTo("${GuideDetail.route}/${guideId}") }, onClickBack = { navController.popBackStack() } ) } //社区页路由 - composable(route = Community.route) { + composable( + route = "${Community.route}/{userid}", + arguments = listOf( + navArgument("userid") { + type = NavType.IntType + nullable = false + } + ) + ) { + val userid = it.arguments?.getInt("userid")//接收userid CommunityScreen( - onClickToSearch = { navController.navigateSingleTopTo(Search.route) }, - onClickToPostDetail = { navController.navigateSingleTopTo(PostDetail.route) }, - onClickToPostEdit = { navController.navigateSingleTopTo(PostEdit.route) }, + userid = userid, + onClickToSearch = { search -> navController.navigateSingleTopTo("${Search.route}/${search}") }, + onClickToPostDetail = { postId -> navController.navigateSingleTopTo("${PostDetail.route}/${postId}") }, + onClickToPostEdit = { navController.navigateSingleTopTo("${PostEdit.route}/${userid}") }, onClickBack = { navController.popBackStack() } ) } //帖子编辑页路由 - composable(route = PostEdit.route) { + composable( + route = "${PostEdit.route}/{userid}", + arguments = listOf( + navArgument("userid") { + type = NavType.IntType + nullable = false + } + ) + ) { + val userid = it.arguments?.getInt("userid")//接收userid PostEditScreen( + userid = userid, onClickBack = { navController.popBackStack() } ) } diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/README.md b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/README.md new file mode 100644 index 0000000..50d2643 --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/README.md @@ -0,0 +1,15 @@ +# 要做的事还有很多 +![TODO](../../../../res/drawable/todo_list.png) + +运行前,先放一张西红柿图片在相册中,方便测试 + +Android Studio无法预览Markdown文件 +``` +In the Android Studio: + +1. Find action (ctrl + shift + A / command + shift + A) +2. Search for Choose Boot Java Runtime for the IDE +3. Select the latest version in the "New:" dropdown - e.g. 11.0.12+7-b1504.27 JetBrains Runtime with JCEF // 这里我选择的最新版本即可。 +4. OK +5. Restart +``` diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Account.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Account.kt index f4bc508..f9a5a0a 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Account.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Account.kt @@ -2,7 +2,7 @@ package com.example.fruitandvegetableguide.data //用户数据类 data class Account( - val id:Long, - val username: String, - val password:String + val id:Int, //id,自增 + val username: String, //用户名 + val password:String //密码 ) diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Guide.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Guide.kt index 6b21ffd..18709c5 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Guide.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Guide.kt @@ -1,13 +1,15 @@ package com.example.fruitandvegetableguide.data +import com.example.fruitandvegetableguide.R + //指南数据类 data class Guide( - val id: Long, - val imgId: Int, - val kind: String, - val imgUrl: String, - val suitable: String, - val taboo: String, - val nutritiveValue: String, - val recommendedMenu: String + val id: Int, //id,自增 + val imgId: Int = R.drawable.loading, + val kind: String, //种类 + val identify :String, //辨识方法 + val suitable: String, //适宜人群 + val taboo: String, //禁忌 + val nutritiveValue: String, //营养价值 + val recommendedMenu: String //推荐菜 ) diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Post.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Post.kt index d2a4973..7e2be28 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Post.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/Post.kt @@ -1,11 +1,13 @@ package com.example.fruitandvegetableguide.data +import com.example.fruitandvegetableguide.R + //帖子数据类 data class Post( - val id: Long, - val userId: Long, - val title: String, - val imgId: Int, - val labels: ArrayList, - val content: String + val id: Int, //id,自增 + val userId: Int, //用户id + val title: String, //标题 + val imgId: Int = R.drawable.loading, + val labels: String, //标签 + val content: String //内容 ) diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/dbhelper/DBHelper.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/dbhelper/DBHelper.kt new file mode 100644 index 0000000..9a2ff1b --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/dbhelper/DBHelper.kt @@ -0,0 +1,3 @@ +package com.example.fruitandvegetableguide.data.dbhelper + +// TODO 这里放数据库工具方法,这些方法可以去网上找现成的 diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/dbutils/DBUtils.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/dbutils/DBUtils.kt deleted file mode 100644 index b788ade..0000000 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/dbutils/DBUtils.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.example.fruitandvegetableguide.data.dbutils - -// TODO 这里放数据库工具方法 \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalGuidesDataProvider.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalGuidesDataProvider.kt index 06de5f1..95afec2 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalGuidesDataProvider.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalGuidesDataProvider.kt @@ -9,18 +9,20 @@ object LocalGuidesDataProvider { Guide( 1, R.drawable.carrots, - "胡萝卜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/2.jpg", - "一般人皆可食用,尤以[癌症、高血压、夜盲症干眼症、营养不良、皮肤粗糙]者为最。", - "[饮酒过量]者不宜食用", - "热量: 37千卡, 蛋白质: 1克\n碳水化合物: 8.8克, 膳食纤维:3.2克\n 胡萝卜素: 4130微克", - "炝腌萝卜" + "种类-默认", + "1、首先就要观察外表,看表皮有没有破损,甚至坑坑洼洼,掉皮的胡萝卜会让其水分蒸发,肯定会影响口感,并且不容易存放。\n" + + "2、把两个相同大小的胡萝卜拿在手里掂一下,如果有一个轻得多的就不要买了,说明放置的时间太长了,里面的水分减少,要买那种沉甸甸的胡萝卜,水分更充足,更新鲜好吃。所以大家买胡萝卜的时候最好用手感受一下重量。\n" + + "3、个头偏大的长得比较老,水分肯定少,口感不鲜嫩;而个头偏小的也不行,虽然很脆嫩,但是还没有完全长成熟,营养价值不高。挑胡萝卜尽量选中等个头,粗细均匀,带有根须的。", + "适宜-默认", + "禁忌-默认", + "营养价值-默认", + "推荐菜-默认" ), Guide( 2, R.drawable.cucumbers, "黄瓜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/3.jpg", + "颜色:黄瓜的颜色应该是均匀的绿色,没有任何瑕疵。\n手感:轻轻掐一下黄瓜,如果手感觉进入黄瓜时很脆嫩,而且有水分出来的黄瓜比较鲜嫩。\n刺:黄瓜的刺特别嫩才是质量口感好的黄瓜。看到黄瓜表皮的刺小而密,而且轻轻一摸就会碎断,这种黄瓜十分新鲜润滑。\n纵陵:选择黄瓜的时候要选择黄瓜的瓜体呈棒状带有纵陵的一类黄瓜。\n顶花:好的黄瓜,顶花会在黄瓜成熟后自然脱落,留下个小黄点。\n大小:太大的黄瓜一般是催熟的黄瓜,相对来说个头小的黄瓜比较好吃。", "一般人群均可食用。尤适宜[热病、肥胖、高血脂、水肿、癌症、嗜酒者、糖尿病 ]患者食用。", "[腰胃虚弱、腹痛腹泻、肺寒咳嗽、 肝病心血管病、肠胃病]患者慎食。", "蛋白质: 0.8克, 维生素A: 15微克, 胡萝卜素: 90微克", @@ -30,7 +32,7 @@ object LocalGuidesDataProvider { 3, R.drawable.tomatoes, "西红柿", - "https://bkimg.cdn.bcebos.com/pic/0823dd54564e9258d109b7bb1bd4c658ccbf6c81e012?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2U5Mg==,g_7,xp_5,yp_5/format,f_auto", + "1. 选择正常大小的西红柿,不要买畸形的西红柿,因为畸形的很有可能是打了一些对人体有害的药在里面,有助于西红柿快速的生长.\n2. 选择能从表皮面能看到西红柿内部的大概结构,这也能说明西红柿是很健康的.\n3. 选择西红柿顶端是有裂缝的,意思就是有一些小小的裂缝痕迹,这样也能说明西红柿没有打催生剂.\n4. 选择西红柿里面不是绿色的,因为现场也不能做好实验,可以先买一个回去试一试,切开看西红柿里面是不是绿色的,如果是绿色的,西红柿外表是红色的,这只能说明西红柿并未成熟,而是打了催生剂.\n5. 选择比较硬一点的西红柿,这样就比较新鲜,同时也容易放置,这样也就不容易坏掉了¹.", "一般人群均可食用。尤适宜于[热性病发热、口渴、食欲不振、习惯性牙龈出血、贫血、头晕心悸、高血压、急慢性肝炎、急慢性肾炎、夜盲症、近视眼]者食用", "[急性肠炎、菌痢、溃疡活动期]病人不宜食", "热量: 19千卡,蛋白质: 0.9 克, 碳水化合物: 4克", @@ -40,7 +42,7 @@ object LocalGuidesDataProvider { 4, R.drawable.carrots, "胡萝卜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/2.jpg", + "1、首先就要观察外表,看表皮有没有破损,甚至坑坑洼洼,掉皮的胡萝卜会让其水分蒸发,肯定会影响口感,并且不容易存放。\n2、把两个相同大小的胡萝卜拿在手里掂一下,如果有一个轻得多的就不要买了,说明放置的时间太长了,里面的水分减少,要买那种沉甸甸的胡萝卜,水分更充足,更新鲜好吃。所以大家买胡萝卜的时候最好用手感受一下重量。\n3、个头偏大的长得比较老,水分肯定少,口感不鲜嫩;而个头偏小的也不行,虽然很脆嫩,但是还没有完全长成熟,营养价值不高。挑胡萝卜尽量选中等个头,粗细均匀,带有根须的。\n", "一般人皆可食用,尤以[癌症、高血压、夜盲症干眼症、营养不良、皮肤粗糙]者为最。", "[饮酒过量]者不宜食用", "热量: 37千卡, 蛋白质: 1克, 碳水化合物: 8.8克, 膳食纤维3.2克, 胡萝卜素: 4130微克", @@ -50,7 +52,7 @@ object LocalGuidesDataProvider { 5, R.drawable.cucumbers, "黄瓜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/3.jpg", + "颜色:黄瓜的颜色应该是均匀的绿色,没有任何瑕疵。\n手感:轻轻掐一下黄瓜,如果手感觉进入黄瓜时很脆嫩,而且有水分出来的黄瓜比较鲜嫩。\n刺:黄瓜的刺特别嫩才是质量口感好的黄瓜。看到黄瓜表皮的刺小而密,而且轻轻一摸就会碎断,这种黄瓜十分新鲜润滑。\n纵陵:选择黄瓜的时候要选择黄瓜的瓜体呈棒状带有纵陵的一类黄瓜。\n顶花:好的黄瓜,顶花会在黄瓜成熟后自然脱落,留下个小黄点。\n大小:太大的黄瓜一般是催熟的黄瓜,相对来说个头小的黄瓜比较好吃。", "一般人群均可食用。尤适宜[热病、肥胖、高血脂、水肿、癌症、嗜酒者、糖尿病 ]患者食用。", "[腰胃虚弱、腹痛腹泻、肺寒咳嗽、 肝病心血管病、肠胃病]患者慎食。", "蛋白质: 0.8克, 维生素A: 15微克, 胡萝卜素: 90微克", @@ -60,7 +62,7 @@ object LocalGuidesDataProvider { 6, R.drawable.tomatoes, "西红柿", - "https://bkimg.cdn.bcebos.com/pic/0823dd54564e9258d109b7bb1bd4c658ccbf6c81e012?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2U5Mg==,g_7,xp_5,yp_5/format,f_auto", + "1. 选择正常大小的西红柿,不要买畸形的西红柿,因为畸形的很有可能是打了一些对人体有害的药在里面,有助于西红柿快速的生长.\n2. 选择能从表皮面能看到西红柿内部的大概结构,这也能说明西红柿是很健康的.\n3. 选择西红柿顶端是有裂缝的,意思就是有一些小小的裂缝痕迹,这样也能说明西红柿没有打催生剂.\n4. 选择西红柿里面不是绿色的,因为现场也不能做好实验,可以先买一个回去试一试,切开看西红柿里面是不是绿色的,如果是绿色的,西红柿外表是红色的,这只能说明西红柿并未成熟,而是打了催生剂.\n5. 选择比较硬一点的西红柿,这样就比较新鲜,同时也容易放置,这样也就不容易坏掉了¹.", "一般人群均可食用。尤适宜于[热性病发热、口渴、食欲不振、习惯性牙龈出血、贫血、头晕心悸、高血压、急慢性肝炎、急慢性肾炎、夜盲症、近视眼]者食用", "[急性肠炎、菌痢、溃疡活动期]病人不宜食", "热量: 19千卡,蛋白质: 0.9 克, 碳水化合物: 4克", @@ -70,7 +72,7 @@ object LocalGuidesDataProvider { 7, R.drawable.carrots, "胡萝卜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/2.jpg", + "1、首先就要观察外表,看表皮有没有破损,甚至坑坑洼洼,掉皮的胡萝卜会让其水分蒸发,肯定会影响口感,并且不容易存放。\n2、把两个相同大小的胡萝卜拿在手里掂一下,如果有一个轻得多的就不要买了,说明放置的时间太长了,里面的水分减少,要买那种沉甸甸的胡萝卜,水分更充足,更新鲜好吃。所以大家买胡萝卜的时候最好用手感受一下重量。\n3、个头偏大的长得比较老,水分肯定少,口感不鲜嫩;而个头偏小的也不行,虽然很脆嫩,但是还没有完全长成熟,营养价值不高。挑胡萝卜尽量选中等个头,粗细均匀,带有根须的。\n", "一般人皆可食用,尤以[癌症、高血压、夜盲症干眼症、营养不良、皮肤粗糙]者为最。", "[饮酒过量]者不宜食用", "热量: 37千卡, 蛋白质: 1克, 碳水化合物: 8.8克, 膳食纤维3.2克, 胡萝卜素: 4130微克", @@ -80,7 +82,7 @@ object LocalGuidesDataProvider { 8, R.drawable.cucumbers, "黄瓜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/3.jpg", + "颜色:黄瓜的颜色应该是均匀的绿色,没有任何瑕疵。\n手感:轻轻掐一下黄瓜,如果手感觉进入黄瓜时很脆嫩,而且有水分出来的黄瓜比较鲜嫩。\n刺:黄瓜的刺特别嫩才是质量口感好的黄瓜。看到黄瓜表皮的刺小而密,而且轻轻一摸就会碎断,这种黄瓜十分新鲜润滑。\n纵陵:选择黄瓜的时候要选择黄瓜的瓜体呈棒状带有纵陵的一类黄瓜。\n顶花:好的黄瓜,顶花会在黄瓜成熟后自然脱落,留下个小黄点。\n大小:太大的黄瓜一般是催熟的黄瓜,相对来说个头小的黄瓜比较好吃。", "一般人群均可食用。尤适宜[热病、肥胖、高血脂、水肿、癌症、嗜酒者、糖尿病 ]患者食用。", "[腰胃虚弱、腹痛腹泻、肺寒咳嗽、 肝病心血管病、肠胃病]患者慎食。", "蛋白质: 0.8克, 维生素A: 15微克, 胡萝卜素: 90微克", @@ -90,7 +92,7 @@ object LocalGuidesDataProvider { 9, R.drawable.tomatoes, "西红柿", - "https://bkimg.cdn.bcebos.com/pic/0823dd54564e9258d109b7bb1bd4c658ccbf6c81e012?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2U5Mg==,g_7,xp_5,yp_5/format,f_auto", + "1. 选择正常大小的西红柿,不要买畸形的西红柿,因为畸形的很有可能是打了一些对人体有害的药在里面,有助于西红柿快速的生长.\n2. 选择能从表皮面能看到西红柿内部的大概结构,这也能说明西红柿是很健康的.\n3. 选择西红柿顶端是有裂缝的,意思就是有一些小小的裂缝痕迹,这样也能说明西红柿没有打催生剂.\n4. 选择西红柿里面不是绿色的,因为现场也不能做好实验,可以先买一个回去试一试,切开看西红柿里面是不是绿色的,如果是绿色的,西红柿外表是红色的,这只能说明西红柿并未成熟,而是打了催生剂.\n5. 选择比较硬一点的西红柿,这样就比较新鲜,同时也容易放置,这样也就不容易坏掉了¹.", "一般人群均可食用。尤适宜于[热性病发热、口渴、食欲不振、习惯性牙龈出血、贫血、头晕心悸、高血压、急慢性肝炎、急慢性肾炎、夜盲症、近视眼]者食用", "[急性肠炎、菌痢、溃疡活动期]病人不宜食", "热量: 19千卡,蛋白质: 0.9 克, 碳水化合物: 4克", @@ -100,7 +102,7 @@ object LocalGuidesDataProvider { 10, R.drawable.carrots, "胡萝卜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/2.jpg", + "1、首先就要观察外表,看表皮有没有破损,甚至坑坑洼洼,掉皮的胡萝卜会让其水分蒸发,肯定会影响口感,并且不容易存放。\n2、把两个相同大小的胡萝卜拿在手里掂一下,如果有一个轻得多的就不要买了,说明放置的时间太长了,里面的水分减少,要买那种沉甸甸的胡萝卜,水分更充足,更新鲜好吃。所以大家买胡萝卜的时候最好用手感受一下重量。\n3、个头偏大的长得比较老,水分肯定少,口感不鲜嫩;而个头偏小的也不行,虽然很脆嫩,但是还没有完全长成熟,营养价值不高。挑胡萝卜尽量选中等个头,粗细均匀,带有根须的。\n", "一般人皆可食用,尤以[癌症、高血压、夜盲症干眼症、营养不良、皮肤粗糙]者为最。", "[饮酒过量]者不宜食用", "热量: 37千卡, 蛋白质: 1克, 碳水化合物: 8.8克, 膳食纤维3.2克, 胡萝卜素: 4130微克", @@ -110,7 +112,7 @@ object LocalGuidesDataProvider { 11, R.drawable.cucumbers, "黄瓜", - "https://ai-public-console.cdn.bcebos.com/portal-pc-static/1694501570778/images/technology/imagerecognition/ingredient/3.jpg", + "颜色:黄瓜的颜色应该是均匀的绿色,没有任何瑕疵。\n手感:轻轻掐一下黄瓜,如果手感觉进入黄瓜时很脆嫩,而且有水分出来的黄瓜比较鲜嫩。\n刺:黄瓜的刺特别嫩才是质量口感好的黄瓜。看到黄瓜表皮的刺小而密,而且轻轻一摸就会碎断,这种黄瓜十分新鲜润滑。\n纵陵:选择黄瓜的时候要选择黄瓜的瓜体呈棒状带有纵陵的一类黄瓜。\n顶花:好的黄瓜,顶花会在黄瓜成熟后自然脱落,留下个小黄点。\n大小:太大的黄瓜一般是催熟的黄瓜,相对来说个头小的黄瓜比较好吃。", "一般人群均可食用。尤适宜[热病、肥胖、高血脂、水肿、癌症、嗜酒者、糖尿病 ]患者食用。", "[腰胃虚弱、腹痛腹泻、肺寒咳嗽、 肝病心血管病、肠胃病]患者慎食。", "蛋白质: 0.8克, 维生素A: 15微克, 胡萝卜素: 90微克", @@ -120,7 +122,7 @@ object LocalGuidesDataProvider { 12, R.drawable.tomatoes, "西红柿", - "https://bkimg.cdn.bcebos.com/pic/0823dd54564e9258d109b7bb1bd4c658ccbf6c81e012?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2U5Mg==,g_7,xp_5,yp_5/format,f_auto", + "1. 选择正常大小的西红柿,不要买畸形的西红柿,因为畸形的很有可能是打了一些对人体有害的药在里面,有助于西红柿快速的生长.\n2. 选择能从表皮面能看到西红柿内部的大概结构,这也能说明西红柿是很健康的.\n3. 选择西红柿顶端是有裂缝的,意思就是有一些小小的裂缝痕迹,这样也能说明西红柿没有打催生剂.\n4. 选择西红柿里面不是绿色的,因为现场也不能做好实验,可以先买一个回去试一试,切开看西红柿里面是不是绿色的,如果是绿色的,西红柿外表是红色的,这只能说明西红柿并未成熟,而是打了催生剂.\n5. 选择比较硬一点的西红柿,这样就比较新鲜,同时也容易放置,这样也就不容易坏掉了¹.", "一般人群均可食用。尤适宜于[热性病发热、口渴、食欲不振、习惯性牙龈出血、贫血、头晕心悸、高血压、急慢性肝炎、急慢性肾炎、夜盲症、近视眼]者食用", "[急性肠炎、菌痢、溃疡活动期]病人不宜食", "热量: 19千卡,蛋白质: 0.9 克, 碳水化合物: 4克", diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalPostsDataProvider.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalPostsDataProvider.kt index 7e26df7..f97b068 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalPostsDataProvider.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/data/local/LocalPostsDataProvider.kt @@ -9,19 +9,17 @@ object LocalPostsDataProvider { Post( 1, 1, - "白菜怎么挑选", + "标题-默认", R.drawable.cabbage, - labels = arrayListOf("挑选方法", "白菜"), - "大白菜一般菜心是黄色的,越往外菜叶绿色逐渐增多,黄色菜叶越多,大白菜越好吃。\n" + - "黄色菜叶多说明生长周期长,主要是大白菜生长时间越长,黄色菜叶越多,说明长势很好,包的也瓷实。\n" + - "黄色菜叶多说明农家肥用得多。一般来说,农家肥种的白菜个头大,菜瓷实,黄色菜叶比较多,吃起来口感也要好一些。颜色发绿的白菜叶片厚实,吃起来口感比较粗,特别耐储存。\n" + labels = "标签-默认", + "内容-默认" ), Post( 2, 2, "关于西瓜的挑选经验", R.drawable.watermelon, - labels = arrayListOf("西瓜", "经验"), + labels = "西瓜,经验", "1、看脐圈: 西瓜底部的脐圈,小、圆而且略微凹陷的西瓜,一般生长时间充分,果实会更甜。\n" + "2、辨颜色: 颜色最好挑青绿色,不要雾雾白白的。\n" + "3、看纹路: 西瓜纹路整齐的,就是好瓜。\n" + @@ -33,15 +31,15 @@ object LocalPostsDataProvider { 3, "生姜怎么挑选?", R.drawable.ginger, - labels = arrayListOf("生姜", "提问"), - "有没有好心人教教如何挑选生姜,大的和小的有什么区别" + labels = "生姜,经验", + "测试数据" ), Post( 4, 1, "白菜怎么挑选", R.drawable.cabbage, - labels = arrayListOf("挑选方法", "白菜"), + labels = "挑选方法,白菜", "大白菜一般菜心是黄色的,越往外菜叶绿色逐渐增多,黄色菜叶越多,大白菜越好吃。\n" + "黄色菜叶多说明生长周期长,主要是大白菜生长时间越长,黄色菜叶越多,说明长势很好,包的也瓷实。\n" + "黄色菜叶多说明农家肥用得多。一般来说,农家肥种的白菜个头大,菜瓷实,黄色菜叶比较多,吃起来口感也要好一些。颜色发绿的白菜叶片厚实,吃起来口感比较粗,特别耐储存。\n" @@ -51,7 +49,7 @@ object LocalPostsDataProvider { 2, "关于西瓜的挑选经验", R.drawable.watermelon, - labels = arrayListOf("西瓜", "经验"), + labels = "西瓜,经验", "1、看脐圈: 西瓜底部的脐圈,小、圆而且略微凹陷的西瓜,一般生长时间充分,果实会更甜。\n" + "2、辨颜色: 颜色最好挑青绿色,不要雾雾白白的。\n" + "3、看纹路: 西瓜纹路整齐的,就是好瓜。\n" + @@ -63,15 +61,15 @@ object LocalPostsDataProvider { 3, "生姜怎么挑选?", R.drawable.ginger, - labels = arrayListOf("生姜", "提问"), - "有没有好心人教教如何挑选生姜,大的和小的有什么区别" + labels = "生姜,经验", + "测试数据" ), Post( 7, 1, "白菜怎么挑选", R.drawable.cabbage, - labels = arrayListOf("挑选方法", "白菜"), + labels = "挑选方法,白菜", "大白菜一般菜心是黄色的,越往外菜叶绿色逐渐增多,黄色菜叶越多,大白菜越好吃。\n" + "黄色菜叶多说明生长周期长,主要是大白菜生长时间越长,黄色菜叶越多,说明长势很好,包的也瓷实。\n" + "黄色菜叶多说明农家肥用得多。一般来说,农家肥种的白菜个头大,菜瓷实,黄色菜叶比较多,吃起来口感也要好一些。颜色发绿的白菜叶片厚实,吃起来口感比较粗,特别耐储存。\n" @@ -81,7 +79,7 @@ object LocalPostsDataProvider { 2, "关于西瓜的挑选经验", R.drawable.watermelon, - labels = arrayListOf("西瓜", "经验"), + labels = "西瓜,经验", "1、看脐圈: 西瓜底部的脐圈,小、圆而且略微凹陷的西瓜,一般生长时间充分,果实会更甜。\n" + "2、辨颜色: 颜色最好挑青绿色,不要雾雾白白的。\n" + "3、看纹路: 西瓜纹路整齐的,就是好瓜。\n" + @@ -93,15 +91,15 @@ object LocalPostsDataProvider { 3, "生姜怎么挑选?", R.drawable.ginger, - labels = arrayListOf("生姜", "提问"), - "有没有好心人教教如何挑选生姜,大的和小的有什么区别" + labels = "生姜,经验", + "测试数据" ), Post( 10, 1, "白菜怎么挑选", R.drawable.cabbage, - labels = arrayListOf("挑选方法", "白菜"), + labels = "挑选方法,白菜", "大白菜一般菜心是黄色的,越往外菜叶绿色逐渐增多,黄色菜叶越多,大白菜越好吃。\n" + "黄色菜叶多说明生长周期长,主要是大白菜生长时间越长,黄色菜叶越多,说明长势很好,包的也瓷实。\n" + "黄色菜叶多说明农家肥用得多。一般来说,农家肥种的白菜个头大,菜瓷实,黄色菜叶比较多,吃起来口感也要好一些。颜色发绿的白菜叶片厚实,吃起来口感比较粗,特别耐储存。\n" @@ -111,7 +109,7 @@ object LocalPostsDataProvider { 2, "关于西瓜的挑选经验", R.drawable.watermelon, - labels = arrayListOf("西瓜", "经验"), + labels = "西瓜,经验", "1、看脐圈: 西瓜底部的脐圈,小、圆而且略微凹陷的西瓜,一般生长时间充分,果实会更甜。\n" + "2、辨颜色: 颜色最好挑青绿色,不要雾雾白白的。\n" + "3、看纹路: 西瓜纹路整齐的,就是好瓜。\n" + @@ -123,15 +121,15 @@ object LocalPostsDataProvider { 3, "生姜怎么挑选?", R.drawable.ginger, - labels = arrayListOf("生姜", "提问"), - "有没有好心人教教如何挑选生姜,大的和小的有什么区别" + labels = "生姜,经验", + "测试数据" ), Post( 14, 1, "白菜怎么挑选", R.drawable.cabbage, - labels = arrayListOf("挑选方法", "白菜"), + labels = "挑选方法,白菜", "大白菜一般菜心是黄色的,越往外菜叶绿色逐渐增多,黄色菜叶越多,大白菜越好吃。\n" + "黄色菜叶多说明生长周期长,主要是大白菜生长时间越长,黄色菜叶越多,说明长势很好,包的也瓷实。\n" + "黄色菜叶多说明农家肥用得多。一般来说,农家肥种的白菜个头大,菜瓷实,黄色菜叶比较多,吃起来口感也要好一些。颜色发绿的白菜叶片厚实,吃起来口感比较粗,特别耐储存。\n" @@ -141,7 +139,7 @@ object LocalPostsDataProvider { 2, "关于西瓜的挑选经验", R.drawable.watermelon, - labels = arrayListOf("西瓜", "经验"), + labels = "西瓜,经验", "1、看脐圈: 西瓜底部的脐圈,小、圆而且略微凹陷的西瓜,一般生长时间充分,果实会更甜。\n" + "2、辨颜色: 颜色最好挑青绿色,不要雾雾白白的。\n" + "3、看纹路: 西瓜纹路整齐的,就是好瓜。\n" + @@ -153,15 +151,15 @@ object LocalPostsDataProvider { 3, "生姜怎么挑选?", R.drawable.ginger, - labels = arrayListOf("生姜", "提问"), - "有没有好心人教教如何挑选生姜,大的和小的有什么区别" + labels = "生姜,经验", + "测试数据" ), Post( 17, 1, "白菜怎么挑选", R.drawable.cabbage, - labels = arrayListOf("挑选方法", "白菜"), + labels = "挑选方法,白菜", "大白菜一般菜心是黄色的,越往外菜叶绿色逐渐增多,黄色菜叶越多,大白菜越好吃。\n" + "黄色菜叶多说明生长周期长,主要是大白菜生长时间越长,黄色菜叶越多,说明长势很好,包的也瓷实。\n" + "黄色菜叶多说明农家肥用得多。一般来说,农家肥种的白菜个头大,菜瓷实,黄色菜叶比较多,吃起来口感也要好一些。颜色发绿的白菜叶片厚实,吃起来口感比较粗,特别耐储存。\n" @@ -171,7 +169,7 @@ object LocalPostsDataProvider { 2, "关于西瓜的挑选经验", R.drawable.watermelon, - labels = arrayListOf("西瓜", "经验"), + labels = "西瓜,经验", "1、看脐圈: 西瓜底部的脐圈,小、圆而且略微凹陷的西瓜,一般生长时间充分,果实会更甜。\n" + "2、辨颜色: 颜色最好挑青绿色,不要雾雾白白的。\n" + "3、看纹路: 西瓜纹路整齐的,就是好瓜。\n" + @@ -179,12 +177,20 @@ object LocalPostsDataProvider { "5、听声音: 将西瓜托在手里,手指轻轻敲,如果发出“咚、咚”的清脆声,并且托瓜的手有震动的感觉就是熟透的瓜,一般瓜味也会比较甜。" ), Post( - 19, + 12, 3, "生姜怎么挑选?", R.drawable.ginger, - labels = arrayListOf("生姜", "提问"), - "有没有好心人教教如何挑选生姜,大的和小的有什么区别" + labels = "生姜,经验", + "测试数据" + ), + Post( + 13, + 3, + "西红柿怎么挑选?", + R.drawable.ginger, + labels = "西红柿,经验", + "测试数据" ) ) } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/CommunityScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/CommunityScreen.kt index e9c222f..09bf496 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/CommunityScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/CommunityScreen.kt @@ -1,76 +1,206 @@ -@file:OptIn(ExperimentalMaterial3Api::class) +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class) package com.example.fruitandvegetableguide.ui.community +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.requiredWidth import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowForwardIos +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.data.Post +import com.example.fruitandvegetableguide.utils.postListInit +@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable fun CommunityScreen( - onClickToSearch: () -> Unit = {}, - onClickToPostDetail: () -> Unit = {}, - onClickToPostEdit: () -> Unit = {}, + userid: Int? = 1, + onClickToSearch: (search: String) -> Unit = {}, + onClickToPostDetail: (postId: Int) -> Unit = {}, + onClickToPostEdit: (userid: Int) -> Unit = {}, onClickBack: () -> Unit = {}, ) { - Column(modifier = Modifier.fillMaxSize()) { - TopAppBar( - modifier = Modifier.fillMaxWidth(), - title = { - Text( - text = "社区", - modifier = Modifier - .fillMaxWidth() - .wrapContentSize(Alignment.Center) - ) - }, - navigationIcon = { - IconButton(onClick = onClickBack) { - Icon( - imageVector = Icons.Default.ArrowBack, - contentDescription = null + val postList = postListInit() + Scaffold( + topBar = { + TopAppBar( + modifier = Modifier.fillMaxWidth(), + title = { + Text( + text = "社区", + modifier = Modifier + .fillMaxWidth() + .wrapContentSize(Alignment.Center) ) + }, + navigationIcon = { + IconButton(onClick = onClickBack) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = null + ) + } + }) + }, + bottomBar = { + BottomAppBar( + modifier = Modifier.fillMaxWidth() + ) { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(modifier = Modifier.requiredWidth(30.dp)) + Button(onClick = { onClickToPostEdit(userid ?: 1) }) { + Text(text = "分享我的经验") + } + Spacer(modifier = Modifier.requiredWidth(30.dp)) } - }) - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text(text = "社区页", modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToSearch, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - ) { - Text(text = "搜索页") + } + }) { + Column { + Spacer( + modifier = Modifier + .requiredHeight(60.dp) + ) + SearchBar(onClickToSearch = onClickToSearch) + + Spacer( + modifier = Modifier + .requiredHeight(10.dp) + .padding(it) + ) + + Posts(postsList = postList, onClickToPostDetail = onClickToPostDetail) } - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToPostDetail, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - ) { - Text(text = "帖子详情页") + } +} + +//搜索框 +@Composable +fun SearchBar( + modifier: Modifier = Modifier, + onClickToSearch: (search: String) -> Unit +) { + var search by remember { + mutableStateOf("") + } + TextField( + value = search, onValueChange = { str -> search = str }, + leadingIcon = { + IconButton(onClick = { + //路由不能传递空字符串 + if (search == "") { + search = " " + } + onClickToSearch(search) + }) { + Icon(imageVector = Icons.Default.Search, contentDescription = null) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + focusedContainerColor = MaterialTheme.colorScheme.surface + ), + placeholder = { Text(text = "搜索") }, + modifier = modifier + .fillMaxWidth() + .heightIn(56.dp) + ) +} + +//帖子列表的显示 +@Composable +fun Posts( + modifier: Modifier = Modifier, + onClickToPostDetail: (postId: Int) -> Unit = {}, + postsList: List +) { + LazyColumn( + modifier = modifier + .padding(vertical = 4.dp) + ) { + items(items = postsList) { post -> + Post(post = post, onClickToPostDetail = onClickToPostDetail) } - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToPostEdit, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + } +} + +//单个帖子的显示 +@Composable +fun Post( + post: Post, + onClickToPostDetail: (postId: Int) -> Unit = {}, +) { + Card( + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary), + modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) + ) { + Row( + modifier = Modifier + .padding(12.dp) + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ) ) { - Text(text = "帖子编辑页") + Column( + modifier = Modifier + .weight(1f) + .padding(12.dp) + ) { + Text( + text = post.title, + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text(text = post.labels) + } + Button(onClick = { + onClickToPostDetail(post.id) + }) { + Icon(imageVector = Icons.Filled.ArrowForwardIos, contentDescription = null) + } } } } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostDetailScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostDetailScreen.kt index c1ad9de..7bb39f9 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostDetailScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostDetailScreen.kt @@ -8,27 +8,39 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.data.Post +import com.example.fruitandvegetableguide.utils.getPostByPostId @Composable -fun PostDetailScreen(onClickBack: () -> Unit) { - Column(modifier = Modifier.fillMaxSize()) { +fun PostDetailScreen( + postId: Int? = 1, + onClickBack: () -> Unit +) { + val scrollState = rememberScrollState() + val post: Post = getPostByPostId(postId ?: 1) + Column(modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState)) { TopAppBar( modifier = Modifier.fillMaxWidth(), title = { Text( - text = "PostTitle", + text = post.title, modifier = Modifier .fillMaxWidth() .wrapContentSize(Alignment.Center) @@ -42,7 +54,23 @@ fun PostDetailScreen(onClickBack: () -> Unit) { ) } }) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "标签", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = post.labels, + ) + Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text(text = "帖子详情页", modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) + Text( + text = "内容", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = post.content + ) } } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostEditScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostEditScreen.kt index 0bb7eb2..24288f8 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostEditScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/community/PostEditScreen.kt @@ -2,30 +2,45 @@ package com.example.fruitandvegetableguide.ui.community +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.utils.postInsert @Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable fun PostEditScreen( + userid: Int? = 1, onClickBack: () -> Unit = {}, ) { + val context = LocalContext.current Column(modifier = Modifier.fillMaxSize()) { TopAppBar( modifier = Modifier.fillMaxWidth(), @@ -46,6 +61,67 @@ fun PostEditScreen( } }) Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text(text = "帖子编辑页", modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) + + var title by remember { mutableStateOf("") } + var labels by remember { mutableStateOf("") } + var content by remember { mutableStateOf("") } + + Text( + text = "标题", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + TextField( + value = title, + onValueChange = { str -> title = str }, + modifier = Modifier.fillMaxWidth(1f) + ) + + Text( + text = "标签", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + TextField( + value = labels, + onValueChange = { str -> labels = str }, + modifier = Modifier.fillMaxWidth(1f) + ) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "内容", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + TextField( + value = content, + onValueChange = { str -> content = str }, + modifier = Modifier.fillMaxWidth(1f) + ) + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button( + modifier = Modifier.padding(vertical = 24.dp), + onClick = { + if ( + title != "" && labels != "" && content != "" && postInsert( + userid = userid ?: 1, + title = title, + labels = labels, + content = content + ) + ) { + Toast.makeText(context, "发布成功", Toast.LENGTH_SHORT).show() + onClickBack() + } else { + Toast.makeText(context, "发布失败", Toast.LENGTH_SHORT).show() + } + } + ) { + Text("发布") + } + } } } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideDetailScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideDetailScreen.kt index da8fb92..b0115d1 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideDetailScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideDetailScreen.kt @@ -8,27 +8,42 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.data.Guide +import com.example.fruitandvegetableguide.utils.getGuideByGuideId +//@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable -fun GuideDetailScreen(onClickBack: () -> Unit) { - Column(modifier = Modifier.fillMaxSize()) { +fun GuideDetailScreen( + guideId: Int? = 1, + onClickBack: () -> Unit +) { + val guide: Guide = getGuideByGuideId(guideId ?: 1) + val scrollState = rememberScrollState() + Column(modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState)) { TopAppBar( modifier = Modifier.fillMaxWidth(), title = { Text( - text = "GuideTitle", + text = guide.kind, modifier = Modifier .fillMaxWidth() .wrapContentSize(Alignment.Center) @@ -43,6 +58,47 @@ fun GuideDetailScreen(onClickBack: () -> Unit) { } }) Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text(text = "指南详情页", modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) + Text( + text = "挑选方法", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.identify, + ) + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "适宜人群", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.suitable, + ) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "食用禁忌", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.taboo, + ) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "营养价值", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.nutritiveValue + ) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "推荐菜品", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.recommendedMenu, + ) } } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideScreen.kt index 5b9f6ab..54d94f3 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/guide/GuideScreen.kt @@ -2,31 +2,53 @@ package com.example.fruitandvegetableguide.ui.guide +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowForwardIos +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.data.Guide +import com.example.fruitandvegetableguide.utils.guideListInit @Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable fun GuideScreen( - onClickToSearch: () -> Unit = {}, - onClickToGuideDetail: () -> Unit = {}, + onClickToSearch: (search: String) -> Unit = {}, + onClickToGuideDetail: (guideId: Int) -> Unit = {}, onClickBack: () -> Unit = {}, ) { Column(modifier = Modifier.fillMaxSize()) { @@ -48,21 +70,97 @@ fun GuideScreen( ) } }) + SearchBar(onClickToSearch = onClickToSearch) Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text(text = "指南页", modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToSearch, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - ) { - Text(text = "搜索页") + Guides(onClickToGuideDetail = onClickToGuideDetail) + } +} + +//搜索框 +@Composable +fun SearchBar( + modifier: Modifier = Modifier, + onClickToSearch: (search: String) -> Unit +) { + var search by remember { + mutableStateOf("") + } + TextField( + value = search, onValueChange = { str -> search = str }, + leadingIcon = { + IconButton(onClick = { + //路由不能传递空字符串 + if (search == "") { + search = " " + } + onClickToSearch(search) + }) { + Icon(imageVector = Icons.Default.Search, contentDescription = null) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + focusedContainerColor = MaterialTheme.colorScheme.surface + ), + placeholder = { Text(text = "搜索") }, + modifier = modifier + .fillMaxWidth() + .heightIn(56.dp) + ) +} + +//指南列表的显示 +@Composable +fun Guides( + modifier: Modifier = Modifier, + onClickToGuideDetail: (guideId: Int) -> Unit = {} +) { + LazyColumn( + modifier = modifier + .padding(vertical = 4.dp) + ) { + items(guideListInit()) { guide -> + Guide(guide = guide, onClickToGuideDetail = onClickToGuideDetail) } - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToGuideDetail, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + } +} + +//单个指南的显示 +@Composable +fun Guide( + guide: Guide, + onClickToGuideDetail: (guideId: Int) -> Unit = {} +) { + Card( + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary), + modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) + ) { + Row( + modifier = Modifier + .padding(12.dp) + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ) ) { - Text(text = "指南详情页") + Column( + modifier = Modifier + .weight(1f) + .padding(12.dp) + ) { + Text( + text = guide.kind, + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text(text = guide.suitable) + } + Button(onClick = { + onClickToGuideDetail(guide.id) + }) { + Icon(imageVector = Icons.Filled.ArrowForwardIos, contentDescription = null) + } } } } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/PhotographScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/PhotographScreen.kt index b1c93c1..ed1e0da 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/PhotographScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/PhotographScreen.kt @@ -1,46 +1,134 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - package com.example.fruitandvegetableguide.ui.imgrecognition +import android.net.Uri +import android.os.Build +import android.util.Log +import android.widget.Toast +import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.requiredHeight -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.android.example.camerx2.camerax.PhotoComponent +import com.example.fruitandvegetableguide.ui.imgrecognition.path.Path +import com.google.accompanist.placeholder.PlaceholderHighlight +import com.google.accompanist.placeholder.material.placeholder +import com.google.accompanist.placeholder.material.shimmer +@RequiresApi(Build.VERSION_CODES.N) @Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable -fun PhotographScreen(onClickToRecognizeResult: () -> Unit = {}) { - Column(modifier = Modifier.fillMaxSize()) { - TopAppBar( - modifier = Modifier.fillMaxWidth(), - title = { - Text( - text = "拍照", - modifier = Modifier - .fillMaxWidth() - .wrapContentSize(Alignment.Center) - ) - }) - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text(text = "拍照页", modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button(onClick = onClickToRecognizeResult,modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) { - Text(text = "识别") +fun PhotographScreen( + onClickToRecognizeResult: (localImgPath: String) -> Unit = {}, + onClickBack: () -> Unit = {} +) { + val mediaAction by lazy { PhotoComponent.instance } + var localImgPath by remember { + mutableStateOf(Uri.EMPTY) + } + //1代表拍照,2代表相册选择 + var pathFlag by remember { + mutableIntStateOf(0) + } + val context = LocalContext.current + + Surface(modifier = Modifier.fillMaxSize()) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(modifier = Modifier.height(24.dp)) + AsyncImage( + model = localImgPath, contentDescription = null, + modifier = Modifier + .width(200.dp) + .height(200.dp) + .clip(CircleShape) + .placeholder( + visible = localImgPath == Uri.EMPTY, + color = Color(231, 234, 239, 255), + highlight = PlaceholderHighlight.shimmer(), + ), + contentScale = ContentScale.Crop, + ) + + Spacer(modifier = Modifier.height(24.dp)) + TextButton( + onClick = { + mediaAction.takePhoto() + pathFlag = 1 + }, + ) { + Text(text = "拍照") + } + Spacer(modifier = Modifier.height(12.dp)) + TextButton( + onClick = { + mediaAction.selectImage() + pathFlag = 2 + }, + ) { + Text(text = "相册") + } + Spacer(modifier = Modifier.height(12.dp)) + Button(onClick = { + if (pathFlag == 0 || localImgPath == null || localImgPath.path == "") { + Toast.makeText(context, "请先选择图片", Toast.LENGTH_SHORT).show() + } else { + val path: String + if (pathFlag == 1) { +// /data/data/com.android.example.camerax2 +// Do not hardcode "`/data/`"; use `Context.getFilesDir().getPath()` instead + path = "${context.dataDir}${localImgPath.path}" +// Log.d("photograph", "拍照Path:${context.dataDir}") + Log.d("photograph", "拍照Path:${path}") + } else { + path = Path.getPath(context, localImgPath) ?: "null" + Log.d("photograph", "相册Path:${path}") + } + val transPath = path.replace('/', '¥') + onClickToRecognizeResult(transPath) + } + }) { + Text(text = "识别") + } } } + mediaAction.Register( + galleryCallback = { + Log.d("photograph", "相册内容${it}") + if (it.isSuccess) { + localImgPath = it.uri + } + }, + graphCallback = { + Log.d("photograph", "拍照内容${it}") + if (it.isSuccess) { + localImgPath = it.uri + } + }, + permissionRationale = { + //权限拒绝的处理 + Toast.makeText(context, "您拒绝了相机相册权限", Toast.LENGTH_SHORT).show() + onClickBack() + } + ) } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/RecognizeResultScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/RecognizeResultScreen.kt index 082b3e5..bcc1fa0 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/RecognizeResultScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/RecognizeResultScreen.kt @@ -2,27 +2,57 @@ package com.example.fruitandvegetableguide.ui.imgrecognition +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.util.Log import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.ui.imgrecognition.bauduai.Ingredient +import com.example.fruitandvegetableguide.utils.getGuideByKind +import org.json.JSONObject +import kotlin.concurrent.thread @Composable -fun RecognizeResultScreen(onClickBack: () -> Unit = {}) { +fun RecognizeResultScreen( + transPath: String? = "", + onClickBack: () -> Unit = {} +) { + var result by remember { + mutableStateOf("") + } + var alreadyRequested by remember { + mutableStateOf(false) + } + var waiting by remember { + mutableStateOf(true) + } + val messageCode = 2342343 Column(modifier = Modifier.fillMaxSize()) { TopAppBar( modifier = Modifier.fillMaxWidth(), @@ -43,6 +73,128 @@ fun RecognizeResultScreen(onClickBack: () -> Unit = {}) { } }) Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text(text = "识别结果页", modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) + + var path = transPath ?: "null" + path = path.replace('¥', '/') + + /* Text( + text = "path是:$path", + modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + )*/ + if (path != "null") { + if (!alreadyRequested && path != "") { + val handler = object : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + if (msg.what == messageCode) { + result = msg.obj.toString() + waiting = false + } + } + } + thread { + val msg = Message() + msg.what = messageCode + msg.obj = Ingredient.ingredient(path) + handler.sendMessage(msg) + } + alreadyRequested = true + } + if (waiting) { + Text(text = "正在等待结果") + } else { + ShowResult(result) + } + } + Log.d("photograph", "RecognizeResultScreen: $result") + } +} + +@Composable +fun ShowResult( + result: String +) { + val resultJSON = JSONObject(result) + val confidence: Double + val kind: String + if (getResult(result = result)) { + confidence = resultJSON.getJSONArray("result").getJSONObject(0).getDouble("score") + kind = resultJSON.getJSONArray("result").getJSONObject(0).getString("name") + val guideList = getGuideByKind(kind) + if (guideList.isEmpty()) { + Text(text = result) + } else { + val guide = guideList[0] + val scrollState = rememberScrollState() + Column(modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState)) { + Row( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = kind, + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text(text = "置信度:${confidence}") + } + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "挑选方法", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.identify, + ) + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "适宜人群", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.suitable, + ) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "食用禁忌", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.taboo, + ) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "营养价值", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.nutritiveValue + ) + + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Text( + text = "推荐菜品", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = guide.recommendedMenu, + ) + } + } + } else { + Text(text = result) + } +} + +private fun getResult(result: String): Boolean { + val resultJSON = JSONObject(result) + return try { + resultJSON.getJSONArray("result").getJSONObject(0).getDouble("score") + resultJSON.getJSONArray("result").getJSONObject(0).getString("name") + true + } catch (e: RuntimeException) { + false } } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/Base64Util.java b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/Base64Util.java new file mode 100644 index 0000000..de544b3 --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/Base64Util.java @@ -0,0 +1,65 @@ +package com.example.fruitandvegetableguide.ui.imgrecognition.bauduai; + +/** + * Base64 工具类 + */ +public class Base64Util { + private static final char last2byte = (char) Integer.parseInt("00000011", 2); + private static final char last4byte = (char) Integer.parseInt("00001111", 2); + private static final char last6byte = (char) Integer.parseInt("00111111", 2); + private static final char lead6byte = (char) Integer.parseInt("11111100", 2); + private static final char lead4byte = (char) Integer.parseInt("11110000", 2); + private static final char lead2byte = (char) Integer.parseInt("11000000", 2); + private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + + public Base64Util() { + } + + public static String encode(byte[] from) { + StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3); + int num = 0; + char currentByte = 0; + + int i; + for (i = 0; i < from.length; ++i) { + for (num %= 8; num < 8; num += 6) { + switch (num) { + case 0: + currentByte = (char) (from[i] & lead6byte); + currentByte = (char) (currentByte >>> 2); + case 1: + case 3: + case 5: + default: + break; + case 2: + currentByte = (char) (from[i] & last6byte); + break; + case 4: + currentByte = (char) (from[i] & last4byte); + currentByte = (char) (currentByte << 2); + if (i + 1 < from.length) { + currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6); + } + break; + case 6: + currentByte = (char) (from[i] & last2byte); + currentByte = (char) (currentByte << 4); + if (i + 1 < from.length) { + currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4); + } + } + + to.append(encodeTable[currentByte]); + } + } + + if (to.length() % 4 != 0) { + for (i = 4 - to.length() % 4; i > 0; --i) { + to.append("="); + } + } + + return to.toString(); + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/FileUtil.java b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/FileUtil.java new file mode 100644 index 0000000..797a961 --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/FileUtil.java @@ -0,0 +1,72 @@ +package com.example.fruitandvegetableguide.ui.imgrecognition.bauduai; + +import java.io.*; + +/** + * 文件读取工具类 + */ +public class FileUtil { + + /** + * 读取文件内容,作为字符串返回 + */ + public static String readFileAsString(String filePath) throws IOException { + File file = new File(filePath); + if (!file.exists()) { + throw new FileNotFoundException(filePath); + } + + if (file.length() > 1024 * 1024 * 1024) { + throw new IOException("File is too large"); + } + + StringBuilder sb = new StringBuilder((int) (file.length())); + // 创建字节输入流 + FileInputStream fis = new FileInputStream(filePath); + // 创建一个长度为10240的Buffer + byte[] bbuf = new byte[10240]; + // 用于保存实际读取的字节数 + int hasRead = 0; + while ( (hasRead = fis.read(bbuf)) > 0 ) { + sb.append(new String(bbuf, 0, hasRead)); + } + fis.close(); + return sb.toString(); + } + + /** + * 根据文件路径读取byte[] 数组 + */ + public static byte[] readFileByBytes(String filePath) throws IOException { + File file = new File(filePath); + if (!file.exists()) { + throw new FileNotFoundException(filePath); + } else { + ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length()); + BufferedInputStream in = null; + + try { + in = new BufferedInputStream(new FileInputStream(file)); + short bufSize = 1024; + byte[] buffer = new byte[bufSize]; + int len1; + while (-1 != (len1 = in.read(buffer, 0, bufSize))) { + bos.write(buffer, 0, len1); + } + + byte[] var7 = bos.toByteArray(); + return var7; + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException var14) { + var14.printStackTrace(); + } + + bos.close(); + } + } + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/GsonUtils.java b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/GsonUtils.java new file mode 100644 index 0000000..2affb7b --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/GsonUtils.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. + */ +package com.example.fruitandvegetableguide.ui.imgrecognition.bauduai; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; + +import java.lang.reflect.Type; + +/** + * Json工具类. + */ +public class GsonUtils { + private static Gson gson = new GsonBuilder().create(); + + public static String toJson(Object value) { + return gson.toJson(value); + } + + public static T fromJson(String json, Class classOfT) throws JsonParseException { + return gson.fromJson(json, classOfT); + } + + public static T fromJson(String json, Type typeOfT) throws JsonParseException { + return (T) gson.fromJson(json, typeOfT); + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/HttpUtil.java b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/HttpUtil.java new file mode 100644 index 0000000..b1b470d --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/HttpUtil.java @@ -0,0 +1,77 @@ +package com.example.fruitandvegetableguide.ui.imgrecognition.bauduai; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.Map; + +/** + * http 工具类 + */ +public class HttpUtil { + + public static String post(String requestUrl, String accessToken, String params) + throws Exception { + String contentType = "application/x-www-form-urlencoded"; + return HttpUtil.post(requestUrl, accessToken, contentType, params); + } + + public static String post(String requestUrl, String accessToken, String contentType, String params) + throws Exception { + String encoding = "UTF-8"; + if (requestUrl.contains("nlp")) { + encoding = "GBK"; + } + return HttpUtil.post(requestUrl, accessToken, contentType, params, encoding); + } + + public static String post(String requestUrl, String accessToken, String contentType, String params, String encoding) + throws Exception { + String url = requestUrl + "?access_token=" + accessToken; + return HttpUtil.postGeneralUrl(url, contentType, params, encoding); + } + + public static String postGeneralUrl(String generalUrl, String contentType, String params, String encoding) + throws Exception { + URL url = new URL(generalUrl); + // 打开和URL之间的连接 + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + // 设置通用的请求属性 + connection.setRequestProperty("Content-Type", contentType); + connection.setRequestProperty("Connection", "Keep-Alive"); + connection.setUseCaches(false); + connection.setDoOutput(true); + connection.setDoInput(true); + + // 得到请求的输出流对象 + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); + out.write(params.getBytes(encoding)); + out.flush(); + out.close(); + + // 建立实际的连接 + connection.connect(); + // 获取所有响应头字段 + Map> headers = connection.getHeaderFields(); + // 遍历所有的响应头字段 + for (String key : headers.keySet()) { + System.err.println(key + "--->" + headers.get(key)); + } + // 定义 BufferedReader输入流来读取URL的响应 + BufferedReader in = null; + in = new BufferedReader( + new InputStreamReader(connection.getInputStream(), encoding)); + String result = ""; + String getLine; + while ((getLine = in.readLine()) != null) { + result += getLine; + } + in.close(); + System.err.println("result:" + result); + return result; + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/Ingredient.java b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/Ingredient.java new file mode 100644 index 0000000..ac034ca --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/bauduai/Ingredient.java @@ -0,0 +1,30 @@ +package com.example.fruitandvegetableguide.ui.imgrecognition.bauduai; + +import android.annotation.SuppressLint; +import android.util.Log; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class Ingredient { + private static final String requestUrl = "https://aip.baidubce.com/rest/2.0/image-classify/v1/classify/ingredient"; + private static final String accessToken = "24.f92314cba46ffeee34854093fa71966c.2592000.1700631460.282335-38996773"; + + public static String ingredient(String path) throws Exception { + try { + byte[] imgData = FileUtil.readFileByBytes(path); + String imgStr = Base64Util.encode(imgData); + String imgParam = URLEncoder.encode(imgStr, "UTF-8"); + String param = "image=" + imgParam; + String result = HttpUtil.post(requestUrl, accessToken, param); + Log.d("ingredient", "ingredient: " + result); + return result; + } catch (Exception e) { + e.printStackTrace(); + } + return "can not get result"; + } + +} \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/PhotoComponent.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/PhotoComponent.kt new file mode 100644 index 0000000..4a595f4 --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/PhotoComponent.kt @@ -0,0 +1,151 @@ +package com.android.example.camerx2.camerax + +import android.os.Build +import android.Manifest +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.runtime.getValue +import androidx.compose.runtime.saveable.rememberSaveable +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.isGranted +import com.google.accompanist.permissions.rememberMultiplePermissionsState +import com.google.accompanist.permissions.rememberPermissionState +import com.google.accompanist.permissions.shouldShowRationale +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + + +class PhotoComponent { + + private var openGalleryLauncher: ManagedActivityResultLauncher? = null + private var takePhotoLauncher: ManagedActivityResultLauncher? = null + + private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + companion object { + val instance get() = Helper.obj + } + + private object Helper { + val obj = PhotoComponent() + } + + //监听拍照权限flow + private val checkCameraPermission = + MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + + private fun setCheckCameraPermissionState(value: Boolean?) { + scope.launch { + checkCameraPermission.emit(value) + } + } + + //相册权限flow + private val checkGalleryImagePermission = + MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + + private fun setCheckGalleryPermissionState(value: Boolean?) { + scope.launch { + checkGalleryImagePermission.emit(value) + } + } + + /** + * @param galleryCallback 相册结果回调 + * @param graphCallback 拍照结果回调 + * @param permissionRationale 权限拒绝状态回调 + **/ + @OptIn(ExperimentalPermissionsApi::class) + @Composable + fun Register( + galleryCallback: (selectResult: PictureResult) -> Unit, + graphCallback: (graphResult: PictureResult) -> Unit, + permissionRationale: ((gallery: Boolean) -> Unit)? = null, + ) { + val rememberGraphCallback = rememberUpdatedState(newValue = graphCallback) + val rememberGalleryCallback = rememberUpdatedState(newValue = galleryCallback) + openGalleryLauncher = rememberLauncherForActivityResult(contract = SelectPicture()) { + rememberGalleryCallback.value.invoke(it) + } + takePhotoLauncher = rememberLauncherForActivityResult(contract = TakePhoto.instance) { + rememberGraphCallback.value.invoke(it) + } + val readGalleryPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + //13以上的权限申请 + Manifest.permission.READ_MEDIA_IMAGES + } else { + Manifest.permission.READ_EXTERNAL_STORAGE + } + var permissionCameraState by rememberSaveable { mutableStateOf(false) } + var permissionGalleryState by rememberSaveable { mutableStateOf(false) } + val permissionList = arrayListOf( + Manifest.permission.CAMERA, + readGalleryPermission, + ) + val galleryPermissionState = rememberPermissionState(readGalleryPermission) + val cameraPermissionState = rememberMultiplePermissionsState(permissionList) + LaunchedEffect(Unit) { + checkCameraPermission.collectLatest { + permissionCameraState = it == true + if (it == true) { + if (cameraPermissionState.allPermissionsGranted) { + setCheckCameraPermissionState(null) + takePhotoLauncher?.launch(null) + } else if (cameraPermissionState.shouldShowRationale) { + setCheckCameraPermissionState(null) + permissionRationale?.invoke(false) + } else { + cameraPermissionState.launchMultiplePermissionRequest() + } + } + } + } + LaunchedEffect(Unit) { + checkGalleryImagePermission.collectLatest { + permissionGalleryState = it == true + if (it == true) { + if (galleryPermissionState.status.isGranted) { + setCheckGalleryPermissionState(null) + openGalleryLauncher?.launch(null) + } else if (galleryPermissionState.status.shouldShowRationale) { + setCheckGalleryPermissionState(null) + permissionRationale?.invoke(true) + } else { + galleryPermissionState.launchPermissionRequest() + } + } + } + } + LaunchedEffect(cameraPermissionState.allPermissionsGranted) { + if (cameraPermissionState.allPermissionsGranted && permissionCameraState) { + setCheckCameraPermissionState(null) + takePhotoLauncher?.launch(null) + } + } + LaunchedEffect(galleryPermissionState.status.isGranted) { + if (galleryPermissionState.status.isGranted && permissionGalleryState) { + setCheckGalleryPermissionState(null) + openGalleryLauncher?.launch(null) + } + } + } + + //调用选择图片功能 + fun selectImage() { + setCheckGalleryPermissionState(true) + } + //调用拍照 + fun takePhoto() { + setCheckCameraPermissionState(true) + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/SelectPicture.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/SelectPicture.kt new file mode 100644 index 0000000..1d18699 --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/SelectPicture.kt @@ -0,0 +1,24 @@ +package com.android.example.camerx2.camerax + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContract + +class SelectPicture : ActivityResultContract() { + + private var context: Context? = null + + override fun createIntent(context: Context, input: Unit?): Intent { + this.context = context + return Intent(Intent.ACTION_PICK).setType("image/*") + } + + override fun parseResult(resultCode: Int, intent: Intent?): PictureResult { + return PictureResult(intent?.data, resultCode == Activity.RESULT_OK) + } +} +//图片结果 +class PictureResult(val uri: Uri?, val isSuccess: Boolean) + diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/TakePhoto.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/TakePhoto.kt new file mode 100644 index 0000000..52f2ab4 --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/camerax/TakePhoto.kt @@ -0,0 +1,55 @@ +package com.android.example.camerx2.camerax + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.MediaStore +import androidx.activity.result.contract.ActivityResultContract +import androidx.core.content.FileProvider +import java.io.File +import java.util.UUID + +class TakePhoto : ActivityResultContract() { + + var outUri: Uri? = null + private var imageName: String? = null + + companion object { + //定义单例的原因是因为拍照返回的时候页面会重新实例takePhoto,导致拍照的uri始终为空 + val instance get() = Helper.obj + } + + private object Helper { + val obj = TakePhoto() + } + + override fun createIntent(context: Context, input: Unit?): Intent = + Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { intent -> + getFileDirectory(context)?.let { + outUri = it + intent.putExtra(MediaStore.EXTRA_OUTPUT, it).apply { + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + } + + } + + override fun parseResult(resultCode: Int, intent: Intent?): PictureResult { + return PictureResult(outUri, resultCode == Activity.RESULT_OK) + } + + private fun getFileDirectory(context: Context): Uri? {//获取app内部文件夹 + imageName = "img_${UUID.randomUUID().toString().substring(0, 7)}" + val fileFolder = File(context.cacheDir, "test_imgs") + if (!fileFolder.exists()) { + fileFolder.mkdirs() + } + val file = File(fileFolder, "${imageName}.jpg") + if (!file.exists()) { + file.createNewFile() + } + return FileProvider.getUriForFile(context, "${context.packageName}.image.provider", file) + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/path/Path.java b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/path/Path.java new file mode 100644 index 0000000..ddf8776 --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/imgrecognition/path/Path.java @@ -0,0 +1,27 @@ +package com.example.fruitandvegetableguide.ui.imgrecognition.path; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.text.TextUtils; + +public class Path { + public static String getPath(Context context, Uri uri) { + String path = null; + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor == null) { + return null; + } + if (cursor.moveToFirst()) { + try { + path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)); + } catch (Exception e) { + e.printStackTrace(); + } + } + cursor.close(); + return path; + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/login/LoginScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/login/LoginScreen.kt index d991df7..445aa13 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/login/LoginScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/login/LoginScreen.kt @@ -49,7 +49,7 @@ import com.example.fruitandvegetableguide.utils.loginVerification @Composable fun LoginScreen( onClickToRegister: () -> Unit = {}, - onClickToMain: (Int) -> Unit = {} + onClickToMain: (userid: Int) -> Unit = {} ) { var userid by remember { mutableIntStateOf(-1) } var username by remember { mutableStateOf("") } diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/main/MainScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/main/MainScreen.kt index c66eca9..2b17937 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/main/MainScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/main/MainScreen.kt @@ -1,9 +1,7 @@ package com.example.fruitandvegetableguide.ui.main -import android.widget.ImageButton import androidx.annotation.DrawableRes import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -41,6 +39,10 @@ import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -49,30 +51,33 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.example.fruitandvegetableguide.data.local.LocalGuidesDataProvider.allGuides -import com.example.fruitandvegetableguide.data.local.LocalPostsDataProvider.allPosts import com.example.fruitandvegetableguide.ui.theme.FruitandVegetableGuideTheme +import com.example.fruitandvegetableguide.utils.guideListInit +import com.example.fruitandvegetableguide.utils.postListInit //主页 +@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable fun MainScreen( - userid: Int?, - onClickToSearch: () -> Unit = {}, - onClickToUser: () -> Unit = {}, + userid: Int? = 1, + onClickToSearch: (search: String) -> Unit = {}, + onClickToUser: (userid: Int) -> Unit = {}, onClickToPhotograph: () -> Unit = {}, onClickToGuide: () -> Unit = {}, - onClickToCommunity: () -> Unit = {}, - onClickToGuideDetail: () -> Unit = {}, - onClickToPostDetail: () -> Unit = {}, + onClickToCommunity: (userid: Int) -> Unit = {}, + onClickToGuideDetail: (guideId: Int) -> Unit = {}, + onClickToPostDetail: (postId: Int) -> Unit = {}, ) { - FruitandVegetableGuideTheme() { + FruitandVegetableGuideTheme { Scaffold(bottomBar = { MyBottomNavigation( + userid = userid ?: 1, onClickToUser = onClickToUser, onClickToPhotograph = onClickToPhotograph ) }) { padding -> HomeScreen( + userid = userid ?: 1, modifier = Modifier.padding(padding), onClickToSearch = onClickToSearch, onClickToGuide = onClickToGuide, @@ -87,12 +92,13 @@ fun MainScreen( //主屏幕 @Composable fun HomeScreen( + userid: Int, modifier: Modifier = Modifier, - onClickToSearch: () -> Unit, + onClickToSearch: (search: String) -> Unit, onClickToGuide: () -> Unit = {}, - onClickToCommunity: () -> Unit = {}, - onClickToGuideDetail: () -> Unit = {}, - onClickToPostDetail: () -> Unit = {}, + onClickToCommunity: (userid: Int) -> Unit = {}, + onClickToGuideDetail: (guideId: Int) -> Unit = {}, + onClickToPostDetail: (postId: Int) -> Unit = {}, ) { Column(modifier.verticalScroll(rememberScrollState())) { Spacer(modifier = Modifier.height(16.dp)) @@ -112,11 +118,9 @@ fun HomeScreen( } GuideGrid(onClickToGuideDetail = onClickToGuideDetail) } -// HomeSection(title = "指南") { -// GuideGrid() -// } + Column { - TextButton(onClick = onClickToCommunity) { + TextButton(onClick = { onClickToCommunity(userid) }) { Text( text = "社区", style = MaterialTheme.typography.titleMedium, @@ -127,9 +131,7 @@ fun HomeScreen( } PostGrid(onClickToPostDetail = onClickToPostDetail) } -// HomeSection(title = "社区") { -// PostGrid() -// } + Spacer(modifier = Modifier.height(16.dp)) } } @@ -138,12 +140,21 @@ fun HomeScreen( @Composable fun SearchBar( modifier: Modifier = Modifier, - onClickToSearch: () -> Unit + onClickToSearch: (search: String) -> Unit ) { + var search by remember { + mutableStateOf("") + } TextField( - value = "", onValueChange = {}, + value = search, onValueChange = { str -> search = str }, leadingIcon = { - IconButton(onClick = onClickToSearch) { + IconButton(onClick = { + //路由不能传递空字符串 + if (search == "") { + search = " " + } + onClickToSearch(search) + }) { Icon(imageVector = Icons.Default.Search, contentDescription = null) } }, @@ -163,14 +174,14 @@ fun SearchBar( fun GuideElement( modifier: Modifier = Modifier, @DrawableRes drawable: Int, + guideId: Int, kind: String, - onClickToGuideDetail: () -> Unit = {}, + onClickToGuideDetail: (guideId: Int) -> Unit = {}, ) { Button( - onClick = onClickToGuideDetail, + onClick = { onClickToGuideDetail(guideId) }, colors = ButtonDefaults.buttonColors( containerColor = Color.White, -// contentColor = Color.Black ), contentPadding = PaddingValues(1.dp) ) { @@ -200,10 +211,11 @@ fun GuideElement( //帖子元素 @Composable fun PostCard( + postId: Int, modifier: Modifier = Modifier, @DrawableRes drawable: Int, title: String, - onClickToPostDetail: () -> Unit = {}, + onClickToPostDetail: (postId: Int) -> Unit = {}, ) { Surface( shape = MaterialTheme.shapes.medium, @@ -215,7 +227,7 @@ fun PostCard( modifier = Modifier.width(255.dp) ) { Button( - onClick = onClickToPostDetail, + onClick = { onClickToPostDetail(postId) }, colors = ButtonDefaults.buttonColors( containerColor = Color.LightGray, ) @@ -231,7 +243,6 @@ fun PostCard( modifier = Modifier.padding(horizontal = 16.dp) ) } - } } } @@ -241,7 +252,7 @@ fun PostCard( @Composable fun GuideGrid( modifier: Modifier = Modifier, - onClickToGuideDetail: () -> Unit = {}, + onClickToGuideDetail: (guideId: Int) -> Unit = {}, ) { LazyHorizontalGrid( rows = GridCells.Fixed(2), @@ -250,8 +261,9 @@ fun GuideGrid( verticalArrangement = Arrangement.spacedBy(2.dp), modifier = modifier.height(300.dp) ) { - items(allGuides) { item -> + items(guideListInit()) { item -> GuideElement( + guideId = item.id, drawable = item.imgId, kind = item.kind, onClickToGuideDetail = onClickToGuideDetail @@ -264,7 +276,7 @@ fun GuideGrid( @Composable fun PostGrid( modifier: Modifier = Modifier, - onClickToPostDetail: () -> Unit = {}, + onClickToPostDetail: (postId: Int) -> Unit = {}, ) { LazyHorizontalGrid( rows = GridCells.Fixed(4), @@ -273,8 +285,9 @@ fun PostGrid( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = modifier.height(300.dp) ) { - items(allPosts) { item -> + items(postListInit()) { item -> PostCard( + postId = item.id, drawable = item.imgId, title = item.title, onClickToPostDetail = onClickToPostDetail @@ -283,26 +296,12 @@ fun PostGrid( } } -//小组件加上标题 -//@Composable -//fun HomeSection(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { -// Column(modifier) { -// Text( -// text = title, -// style = MaterialTheme.typography.titleMedium, -// modifier = Modifier -// .paddingFromBaseline(top = 40.dp, bottom = 16.dp) -// .padding(horizontal = 16.dp) -// ) -// content() -// } -//} - //底部导航栏 @Composable fun MyBottomNavigation( + userid: Int, modifier: Modifier = Modifier, - onClickToUser: () -> Unit = {}, + onClickToUser: (userid: Int) -> Unit = {}, onClickToPhotograph: () -> Unit = {}, ) { NavigationBar(containerColor = MaterialTheme.colorScheme.surfaceVariant, modifier = modifier) { @@ -333,14 +332,8 @@ fun MyBottomNavigation( ) }, label = { Text(text = "用户") }, selected = false, - onClick = onClickToUser + onClick = { onClickToUser(userid) } ) } } -//预览 -//@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) -//@Composable -//fun PostPrev() { -// MainScreen() -//} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/search/SearchScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/search/SearchScreen.kt index 6afbcf5..54edfcd 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/search/SearchScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/search/SearchScreen.kt @@ -1,34 +1,91 @@ -@file:OptIn(ExperimentalMaterial3Api::class) +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class) package com.example.fruitandvegetableguide.ui.search +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.waitForUpOrCancellation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.requiredWidth import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowForwardIos +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.data.Guide +import com.example.fruitandvegetableguide.data.Post +import com.example.fruitandvegetableguide.utils.getGuideByKind +import com.example.fruitandvegetableguide.utils.getPostByLabels @Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable fun SearchScreen( + search: String? = "", onClickBack: () -> Unit = {}, - onClickToGuideDetail: () -> Unit = {}, - onClickToPostDetail: () -> Unit = {} + onClickToGuideDetail: (guideId: Int) -> Unit = {}, + onClickToPostDetail: (postId: Int) -> Unit = {} ) { + var thisSearch by rememberSaveable { + mutableStateOf(search) + } + var guideClassify by remember { + mutableStateOf(true) + } //筛选指南 + var postClassify by remember { + mutableStateOf(false) + } //筛选帖子 + + //刷新,当再次点击搜索的时候,该值改变,触发刷新 + var refresh by remember { + mutableIntStateOf(1) + } + val guides = getGuideByKind(thisSearch ?: "") //搜索指南 + val posts = getPostByLabels(thisSearch ?: "") //搜索帖子 + Column(modifier = Modifier.fillMaxSize()) { TopAppBar( modifier = Modifier.fillMaxWidth(), @@ -48,24 +105,221 @@ fun SearchScreen( ) } }) - Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text( - text = "搜索结果页", - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + //搜索框 + TextField( + value = thisSearch ?: "", onValueChange = { str -> thisSearch = str }, + leadingIcon = { + IconButton(onClick = { refresh += 1 }) { + Icon(imageVector = Icons.Default.Search, contentDescription = null) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + focusedContainerColor = MaterialTheme.colorScheme.surface + ), + placeholder = { Text(text = "搜索") }, + modifier = Modifier + .fillMaxWidth() + .heightIn(56.dp) ) + + //筛选按钮 Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToGuideDetail, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() ) { - Text(text = "指南详情页") + Spacer(modifier = Modifier.requiredWidth(15.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + OutlinedButton( + onClick = { + postClassify = !postClassify + guideClassify = !guideClassify + }, + shape = RoundedCornerShape(12.dp), + contentPadding = PaddingValues(horizontal = 30.dp, vertical = 16.dp), + modifier = Modifier.bounceClick(), + enabled = postClassify && !guideClassify + ) { + Text(text = "筛选指南") + } + } + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + OutlinedButton( + onClick = { + guideClassify = !guideClassify + postClassify = !postClassify + }, + shape = RoundedCornerShape(12.dp), + contentPadding = PaddingValues(horizontal = 30.dp, vertical = 16.dp), + modifier = Modifier.bounceClick(), + enabled = guideClassify && !postClassify //不可用 + ) { + Text(text = "筛选帖子") + } + } + Spacer(modifier = Modifier.requiredWidth(15.dp)) } + + //显示结果列表 Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToPostDetail, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + if (guideClassify) { + Guides(guidesList = guides, onClickToGuideDetail = onClickToGuideDetail) + } + if (postClassify) { + Posts(postsList = posts, onClickToPostDetail = onClickToPostDetail) + } + } +} + + +//帖子列表的显示 +@Composable +fun Posts( + modifier: Modifier = Modifier, + onClickToPostDetail: (postId: Int) -> Unit = {}, + postsList: List +) { + LazyColumn(modifier = modifier.padding(vertical = 4.dp)) { + items(items = postsList) { post -> + Post( + post = post, + onClickToPostDetail = onClickToPostDetail + ) + } + } +} + +//单个帖子的显示 +@Composable +fun Post( + post: Post, + onClickToPostDetail: (postId: Int) -> Unit = {}, +) { + Card( + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary), + modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) + ) { + Row( + modifier = Modifier + .padding(12.dp) + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ) ) { - Text(text = "帖子详情页") + Column( + modifier = Modifier + .weight(1f) + .padding(12.dp) + ) { + Text( + text = post.title, + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text(text = post.labels) + } + Button(onClick = { + onClickToPostDetail(post.id) + }) { + Icon(imageVector = Icons.Filled.ArrowForwardIos, contentDescription = null) + } + } + } +} + +//指南列表的显示 +@Composable +fun Guides( + modifier: Modifier = Modifier, + onClickToGuideDetail: (guideId: Int) -> Unit = {}, + guidesList: List +) { + LazyColumn(modifier = modifier.padding(vertical = 4.dp)) { + items(items = guidesList) { guide -> + Guide( + guide = guide, + onClickToGuideDetail = onClickToGuideDetail + ) + } + } +} + +//单个指南的显示 +@Composable +fun Guide( + guide: Guide, + onClickToGuideDetail: (guideId: Int) -> Unit = {}, +) { + Card( + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary), + modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) + ) { + Row( + modifier = Modifier + .padding(12.dp) + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ) + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(12.dp) + ) { + Text( + text = guide.kind, + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text(text = guide.suitable) + } + Button(onClick = { + onClickToGuideDetail(guide.id) + }) { + Icon(imageVector = Icons.Filled.ArrowForwardIos, contentDescription = null) + } } } -} \ No newline at end of file +} + +//实现按钮点击效果 +enum class ButtonState { Pressed, Idle } + +fun Modifier.bounceClick() = composed { + var buttonState by remember { mutableStateOf(ButtonState.Idle) } + val scale by animateFloatAsState(if (buttonState == ButtonState.Pressed) 0.70f else 1f) + + this + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { } + ) + .pointerInput(buttonState) { + awaitPointerEventScope { + buttonState = if (buttonState == ButtonState.Pressed) { + waitForUpOrCancellation() + ButtonState.Idle + } else { + awaitFirstDown(false) + ButtonState.Pressed + } + } + } +} diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/MyPost.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/MyPost.kt new file mode 100644 index 0000000..0cd2f3e --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/MyPost.kt @@ -0,0 +1,16 @@ +package com.example.fruitandvegetableguide.ui.user + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + + +//一个类,用来方便显示我的帖子 +class MyPost( + val id: Int, + val title: String, + val content: String, + initialChecked: Boolean = false +) { + var checked: Boolean by mutableStateOf(initialChecked) +} \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/MyPostScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/MyPostScreen.kt index 8539336..a12ed5c 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/MyPostScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/MyPostScreen.kt @@ -1,35 +1,86 @@ -@file:OptIn(ExperimentalMaterial3Api::class) +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class) package com.example.fruitandvegetableguide.ui.user +import android.widget.Toast +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material3.Button +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.data.Post +import com.example.fruitandvegetableguide.utils.DeletePost +import com.example.fruitandvegetableguide.utils.getPostByPostId +import com.example.fruitandvegetableguide.utils.getPostByUserid +@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE) @Composable -fun MyPostScreen(onClickBack: () -> Unit = {}) { +fun MyPostScreen(userid: Int? = 1, onClickBack: () -> Unit = {}) { + val context = LocalContext.current + // 如果执行了删除操作,需要刷新页面 + val deleteFlag1 by remember { + mutableIntStateOf(0) + } + var deleteFlag2 by remember { + mutableIntStateOf(0) + } + + var myPosts = getPostByUserid(userid ?: 1) + + /*var id1 = "" + for (myPost in myPosts) { + id1 += myPost.id + } + Log.d("MyPostScreen", "MyPostScreen1111,the one myPost: \n${id1}")*/ + + //新建一个列表用于显示 + var postList = transMyPostList(myPosts) + + /*var id2 = "" + for (post in postList) { + id2 += post.id + } + Log.d("MyPostScreen", "MyPostScreen22222,the two postList: \n${id2}")*/ + + //记录选中的帖子 + var postSelected = mutableListOf().toMutableStateList() Column(modifier = Modifier.fillMaxSize()) { TopAppBar( modifier = Modifier.fillMaxWidth(), title = { Text( - text = "MyPost", + text = "我的帖子", modifier = Modifier .fillMaxWidth() .wrapContentSize(Alignment.Center) @@ -42,11 +93,102 @@ fun MyPostScreen(onClickBack: () -> Unit = {}) { contentDescription = null ) } + }, + actions = { + IconButton(onClick = { + if (DeletePost(postSelected)) { + postSelected = mutableListOf().toMutableStateList() //删除后,清空选中的帖子列表 + Toast.makeText(context, "删除成功", Toast.LENGTH_SHORT).show() + deleteFlag2 += 1 + } else { + Toast.makeText(context, "删除失败", Toast.LENGTH_SHORT).show() + } + }) { + Icon(imageVector = Icons.Default.Delete, contentDescription = null) + } }) Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text( - text = "我的帖子页", - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + + if (postList.isEmpty()) { + Text( + text = "您还没分享过经验", + modifier = Modifier + .fillMaxSize(), + textAlign = TextAlign.Center + ) + } else { + if (deleteFlag1 < deleteFlag2) { + myPosts = getPostByUserid(userid ?: 1) + postList = transMyPostList(myPosts) + deleteFlag2 = 0 + } + /*var id3 = "" + for (myPost in myPosts) { + id3 += myPost.id + } + Log.d("MyPostScreen", "MyPostScreen22222 the three: \n${id3}") + var id4 = "" + for (post in postList) { + id4 += post.id + } + Log.d("MyPostScreen", "MyPostScreen22222,the four postList: \n${id4}")*/ + + LazyColumn { + items(items = postList) { post -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .border(1.dp, Color.Black, RoundedCornerShape(8.dp)) + .padding(8.dp) + .fillMaxWidth() + ) { + Column { + Text( + modifier = Modifier + .padding(start = 16.dp), + text = post.title, + style = MaterialTheme.typography.headlineSmall + ) + Text( + modifier = Modifier + .padding(start = 16.dp), + text = post.content + ) + } + Checkbox( + checked = post.checked, + onCheckedChange = { checked -> + if (post.checked) { + postSelected.remove(getPostByPostId(post.id)) + } else { + postSelected.add(getPostByPostId(post.id)) + } + post.checked = checked + }) + } + Spacer(modifier = Modifier.requiredHeight(10.dp)) + } + } + } + } +} + +//转换列表,方便删除选择和删除 +private fun transMyPostList(myPosts: List): List { + var newMyPosts = mutableListOf() + for (myPost in myPosts) { + newMyPosts.add( + MyPost( + myPost.id, + myPost.title, + if (myPost.content.length > 15) { + myPost.content.substring(0, 15) + "……" + } else { + myPost.content + "……" + } + ) ) } + return newMyPosts } \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/UserScreen.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/UserScreen.kt index 07daf87..9e04881 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/UserScreen.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/ui/user/UserScreen.kt @@ -2,7 +2,9 @@ package com.example.fruitandvegetableguide.ui.user +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -11,33 +13,39 @@ import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Camera +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Password import androidx.compose.material.icons.filled.Spa -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import com.example.fruitandvegetableguide.R +import com.example.fruitandvegetableguide.utils.getUsernameByUserid @Composable fun UserScreen( + userid: Int? = 1, onClickToMain: () -> Unit = {}, - onClickToMyPost: () -> Unit = {}, + onClickToMyPost: (userid: Int) -> Unit = {}, onClickToPhotograph: () -> Unit = {}, ) { + val username: String = getUsernameByUserid(userid ?: 1) Scaffold(bottomBar = { MyBottomNavigation( + userid = userid ?: 1, onClickToMain = onClickToMain, onClickToPhotograph = onClickToPhotograph ) @@ -58,24 +66,50 @@ fun UserScreen( ) }) Spacer(modifier = Modifier.requiredHeight(10.dp)) - Text( - text = "用户页", - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - ) + Row { + Image( + painter = painterResource(R.drawable.user_circle), + contentDescription = null, + Modifier.padding(16.dp) + ) + Column { + Text( + text = "用户", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.ExtraBold) + ) + Text(text = "账号:$username") + } + } Spacer(modifier = Modifier.requiredHeight(10.dp)) - Button( - onClick = onClickToMyPost, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - ) { - Text(text = "我的帖子") + Row { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = null, + Modifier.padding(16.dp) + ) + OutlinedButton(onClick = { onClickToMyPost(userid ?: 1) }) { + Text(text = "我的帖子") + } + } + Spacer(modifier = Modifier.requiredHeight(10.dp)) + Row { + Icon( + imageVector = Icons.Default.Password, + contentDescription = null, + Modifier.padding(16.dp) + ) + OutlinedButton(onClick = { /*TODO*/ }) { + Text(text = "修改密码") + } } } } } -//Unable to match the desired swap behavior. + //底部导航栏 @Composable fun MyBottomNavigation( + userid: Int, modifier: Modifier = Modifier, onClickToMain: () -> Unit = {}, onClickToPhotograph: () -> Unit = {}, diff --git a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/utils/Utils.kt b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/utils/Utils.kt index 18d2484..e683a0d 100644 --- a/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/utils/Utils.kt +++ b/src/FruitandVegetableGuide/app/src/main/java/com/example/fruitandvegetableguide/utils/Utils.kt @@ -1,14 +1,27 @@ package com.example.fruitandvegetableguide.utils +import com.example.fruitandvegetableguide.R +import com.example.fruitandvegetableguide.data.Guide +import com.example.fruitandvegetableguide.data.Post +import com.example.fruitandvegetableguide.data.local.LocalAccountsDataProvider +import com.example.fruitandvegetableguide.data.local.LocalGuidesDataProvider +import com.example.fruitandvegetableguide.data.local.LocalPostsDataProvider + +// TODO 这里放页面逻辑相关代码,要自己写,通过调用数据库工具方法实现 +// 输入参数和返回参数不要改 -// TODO 这里放页面逻辑相关代码 fun loginVerification(username: String, password: String): Int { // TODO 1.登录验证 // 输入:username:用户名, password:密码 // 逻辑:查询用户表,用户名和密码正确返回id;用户名和密码不匹配返回-1;用户表中没有该用户名返回-1 // 输出:id或-1 - // 将下面的测试代码清空,完成需要的代码,之后的TODO同此 - return 1 + // 将下面的测试代码删除,完成需要的代码,之后的TODO同此 + if (password == "123456") { + if (username == "aaa") return 1 + if (username == "bbb") return 2 + if (username == "ccc") return 3 + } + return -1 } fun register(username: String, password: String): Boolean { @@ -16,6 +29,168 @@ fun register(username: String, password: String): Boolean { // 输入:username:用户名, password:密码 // 逻辑:插入用户表;插入成功返回true;插入失败返回false // 输出:true或false - // 将下面的测试代码清空,完成需要的代码,之后的TODO同此 + // 将下面的测试代码删除,完成需要的代码 return true } + +fun postListInit(): List { + // TODO 3.帖子列表初始化 + // 输入:无 + // 逻辑:查询post表,获取前100个帖子信息用于最初显示。 + // 查询的每一行帖子数据封装成Post数据类实例,即每一行数据信息都可以用来声明一个Post数据类对象,然后将这些对象封装成一个列表,使用mutableListOf + // 输出:列表 + // 将下面的测试代码删除,完成需要的代码 + return LocalPostsDataProvider.allPosts +} + +fun getPostByPostId(postId: Int): Post { + // TODO 4.根据id在post表里查找帖子 + // 输入:postId:要查找帖子的id + // 逻辑:查询帖子表,条件id==postId。 + // 查询的数据信息封装成一个Post数据类对象 + // 输出:Post对象 + // 将下面的测试代码删除,完成需要的代码 + for (post in LocalPostsDataProvider.allPosts) { + if (post.id == postId) { + return post + } + } + return LocalPostsDataProvider.allPosts[0] +} + +fun getGuideByKind(kind: String): List { + // TODO 5.根据kind在guide表里查找指南 + // 输入:kind:要查找指南的kind + // 逻辑:查询guide表,条件 输入的kind in guide表的kind。 + // 查询的每一行数据信息封装成一个Guide数据类对象 + // 输出:Guide的列表,没有则返回空列表 + // 将下面的测试代码删除,完成需要的代码 + if (kind == "") { + return guideListInit() + } + var guides = mutableListOf() + for (guide in LocalGuidesDataProvider.allGuides) { + if (kind in guide.kind) { + guides.add(guide) + } + } + return guides +} + +fun getPostByLabels(labels: String): List { + // TODO 6.根据labels在post表里查找帖子 + // 输入:labels:要查找帖子的labels + // 逻辑:查询post表,条件 输入的labels in post表的labels。 + // 查询的每一行数据信息封装成一个Post数据类对象 + // 输出:Post的列表 + // 将下面的测试代码删除,完成需要的代码 + if (labels == "") { + return postListInit() + } + var posts = mutableListOf() + for (post in LocalPostsDataProvider.allPosts) { + if (labels in post.labels) { + posts.add(post) + } + } + return posts +} + + +fun getGuideByGuideId(guideId: Int): Guide { + // TODO 7.根据id在guide表里查找指南 + // 输入:guideId:要查找指南的id + // 逻辑:查询指南表,条件id==guideId。 + // 查询的数据信息封装成一个Guide数据类对象 + // 输出:Guide对象 + // 将下面的测试代码删除,完成需要的代码 + for (guide in LocalGuidesDataProvider.allGuides) { + if (guide.id == guideId) { + return guide + } + } + return LocalGuidesDataProvider.allGuides[0] +} + +fun guideListInit(): List { + // TODO 8.指南列表初始化 + // 输入:无 + // 逻辑:查询guide表,获取前100个指南信息用于最初显示。 + // 查询的每一行指南数据封装成Guide数据类实例,即每一行数据信息都可以用来声明一个Guide数据类对象,然后将这些对象封装成一个列表,使用mutableListOf + // 输出:列表 + // 将下面的测试代码删除,完成需要的代码 + return LocalGuidesDataProvider.allGuides +} + +fun postInsert( + userid: Int, + title: String, + imgId: Int = R.drawable.loading, + labels: String, + content: String +): Boolean { + // TODO 9.插入新建的帖子 + // 输入:userid:用户id + // title:标题 + // imgId:舍弃图片,这里保持默认就行 + // labels:标签 + // content:内容 + // 逻辑:将传入的内容插入到post表。插入成功返回true,否则返回false + // 输出:true或false + // 将下面的测试代码删除,完成需要的代码 + val newPost = + Post(LocalPostsDataProvider.allPosts.size + 1, userid, title, imgId, labels, content) + try { + LocalPostsDataProvider.allPosts.add(newPost) + } catch (e: RuntimeException) { + return false + } + return true +} + +fun getUsernameByUserid(userid: Int): String { + // TODO 10.根据id在account表里查找用户用户名称 + // 输入:userid:要查找的用户的id + // 逻辑:查询账户表,条件id==userid。 + // 输出:username + // 将下面的测试代码删除,完成需要的代码 + for (user in LocalAccountsDataProvider.allUserAccounts) { + if (user.id == userid) { + return user.username + } + } + return LocalAccountsDataProvider.allUserAccounts[0].username +} + +fun getPostByUserid(userid: Int): List { + // TODO 11.根据userid在post表里查找帖子 + // 输入:userid:要查找帖子的userid + // 逻辑:查询post表,条件 userid == userid。 + // 查询的每一行数据信息封装成一个Post数据类对象 + // 输出:Post的列表 + // 将下面的测试代码删除,完成需要的代码 + var postList = mutableListOf() + for (post in LocalPostsDataProvider.allPosts) { + if (post.userId == userid) { + postList.add(post) + } + } + return postList +} + +fun DeletePost(postToDeleteList: List): Boolean { + // TODO 12.根据传入的要删除的帖子的列表,从数据库post表里删除帖子 + // 输入:postToDeleteList:要删除帖子的列表 + // 逻辑:遍历postToDeleteList,每次遍历,从数据库post表里删除帖子,sql语句条件可以为:DELETE FROM post WHERE id==post.id; + // 删除成功返回true,否则返回false + // 输出:true或false + // 将下面的测试代码删除,完成需要的代码 + try { + for (post in postToDeleteList) { + LocalPostsDataProvider.allPosts.remove(post) + } + } catch (e: RuntimeException) { + return false + } + return true +} \ No newline at end of file diff --git a/src/FruitandVegetableGuide/app/src/main/res/drawable/loading.png b/src/FruitandVegetableGuide/app/src/main/res/drawable/loading.png new file mode 100644 index 0000000..c6cfd21 Binary files /dev/null and b/src/FruitandVegetableGuide/app/src/main/res/drawable/loading.png differ diff --git a/src/FruitandVegetableGuide/app/src/main/res/drawable/todo_list.png b/src/FruitandVegetableGuide/app/src/main/res/drawable/todo_list.png new file mode 100644 index 0000000..6895a7b Binary files /dev/null and b/src/FruitandVegetableGuide/app/src/main/res/drawable/todo_list.png differ diff --git a/src/FruitandVegetableGuide/app/src/main/res/drawable/user_circle.png b/src/FruitandVegetableGuide/app/src/main/res/drawable/user_circle.png new file mode 100644 index 0000000..31d9460 Binary files /dev/null and b/src/FruitandVegetableGuide/app/src/main/res/drawable/user_circle.png differ diff --git a/src/FruitandVegetableGuide/app/src/main/res/xml/file_provider_paths.xml b/src/FruitandVegetableGuide/app/src/main/res/xml/file_provider_paths.xml new file mode 100644 index 0000000..f97985a --- /dev/null +++ b/src/FruitandVegetableGuide/app/src/main/res/xml/file_provider_paths.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file