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.pswp.cn/bicheng/76580.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76580.shtml
英文地址,請注明出處:http://en.pswp.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…