flutter 获取通话记录和通讯录

Dart SDK version is 3.7.01 
dependencies:flutter:sdk: flutterpermission_handler: ^11.0.1  # 权限管理flutter_contacts: ^1.1.9+2call_log: ^5.0.5cupertino_icons: ^1.0.8dev_dependencies:flutter_test:sdk: flutterflutter_lints: ^5.0.0
2  contact_and_calls_page.dartimport 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:call_log/call_log.dart';class ContactAndCallsPage extends StatefulWidget {@override_ContactAndCallsPageState createState() => _ContactAndCallsPageState();
}class _ContactAndCallsPageState extends State<ContactAndCallsPage>with SingleTickerProviderStateMixin {List<Contact> _contacts = [];Iterable<CallLogEntry> _callLogs = [];bool _isLoading = true;String _errorMessage = '';late TabController _tabController;@overridevoid initState() {super.initState();_tabController = TabController(length: 2, vsync: this);_requestPermissions();}@overridevoid dispose() {_tabController.dispose();super.dispose();}// 请求权限Future<void> _requestPermissions() async {setState(() {_isLoading = true;_errorMessage = '';});try {// Android 特定权限处理var contactStatus = await Permission.contacts.request();var phoneStatus = await Permission.phone.request();// 检查是否获得权限if (contactStatus.isGranted || phoneStatus.isGranted) {await _fetchContacts();await _fetchCallLogs();} else {setState(() {_errorMessage = '需要通讯录和通话记录权限以正常使用功能';});_showPermissionDeniedDialog();}} catch (e) {setState(() {_errorMessage = '权限请求发生错误: $e';});print('权限请求错误: $e');} finally {setState(() {_isLoading = false;});}}// 显示权限被拒绝的对话框void _showPermissionDeniedDialog() {showDialog(context: context,builder: (context) => AlertDialog(title: Text('权限受限'),content: Text(_errorMessage),actions: [TextButton(onPressed: () {Navigator.of(context).pop();openAppSettings(); // 打开应用设置},child: Text('打开设置'),),TextButton(onPressed: () => Navigator.of(context).pop(),child: Text('取消'),),],),);}// 获取通讯录联系人Future<void> _fetchContacts() async {try {// 多重权限检查bool permission = await FlutterContacts.requestPermission(readonly: true);if (permission) {// 添加详细配置获取联系人final contacts = await FlutterContacts.getContacts(withProperties: true,   // 获取联系人属性withPhoto: false,        // 不获取照片以提高性能sorted: true,            // 按名称排序);// 过滤掉没有电话号码的联系人final filteredContacts = contacts.where((contact) =>contact.phones.isNotEmpty).toList();setState(() {_contacts = filteredContacts;});print('获取到联系人数量: ${_contacts.length}');} else {setState(() {_errorMessage = '未获得通讯录权限';});}} catch (e) {print('获取通讯录错误: $e');setState(() {_errorMessage = '获取通讯录失败: $e';});}}// 获取通话记录Future<void> _fetchCallLogs() async {try {// 获取通话记录Iterable<CallLogEntry> callLogs = await CallLog.get();// 过滤最近30天的通话记录final now = DateTime.now();final thirtyDaysAgo = now.subtract(Duration(days: 30));setState(() {_callLogs = callLogs.where((log) {final logTime = DateTime.fromMillisecondsSinceEpoch(log.timestamp ?? 0);return logTime.isAfter(thirtyDaysAgo);}).take(100)  // 限制最多100条记录.toList();});print('获取到通话记录数量: ${_callLogs.length}');} catch (e) {print('获取通话记录错误: $e');setState(() {_errorMessage = '获取通话记录失败: $e';});}}// 转换通话类型String _getCallType(CallType? callType) {switch (callType) {case CallType.incoming:return '呼入';case CallType.outgoing:return '呼出';case CallType.missed:return '未接';default:return '未知';}}// 格式化通话记录时间String _formatCallLogTime(int? timestamp) {if (timestamp == null) return '未知时间';final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);return '${dateTime.year}-${_twoDigits(dateTime.month)}-${_twoDigits(dateTime.day)} ''${_twoDigits(dateTime.hour)}:${_twoDigits(dateTime.minute)}:${_twoDigits(dateTime.second)}';}// 补充两位数方法String _twoDigits(int n) {return n.toString().padLeft(2, '0');}// 格式化通话时长String _formatCallDuration(int? duration) {if (duration == null || duration == 0) return '未接通';final minutes = duration ~/ 60;final seconds = duration % 60;return '$minutes分${_twoDigits(seconds)}秒';}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('通讯录和通话记录'),bottom: TabBar(controller: _tabController,tabs: [Tab(icon: Icon(Icons.contacts), text: '通讯录'),Tab(icon: Icon(Icons.call), text: '通话记录'),],),actions: [IconButton(icon: Icon(Icons.refresh),onPressed: _requestPermissions, // 直接调用权限请求方法),],),body: _isLoading? Center(child: CircularProgressIndicator()): _errorMessage.isNotEmpty? Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.error_outline, color: Colors.red, size: 100),SizedBox(height: 20),Text(_errorMessage,style: TextStyle(color: Colors.red),textAlign: TextAlign.center,),SizedBox(height: 20),ElevatedButton(onPressed: _requestPermissions,child: Text('重新获取权限'),)],),): TabBarView(controller: _tabController,children: [_buildContactsList(),_buildCallLogsList(),],),);}// 构建通讯录列表Widget _buildContactsList() {if (_contacts.isEmpty) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.contacts, size: 100, color: Colors.grey),SizedBox(height: 20),Text('暂无联系人',style: TextStyle(fontSize: 18, color: Colors.grey),),],),);}return ListView.builder(itemCount: _contacts.length,itemBuilder: (context, index) {Contact contact = _contacts[index];return ListTile(leading: CircleAvatar(backgroundColor: Colors.primaries[index % Colors.primaries.length],child: Text(contact.name.first.isNotEmpty ? contact.name.first[0] : '?',style: TextStyle(fontWeight: FontWeight.bold,color: Colors.white,),),),title: Text(contact.name.first.isNotEmpty ? contact.name.first : '未命名',style: TextStyle(fontWeight: FontWeight.bold),),subtitle: Text(contact.phones.isNotEmpty? contact.phones.first.number: '无电话号码',style: TextStyle(color: Colors.grey[600]),),);},);}// 构建通话记录列表Widget _buildCallLogsList() {if (_callLogs.isEmpty) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.call, size: 100, color: Colors.grey),SizedBox(height: 20),Text('暂无通话记录',style: TextStyle(fontSize: 18, color: Colors.grey),),],),);}return ListView.builder(itemCount: _callLogs.length,itemBuilder: (context, index) {CallLogEntry callLog = _callLogs.elementAt(index);return ListTile(leading: Icon(callLog.callType == CallType.missed? Icons.call_missed: (callLog.callType == CallType.incoming? Icons.call_received: Icons.call_made),color: callLog.callType == CallType.missed? Colors.red: Colors.green,),title: Text(callLog.number ?? '未知号码'),subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [Text('时间: ${_formatCallLogTime(callLog.timestamp)}'),Text('类型: ${_getCallType(callLog.callType)}'),Text('通话时长: ${_formatCallDuration(callLog.duration)}'),],),);},);}
}  

