AtomGit Flutter鸿蒙客户端:仓库详情页 路由参数仓库详情页通过命名路由/repo进入接收owner和name参数// 导航到详情页Navigator.pushNamed(context,/repo,arguments:{owner:atomgit,name:flutter-ohos});// 详情页提取参数finalargsModalRoute.of(context)!.settings.argumentsasMapString,dynamic;finalownerargs[owner]asString;finalnameargs[name]asString;RepoDetailProviderProvider 在创建时即开始加载数据classRepoDetailProviderextendsChangeNotifier{finalAtomGitApiClient_apiClient;Repository?_repository;String?_readme;bool _isLoadingfalse;String?_error;Futurevoidload(Stringowner,Stringname)async{_isLoadingtrue;_errornull;notifyListeners();try{finalencodedOwnerUri.encodeComponent(owner);finalencodedNameUri.encodeComponent(name);finalresponseawait_apiClient.get(/repos/$encodedOwner/$encodedName,);finaldataparseMap(response.data);if(data!null){_repositoryRepository.fromJson(data);}_readmeawait_loadReadme(encodedOwner,encodedName,_repository?.defaultBranch??main);}onApiExceptioncatch(e){_errore.message;}catch(e){_error加载失败:$e;}finally{_isLoadingfalse;notifyListeners();}}}关键细节owner 和 name 都经过Uri.encodeComponent编码处理含特殊字符的仓库路径。README 加载README 加载采用静默失败策略 —— 没有 README 不报错只是不展示FutureString?_loadReadme(Stringowner,Stringrepo,Stringbranch)async{try{finalresponseawait_apiClient.get(/repos/$owner/$repo/readme,queryParams:{ref:branch},);finaldataparseMap(response.data);if(data!nulldata[content]isString){finalcontentdata[content]asString;returnutf8.decode(base64.decode(content));}}onApiException{// README 不存在是正常的不报错}catch(_){}returnnull;}API 返回的 README 内容是 Base64 编码的解码后用MarkdownViewer渲染。页面结构RepoDetailScreen 是 StatelessWidget通过 ChangeNotifierProvider 注入 ProviderclassRepoDetailScreenextendsStatelessWidget{overrideWidgetbuild(BuildContextcontext){finalargsModalRoute.of(context)!.settings.argumentsasMapString,dynamic;finalownerargs[owner]asString;finalnameargs[name]asString;returnChangeNotifierProvider(create:(_)RepoDetailProvider(context.readAtomGitApiClient())..load(owner,name),child:_RepoDetailBody(owner:owner,name:name),);}}自定义 Tab 栏不使用 Flutter 的 TabBar/TabBarView而是自定义 Row 按钮实现四个标签切换enum_DetailTab{code,issues,pulls,readme}class_RepoDetailBodyStateextendsState_RepoDetailBody{_DetailTab _currentTab_DetailTab.readme;Widget_buildTabBar(){returnMaterial(elevation:1,child:Padding(padding:constEdgeInsets.symmetric(horizontal:16,vertical:8),child:Row(children:[_TabButton(label:代码,isSelected:_currentTab_DetailTab.code,onTap:(){Navigator.pushNamed(context,/repo/code,arguments:{owner:widget.owner,name:widget.name,branch:provider.repository?.defaultBranch??main,});},),_TabButton(label:Issues,isSelected:_currentTab_DetailTab.issues,onTap:(){Navigator.pushNamed(context,/repo/issues,arguments:{owner:widget.owner,name:widget.name,type:issue,});},),_TabButton(label:PRs,isSelected:_currentTab_DetailTab.pulls,onTap:(){Navigator.pushNamed(context,/repo/pulls,arguments:{owner:widget.owner,name:widget.name,type:pr,});},),_TabButton(label:README,isSelected:_currentTab_DetailTab.readme,onTap:()setState(()_currentTab_DetailTab.readme),),],),),);}}每个按钮的选中态通过底部蓝色指示条实现class_TabButtonextendsStatelessWidget{finalStringlabel;finalbool isSelected;finalVoidCallbackonTap;Widgetbuild(BuildContextcontext){returnExpanded(child:GestureDetector(onTap:onTap,child:Column(children:[Text(label,style:TextStyle(color:isSelected?Theme.of(context).colorScheme.primary:null,fontWeight:isSelected?FontWeight.w600:null,)),if(isSelected)Container(height:2,margin:constEdgeInsets.only(top:4),color:Theme.of(context).colorScheme.primary,),],),),);}}头部信息区仓库名、描述、统计数据以卡片形式展示Widget_buildRepoHeader(Repositoryrepo){returnCard(margin:constEdgeInsets.all(16),child:Padding(padding:constEdgeInsets.all(16),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Row(children:[Icon(repo.isPrivate?Icons.lock_outline:Icons.book_outlined,size:20),constSizedBox(width:8),Expanded(child:Text(repo.fullName,style:Theme.of(context).textTheme.titleMedium)),]),if(repo.description!null)...[constSizedBox(height:8),Text(repo.description!),],constSizedBox(height:12),Row(children:[_StatItem(Icons.star_border,${repo.stargazersCount}),_StatItem(Icons.call_split,${repo.forksCount}),_StatItem(Icons.remove_red_eye_outlined,${repo.watchersCount}),_StatItem(Icons.error_outline,${repo.openIssuesCount}),]),],),),);}README 渲染Widget_buildReadme(RepoDetailProviderprovider){if(provider.readmenull||provider.readme!.isEmpty){returnconstCenter(child:Text(暂无 README));}returnSingleChildScrollView(padding:constEdgeInsets.all(16),child:MarkdownViewer(markdown:provider.readme!),);}状态处理三种状态的完整处理Widget_buildBody(RepoDetailProviderprovider){if(provider.error!nullprovider.repositorynull){returnErrorRetryWidget(message:provider.error!,onRetry:()provider.load(widget.owner,widget.name),);}if(provider.repositorynull){returnconstLoadingIndicator(message:加载仓库信息...);}returnColumn(children:[_buildTabBar(),Expanded(child:_buildReadme(provider)),]);}错误时展示可重试的错误页面加载中展示 LoadingIndicator成功后展示完整内容。