@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<runningDeviceTargetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="RUNNING_DEVICE_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="SERIAL_NUMBER" />
|
||||
<value value="30467862410075A" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</runningDeviceTargetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2023-10-26T14:23:55.395630500Z" />
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
@ -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"
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
# 要做的事还有很多
|
||||

|
||||
|
||||
运行前,先放一张西红柿图片在相册中,方便测试
|
||||
|
||||
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
|
||||
```
|
||||
@ -0,0 +1,3 @@
|
||||
package com.example.fruitandvegetableguide.data.dbhelper
|
||||
|
||||
// TODO 这里放数据库工具方法,这些方法可以去网上找现成的
|
||||
@ -1,3 +0,0 @@
|
||||
package com.example.fruitandvegetableguide.data.dbutils
|
||||
|
||||
// TODO 这里放数据库工具方法
|
||||
@ -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<Post>
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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> T fromJson(String json, Class<T> classOfT) throws JsonParseException {
|
||||
return gson.fromJson(json, classOfT);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, Type typeOfT) throws JsonParseException {
|
||||
return (T) gson.fromJson(json, typeOfT);
|
||||
}
|
||||
}
|
||||
@ -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<String, List<String>> 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;
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Unit?, PictureResult>? = null
|
||||
private var takePhotoLauncher: ManagedActivityResultLauncher<Unit?, PictureResult>? = 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<Boolean?>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
private fun setCheckCameraPermissionState(value: Boolean?) {
|
||||
scope.launch {
|
||||
checkCameraPermission.emit(value)
|
||||
}
|
||||
}
|
||||
|
||||
//相册权限flow
|
||||
private val checkGalleryImagePermission =
|
||||
MutableSharedFlow<Boolean?>(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)
|
||||
}
|
||||
}
|
||||
@ -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<Unit?, PictureResult>() {
|
||||
|
||||
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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<paths>
|
||||
<external-path
|
||||
name="img_files"
|
||||
path="." />
|
||||
<cache-path
|
||||
name="cache"
|
||||
path="." />
|
||||
|
||||
</paths>
|
||||
Loading…
Reference in new issue