Dart SDK version is 3.7.0 1
dependencies: flutter: sdk: flutter permission_handler: ^11.0.1 # 权限管理 flutter_contacts: ^1.1.9+2 call_log: ^5.0.5 cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0
2 contact_and_calls_page.dart import '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; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); _requestPermissions(); } @override void 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)}秒'; } @override Widget 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 { @override Widget 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.compileSdkVersion ndkVersion = flutter.ndkVersion compileOptions { // Java 8 配置 sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 // 启用 Core Library Desugaring isCoreLibraryDesugaringEnabled = true } kotlinOptions { // 匹配 Java 版本 jvmTarget = JavaVersion.VERSION_1_8.toString() } defaultConfig { applicationId = "com.example.contactlist" minSdk = 21 // 确保 minSdk 不低于 21 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName // 启用 MultiDex multiDexEnabled = 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 = "../.." }