3 main.dart

import 'package:flutter/material.dart';
import 'contact_and_calls_page.dart';void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: '通讯录和通话记录',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),home: ContactAndCallsPage(),);}
}

4   降级java sdk到1.8 ---   build.gradle.kts

plugins {id("com.android.application")id("kotlin-android")id("dev.flutter.flutter-gradle-plugin")
}android {namespace = "com.example.contactlist"compileSdk = flutter.compileSdkVersionndkVersion = flutter.ndkVersioncompileOptions {// Java 8 配置sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8// 启用 Core Library DesugaringisCoreLibraryDesugaringEnabled = true}kotlinOptions {// 匹配 Java 版本jvmTarget = JavaVersion.VERSION_1_8.toString()}defaultConfig {applicationId = "com.example.contactlist"minSdk = 21  // 确保 minSdk 不低于 21targetSdk = flutter.targetSdkVersionversionCode = flutter.versionCodeversionName = flutter.versionName// 启用 MultiDexmultiDexEnabled = true}buildTypes {release {signingConfig = signingConfigs.getByName("debug")}}
}dependencies {// 升级到 2.1.4 或更高版本coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")// MultiDex 支持implementation("androidx.multidex:multidex:2.0.1")
}flutter {source = "../.."
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/76580.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bash脚本手动清空mysql表数据

文章目录 1、bash脚本手动清空mysql表数据 1、bash脚本手动清空mysql表数据 #!/bin/bash# 配置区域&#xff08;修改此处&#xff09; MYSQL_USER"root" MYSQL_PASSWORD"123456" MYSQL_HOST"localhost" DATABASES("hps-base:base_test_ite…

Spark Core编程

一文读懂Spark Core编程核心要点 最近在学习大数据处理框架Spark&#xff0c;今天来给大家分享一下Spark Core编程中非常重要的内容&#xff0c;包括RDD算子、累加器和广播变量&#xff0c;希望能帮助大家更好地理解和掌握Spark编程。先来说说RDD算子&#xff0c;它是Spark编程…

SDP(一)

SDP(Session Description Protocol)会话描述协议相关参数 Session Description Protocol Version (v): 0 --说明&#xff1a;SDP当前版本号 Owner/Creator, Session Id (o): - 20045 20045 IN IP4 192.168.0.0 --说明&#xff1a;发起者/创建者 会话ID&#xff0c;那么该I…

HarmonyOS:组件布局保存至相册

一&#xff0c;需求背景 有这样一个需求&#xff0c;将页面上的某个自定义组件以图片的形式保存至相册。 二&#xff0c;需求拆解 根据需求分析&#xff0c;可将需求拆解成两步&#xff1a; 1&#xff0c;将组件转换成图片资源&#xff1b; 2&#xff0c;将图片保存到相册…

算法中的数论基础

算法中的数论基础 本篇文章适用于算法考试或比赛之前的临场复习记忆&#xff0c;没有复杂公式推理&#xff0c;基本上是知识点以及函数模版&#xff0c;涵盖取模操作、位运算的小技巧、组合数、概率期望、进制转换、最大公约数、最小公倍数、唯一分解定理、素数、快速幂等知识…

Redis下载稳定版本5.0.4

https://www.redis.net.cn/download/ Redis下载 Redis 版本号采用标准惯例:主版本号.副版本号.补丁级别,一个副版本号就标记为一个标准发行版本,例如 1.2,2.0,2.2,2.4,2.6,2.8,奇数的副版本号用来表示非标准版本,例如2.9.x发行版本是Redis 3.0标准版本的非标准发行版本…

‌UniApp 安卓打包完整步骤(小白向)

‌ ‌一、环境准备‌ ‌安装 HBuilderX‌ 下载最新版 HBuilderX 并安装&#xff08;官方 IDE&#xff0c;支持一键打包&#xff09;‌16确保已安装 Node.js&#xff08;用于依赖管理&#xff09;‌26 ‌配置 Android 开发环境‌ 安装 ‌Java JDK 17‌&#xff08;建议选择稳定…

【Springboot知识】Springboot配置加载机制深入解读

文章目录 配置加载概述**Spring Boot 配置加载机制详解****一、配置加载顺序&#xff08;优先级由低到高&#xff09;****二、关键配置机制说明****1. Profile 机制****2. 外部化配置****3. 配置属性绑定到 Bean****4. 动态覆盖配置** **三、配置加载流程图****2. 配置导入&…

AI图像生成

要通过代码实现AI图像生成&#xff0c;可以使用深度学习框架如TensorFlow、PyTorch或GANs等技术。下面是一个简单的示例代码&#xff0c;演示如何使用GANs生成手写数字图像&#xff1a; import torch import torchvision import torchvision.transforms as transforms import …

基于springboot的个人博客系统

一、系统架构 前端&#xff1a;html | bootstrap | jquery | css | ajax 后端&#xff1a;springboot | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、代码及数据 三、功能介绍 01. 注册 02. 登录 03. 管理后台-首页 04. 管理后台-文章-所有文…

BOTA六维力矩传感器如何打通机器人AI力控操作的三层架构?感知-决策-执行全链路揭秘

想象一下&#xff0c;你对着一个机器人说&#xff1a;“请帮我泡杯茶。”然后&#xff0c;它就真的开始行动了&#xff1a;找茶壶、烧水、取茶叶、泡茶……这一切看似简单&#xff0c;但背后却隐藏着复杂的AI技术。今天&#xff0c;我们就来揭秘BOTA六维力矩传感器在机器人操控…

ffmpeg播放音视频流程

文章目录 &#x1f3ac; FFmpeg 解码播放流程概览&#xff08;以音视频文件为例&#xff09;1️⃣ 创建结构体2️⃣ 打开音视频文件3️⃣ 查找解码器并打开解码器4️⃣ 循环读取数据包&#xff08;Packet&#xff09;5️⃣ 解码成帧&#xff08;Frame&#xff09;6️⃣ 播放 / …

在 Wireshark 中如何筛选数据包

1. 显示过滤器&#xff08;Display Filters&#xff09; 显示过滤器用于 在已捕获的数据包中筛选&#xff0c;语法类似于编程语言中的条件表达式。 &#xff08;1&#xff09;基本过滤 表达式说明ip.addr 192.168.1.1显示所有涉及 192.168.1.1 的 IP 包ip.src 192.168.1.1…

ES6 新增特性 箭头函数

简述&#xff1a; ECMAScript 6&#xff08;简称ES6&#xff09;是于2015年6月正式发布的JavaScript语言的标准&#xff0c;正式名为ECMAScript 2015&#xff08;ES2015&#xff09;。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序&#xff0c;成为企业级开发语…

Python数据可视化-第7章-绘制3D图表和统计地图

环境 开发工具 VSCode库的版本 numpy1.26.4 matplotlib3.10.1 ipympl0.9.7教材 本书为《Python数据可视化》一书的配套内容&#xff0c;本章为第7章 绘制3D图表和统计地图 本章首先介绍了使用mplot3d工具包绘制3D图表&#xff0c;然后介绍了使用animation模块制作动画&#…

【从零开始学习JVM | 第二篇】HotSpot虚拟机对象探秘

对象的创建 1.类加载检查 虚拟机遇到一条new的指令&#xff0c;首先去检查这个指令的参数能否在常量池中定位到这个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有&#xff0c;那必须先执行类的加载过程。 2.分配内存 在类…

Oracle 表空间高水位收缩全攻略

1. 概述 本文档是针对某个特定用户表空间收缩的文档&#xff0c;实际操作要结合生产库具体情况。主要包括以下几个流程&#xff1a; 收集当前数据库相关信息降低数据库表高水位线Resize 收缩数据文件 具体细节详见以下章节。 2. 时间规划 操作类型预估时间实际时间数据库信…

Pytest多环境切换实战:测试框架配置的最佳实践!

你是否也遇到过这种情况&#xff1a;本地测试通过&#xff0c;一到测试环境就翻车&#xff1f;环境变量错乱、接口地址混乱、数据源配置丢失……这些「环境切换」问题简直像定时炸弹&#xff0c;随时引爆你的测试流程&#xff01; 测试人员每天都跟不同的环境打交道&#xff0…

蓝桥杯赛前题

开始每个人能量为3 答题了&#xff0c;答题者1 扣分最后算 #include<bits/stdc.h> using namespace std;const int N1e510; int a[N]; int main(){int n,k,q;cin>>n>>k>>q;for(int i1;i<n;i){a[i]k; }for(int i1;i<q;i){int x;cin>>x;a[…

VSCode优雅的使用debug

原始用法&#xff1a;(这里不使用) 配置launch.json&#xff0c;里面传入参数然后debug&#xff0c;这里我们通常需要传入的参数比较多&#xff0c;而且经常修改参数&#xff0c;直接去修改launch.json会比较麻烦&#xff0c;所以使用sh脚本比较方便。 {// Use IntelliSense to…