{"id":1299,"date":"2026-09-14T14:07:42","date_gmt":"2026-09-14T14:07:42","guid":{"rendered":"https:\/\/boochlin.com\/?p=1299"},"modified":"2026-09-19T16:21:27","modified_gmt":"2026-09-19T16:21:27","slug":"funny-vote-app-vibe-coding%e3%80%81ai-%e7%b5%90%e5%b0%8d%e8%88%87-firebase-%e9%9b%b2%e5%8e%9f%e7%94%9f%e7%b5%82%e5%b1%80%e9%9b%9c%e8%a8%98","status":"publish","type":"post","link":"https:\/\/boochlin.com\/?p=1299","title":{"rendered":"Funny vote APP \u2013 \u5f37\u8feb\u75c7\u7684 100% \u756b\u9762\u5fa9\u523b\uff1a\u628a\u5341\u5e74\u524d\u7684\u9752\u6625\u642c\u9032 Material 3\uff08\u516d\uff09"},"content":{"rendered":"<div class=\"wp-block-group\" style=\"border-left:4px solid #C14420;background:#f8f9fa;padding:14px 18px;margin-bottom:28px;border-radius:0 6px 6px 0\">\n<p style=\"margin:0;font-size:0.95em;color:#444\">\n    \u672c\u6587\u70ba<strong>\u300aFunny Vote \u5341\u5e74\u6f14\u9032\u7cfb\u5217\u300b<\/strong>\u7b2c 6 \u7bc7\uff08\u5168\u7cfb\u5217\u5171 8 \u7bc7\uff09\uff5c<a href=\"https:\/\/boochlin.com\/?page_id=1319\" style=\"font-weight:bold;color:#C14420;text-decoration:underline\">\u67e5\u770b\u5168\u7cfb\u5217\u67b6\u69cb\u5c0e\u89bd<\/a>\n  <\/p>\n<\/div>\n<p><img decoding=\"async\" src=\"..\/..\/assets\/images\/post1299_material3_banner.png\" alt=\"Funny Vote Material 3 \u756b\u9762\u5fa9\u523b\u65d7\u8266\u5c01\u9762\"><\/p>\n<p>\u67b6\u69cb\u642d\u597d\u4e86\uff0c\u72c0\u614b\u7ba1\u7dda\u901a\u4e86\uff0c\u7167\u7406\u8aaa\u5de5\u7a0b\u5e2b\u9019\u6642\u5019\u5c31\u53ef\u4ee5\u6253\u5361\u4e0b\u73ed\u3001\u5728\u5c65\u6b77\u4e0a\u5beb\u300c\u7cbe\u901a MVI \u8207 Compose \u5de5\u696d\u7d1a\u7ba1\u7dda\u300d\u4e86\u3002<\/p>\n<p>\u4f46\u7576\u6211\u628a\u4e0a\u4e00\u7bc7\u5beb\u597d\u7684 App \u62ff\u5728\u624b\u4e0a\u628a\u73a9\u6642\uff0c\u5fc3\u88e1\u90a3\u4e00\u80a1\u5de5\u7a0b\u5e2b\u7684\u5f37\u8feb\u75c7\u706b\u82d7\u537b\u8d8a\u71d2\u8d8a\u65fa\uff1a<br \/>\n<strong>\u300c\u4e0d\u884c\uff0c\u73fe\u5728\u9019\u96bb App \u6839\u672c\u53ea\u662f\u500b\u88ab\u95b9\u5272\u7684\u9aa8\u67b6\uff01\u300d<\/strong><\/p>\n<p>\u5927\u5bb6\u53bb GitHub \u4e0a\u7ffb\u7ffb\u90a3\u4e9b\u865f\u7a31\u300c\u5341\u5e74\u7d93\u5178\u91cd\u69cb\u300d\u7684\u958b\u6e90 Repository\uff0c\u5341\u500b\u6709\u4e5d\u500b\u90fd\u72af\u4e86\u4e00\u7a2e\u300c\u67b6\u69cb\u5f88\u6f6e\u3001\u529f\u80fd\u780d\u534a\u300d\u7684\u5077\u61f6\u75c5\uff1a\u5f8c\u7aef\u63db\u6210\u4e86\u6700\u6f6e\u7684\u5354\u7a0b\u548c Flow\uff0cUI \u63db\u6210\u4e86 Compose\uff0c\u4f46\u539f\u672c\u8907\u96dc\u7684\u529f\u80fd\u3001\u7d30\u81a9\u7684\u4e92\u52d5\u5f48\u7a97\u3001\u751a\u81f3\u9023\u5c0e\u89bd\u6559\u5b78\u9801\u5168\u90e8\u88ab\u7121\u60c5\u95b9\u5272\uff0c\u53ea\u7559\u4e0b\u4e00\u500b\u7a7a\u6d1e\u7c21\u964b\u7684\u5217\u8868\u5728\u81ea\u55e8\u3002<\/p>\n<p>\u8eab\u70ba Funny Vote \u7684\u89aa\u751f\u7236\u89aa\uff0c\u770b\u8457\u5341\u5e74\u524d\u5728 Java \u820a\u77f3\u5668\u6642\u4ee3\u71ac\u591c\u624b\u523b\u7684\u90a3\u4e9b\u9752\u6625\u7d30\u7bc0\u2014\u20146 \u9801\u7d93\u5178\u624b\u52e2\u5c0e\u89bd\u6559\u5b78\u3001\u81ea\u8a02\u793e\u7fa4\u5206\u4eab\u5e95\u5c64\u5f48\u7a97\u3001\u56db\u500b\u5c64\u7d1a\u7684\u95dc\u65bc\u9801\u9762\u3001\u6295\u7968\u5f97\u7968\u6578\u52d5\u614b\u6392\u5e8f FAB\u2014\u2014\u5982\u679c\u628a\u5b83\u5011\u4e1f\u6389\uff0c\u9019\u8d9f\u91cd\u69cb\u6839\u672c\u6beb\u7121\u9748\u9b42\u53ef\u8a00\u3002<\/p>\n<p>\u65bc\u662f\uff0c\u6211\u958b\u4e86 <code>modern-android<\/code> \u5206\u652f\uff0c\u7d66\u81ea\u5df1\u7acb\u4e0b\u4e86\u4e00\u500b\u8fd1\u4e4e\u81ea\u8650\u7684\u8ecd\u4ee4\u72c0\uff1a<strong>\u4e0d\u53ea\u8981\u63db\u4e0a\u73fe\u4ee3 Material 3\uff0c\u539f\u7248\u7684\u6240\u6709\u529f\u80fd\u3001\u52d5\u614b\u8207\u756b\u9762\u7d30\u7bc0\uff0c\u4e00\u500b\u90fd\u4e0d\u80fd\u5c11\uff0c\u5168\u90e8 100% \u50cf\u7d20\u7d1a\u5fa9\u523b\uff01<\/strong><\/p>\n<p>\u5b8c\u6574\u5fa9\u523b\u5206\u652f\u539f\u59cb\u78bc\u50b3\u9001\u9580\uff1a<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/boochlin06\/FunnyVote\/tree\/modern-android\">Funny Vote modern-android \u73fe\u4ee3\u5b8c\u5099\u5206\u652f<\/a><\/li>\n<\/ul>\n<hr>\n<h2>1. \u62d2\u7d55\u95b9\u5272\uff1a\u5341\u5e74\u524d\u7684\u756b\u9762\u5730\u5716\u8207 Material 3 \u77e9\u9663<\/h2>\n<p>\u5148\u628a\u5341\u5e74\u524d\u539f\u7248\u7684\u756b\u9762\u76e4\u9ede\u51fa\u4f86\u3002\u5f88\u591a\u4eba\u4ee5\u70ba\u6295\u7968 App \u4e0d\u5c31\u4e00\u500b\u5217\u8868\u52a0\u4e00\u500b\u6295\u7968\u6309\u9215\uff1f<br \/>\n\u932f\u3002\u7ffb\u958b\u8001\u5c08\u6848\u7684 Activity \u6e05\u55ae\uff0c\u6574\u5957 App \u5176\u5be6\u5305\u542b\u4e86\u4e94\u5927\u696d\u52d9\u93c8\u8def\uff1a<\/p>\n<pre><code class=\"language-text\">[Funny Vote \u5b8c\u6574\u529f\u80fd\u6a39]\n\u251c\u2500\u2500 \u555f\u52d5\u8207\u5f15\u5c0e\n\u2502    \u251c\u2500\u2500 WelcomeScreen (Logo \u6de1\u5165\u6de1\u51fa\u8207 1.2 \u79d2\u7121\u7e2b\u904e\u6e21)\n\u2502    \u2514\u2500\u2500 TutorialScreen (6 \u9801\u7d93\u5178\u529f\u80fd\u5c0e\u89bd\u3001\u52d5\u614b\u6307\u793a\u9ede\u3001\u96d9\u5411\u6ed1\u52d5)\n\u251c\u2500\u2500 \u9996\u9801\u4e2d\u6a1e (HomeScreen)\n\u2502    \u251c\u2500\u2500 PromotionCarousel (\u7126\u9ede\u8a71\u984c\u81ea\u7814\u7121\u5ee3\u544a\u8f2a\u64ad\u6a6b\u5e45)\n\u2502    \u251c\u2500\u2500 3 \u5927\u5206\u985e\u5206\u9801 (\u71b1\u9580\u6392\u884c \/ \u6700\u65b0\u4e0a\u67b6 \/ \u6211\u7684\u6536\u85cf)\n\u2502    \u2514\u2500\u2500 ShareBottomSheet (\u81ea\u8a02\u793e\u7fa4\u5206\u4eab\u5f48\u7a97\uff1aFB\/LINE\/Twitter\/\u8907\u88fd)\n\u251c\u2500\u2500 \u6295\u7968\u8a73\u60c5 (VoteDetailScreen)\n\u2502    \u251c\u2500\u2500 \u898f\u5247\u8aaa\u660e\u5f48\u7a97 (\u8d77\u8a16\u6642\u9593\u3001\u6295\u7968\u9650\u5236\u3001\u5b89\u5168\u6a21\u5f0f)\n\u2502    \u251c\u2500\u2500 \u52d5\u614b\u5373\u6642\u6392\u5e8f (\u5f97\u7968\u6578\u5347\u964d\u51aa\u52d5\u614b\u91cd\u6392)\n\u2502    \u2514\u2500\u2500 \u81ea\u7531\u65b0\u589e\u9078\u9805 (\u5408\u6cd5\u898f\u5247\u4e0b\u5373\u6642\u52d5\u614b\u64f4\u5145\u9078\u9805)\n\u251c\u2500\u2500 \u500b\u4eba\u4e2d\u5fc3 (ProfileScreen)\n\u2502    \u251c\u2500\u2500 \u96d9 Tab \u6b77\u53f2 (\u6211\u767c\u8d77\u7684 \/ \u6211\u6536\u85cf\u7684)\n\u2502    \u2514\u2500\u2500 \u4fee\u6539\u500b\u4eba\u66b1\u7a31\u8207\u982d\u50cf\u6301\u4e45\u5316\n\u2514\u2500\u2500 \u7cfb\u7d71\u8207\u689d\u6b3e (AboutScreen)\n     \u251c\u2500\u2500 AboutAppScreen (\u7248\u672c\u8207\u7406\u5ff5)\n     \u251c\u2500\u2500 AuthorInfoScreen (\u4f5c\u8005\u7d93\u6b77\u8207\u793e\u7fa4)\n     \u251c\u2500\u2500 LicenceScreen (\u7b2c\u4e09\u65b9\u958b\u6e90\u7d44\u4ef6\u6388\u6b0a)\n     \u2514\u2500\u2500 ProblemScreen (\u5e38\u898b\u554f\u984c\u8207\u4f7f\u7528 FAQ)\n<\/code><\/pre>\n<p>\u8981\u5728\u4e0d\u5f15\u5165\u4efb\u4f55\u4e00\u4efd XML \u7684\u524d\u63d0\u4e0b\uff0c\u7528\u7d14 Compose \u628a\u9019\u5957\u9f90\u5927\u7684\u756b\u9762\u5168\u90e8\u523b\u5b8c\uff0c\u90a3\u5e7e\u9031\u6211\u7684\u5de5\u4f4d\u87a2\u5e55\u4e0a\u5168\u90fd\u662f Material 3 \u7684\u5143\u4ef6\u5728\u98db\u821e\u3002<\/p>\n<hr>\n<h2>2. \u6838\u5fc3\u6230\u5f79\u4e00\uff1a\u7528 HorizontalPager \u964d\u4f0f 6 \u9801\u7d93\u5178\u5c0e\u89bd<\/h2>\n<p>\u5341\u5e74\u524d\uff0c\u70ba\u4e86\u5728\u4f7f\u7528\u8005\u7b2c\u4e00\u6b21\u958b\u555f App \u6642\u5c55\u793a 6 \u9801\u65b0\u624b\u5c0e\u89bd\uff0c\u6211\u5011\u5728 Java \u88e1\u7528 <code>ViewPager<\/code>\u3001\u81ea\u8a02 <code>PagerAdapter<\/code>\uff0c\u9084\u5f97\u5728 XML \u88e1\u624b\u523b\u516d\u500b\u5713\u9ede\u7684 Shape Drawable\uff0c\u624b\u52d5\u5728 <code>onPageScrolled<\/code> \u88e1\u9762\u8a08\u7b97\u504f\u79fb\u91cf\u53bb\u63db\u984f\u8272\uff0c\u4ee3\u78bc\u53c8\u81ed\u53c8\u9577\u3002<\/p>\n<p>\u5728 Compose \u88e1\uff0c\u5b98\u65b9\u63d0\u4f9b\u4e86 <code>HorizontalPager<\/code>\u3002\u770b\u4f3c\u7c21\u55ae\uff0c\u4f46\u5728\u771f\u5be6\u5fa9\u523b\u6642\uff0c\u9b54\u9b3c\u90fd\u5728\u7d30\u7bc0\u88e1\uff1a<\/p>\n<ol>\n<li><strong>\u52d5\u614b\u6307\u793a\u9ede\uff08Indicator\uff09\u7684\u6d41\u66a2\u904e\u6e21<\/strong>\uff1a\u7576\u9801\u9762\u6ed1\u5230\u7b2c 6 \u9801\u6642\uff0c\u53f3\u4e0b\u89d2\u7684\u6309\u9215\u5fc5\u9808\u5f9e\u300c\u4e0b\u4e00\u9801\u300d\u5e73\u6ed1\u904e\u6e21\u70ba\u300c\u7acb\u5373\u9ad4\u9a57\u300d\uff1b<\/li>\n<li><strong>\u9802\u90e8\u7684\u8df3\u904e\uff08SKIP\uff09\u908f\u8f2f<\/strong>\uff1a\u96a8\u6642\u9ede\u64ca\u90fd\u80fd\u76f4\u63a5\u89f8\u767c DataStore \u5beb\u5165\u300c\u5df2\u8b80\u6559\u5b78\u300d\u65d7\u6a19\uff0c\u4e26\u7121\u7e2b\u5c0e\u822a\u81f3\u9996\u9801\u3002<\/li>\n<\/ol>\n<p>\u5728 <code>modern-android<\/code> \u5206\u652f\u88e1\uff0c\u6838\u5fc3\u5be6\u4f5c\u6fc3\u7e2e\u6210\u4e86\u9019\u6bb5\u4e7e\u6de8\u7684\u4ee3\u78bc\uff1a<\/p>\n<pre><code class=\"language-kotlin\">\/\/ 6 \u9801\u5c0e\u89bd\u6559\u5b78\u7684 Compose \u73fe\u4ee3\u5be6\u4f5c\n@Composable\nfun TutorialScreen(onFinishTutorial: () -&gt; Unit) {\n    val pagerState = rememberPagerState(pageCount = { 6 })\n    val coroutineScope = rememberCoroutineScope()\n\n    Scaffold(\n        topBar = {\n            TutorialTopBar(onSkip = onFinishTutorial)\n        },\n        bottomBar = {\n            TutorialBottomBar(\n                pagerState = pagerState,\n                onNext = {\n                    coroutineScope.launch {\n                        if (pagerState.currentPage &lt; 5) {\n                            pagerState.animateScrollToPage(pagerState.currentPage + 1)\n                        } else {\n                            onFinishTutorial()\n                        }\n                    }\n                }\n            )\n        }\n    ) { padding -&gt;\n        HorizontalPager(\n            state = pagerState,\n            modifier = Modifier.fillMaxSize().padding(padding)\n        ) { page -&gt;\n            TutorialPageContent(pageIndex = page)\n        }\n    }\n}\n<\/code><\/pre>\n<p>\u6c92\u6709\u4efb\u4f55\u8907\u96dc\u7684 Adapter\uff0c\u6c92\u6709\u8106\u5f31\u7684 Drawable \u72c0\u614b\u5207\u63db\uff0c\u624b\u52e2\u6ed1\u52d5\u8207\u786c\u9ad4\u52a0\u901f\u5e40\u7387\u76f4\u63a5\u9802\u6eff 120fps\u3002<\/p>\n<hr>\n<h2>3. \u6838\u5fc3\u6230\u5f79\u4e8c\uff1a\u4e1f\u68c4 AdMob \u5ee3\u544a\u5783\u573e\uff0c\u63db\u4e0a\u6297\u624b\u52e2\u885d\u7a81\u8f2a\u64ad\u6a6b\u5e45<\/h2>\n<p>\u5341\u5e74\u524d\u8eab\u70ba\u7368\u7acb\u958b\u767c\u8005\uff0c\u7e3d\u5e7b\u60f3\u8457\u5728 App \u88e1\u653e\u5e7e\u500b\u5ee3\u544a\u689d\u5c31\u80fd\u8cfa\u5927\u9322\uff08\u7b11\uff0c\u5f8c\u4f86\u767c\u73fe\u4e00\u500b\u6708\u8cfa\u4e0d\u5230\u5169\u676f\u5496\u5561\uff09\u3002\u90a3\u6642\u5019\u9996\u9801\u9802\u90e8\u585e\u4e86\u4e00\u500b\u523a\u773c\u7684 Google AdMob \u6a6b\u5e45\uff0c\u4e0d\u50c5\u62c9\u4f4e\u6574\u500b App \u7684\u8cea\u611f\uff0c\u9084\u52d5\u4e0d\u52d5\u62d6\u6162\u9996\u9801\u555f\u52d5\u901f\u5ea6\u3002<\/p>\n<p>\u9019\u6b21\u91cd\u69cb\uff0c\u6211\u76f4\u63a5\u628a AdMob \u5fb9\u5e95\u62d4\u9664\uff0c\u6539\u7528\u7d14 Compose \u81ea\u7814\u4e86\u4e00\u500b\u6975\u5177\u73fe\u4ee3\u611f\u7684 <strong>\u7126\u9ede\u8a71\u984c\u8f2a\u64ad\u6a6b\u5e45\uff08PromotionCarousel\uff09<\/strong>\u3002<\/p>\n<p>\u5728\u523b\u9019\u500b\u5143\u4ef6\u6642\uff0c\u5f88\u591a\u4eba\u7684\u516c\u7248\u5beb\u6cd5\u53ea\u662f\u7121\u8166\u585e\u500b <code>delay(4500)<\/code> \u7136\u5f8c\u89f8\u767c\u63db\u9801\u3002\u7d50\u679c\u771f\u6a5f\u4e0a\u4e00\u6e2c\uff0c\u707d\u96e3\u767c\u751f\u4e86\uff1a<strong>\u7576\u4f7f\u7528\u8005\u624b\u6307\u6b63\u6309\u5728\u756b\u9762\u4e0a\u5de6\u53f3\u62d6\u66f3\u5361\u7247\u6642\uff0c\u80cc\u666f\u5354\u7a0b\u7a81\u7136\u57f7\u884c <code>animateScrollToPage<\/code>\uff0c\u786c\u751f\u751f\u628a\u756b\u9762\u626f\u8d70\uff01\u624b\u52e2\u77ac\u9593\u6495\u88c2\u5361\u6b7b\uff01<\/strong><\/p>\n<p>\u5728\u751f\u7522\u4ee3\u78bc\u4e2d\uff0c\u5fc5\u9808\u52a0\u5165\u624b\u52e2\u76e3\u807d\u9632\u79a6\uff1a<\/p>\n<pre><code class=\"language-kotlin\">\/\/ \u7d14 Compose \u6253\u9020\u7684\u96f6\u5ee3\u544a\u8a71\u984c\u7126\u9ede\u8f2a\u64ad\uff08\u542b\u624b\u52e2\u9632\u885d\u7a81\u4fdd\u8b77\uff09\n@Composable\nfun PromotionCarousel(\n    promotions: List&lt;Promotion&gt;,\n    onPromotionClick: (Promotion) -&gt; Unit\n) {\n    val pagerState = rememberPagerState(pageCount = { promotions.size })\n\n    \/\/ 4.5 \u79d2\u81ea\u52d5\u5e73\u6ed1\u8f2a\u64ad\uff08\u4f7f\u7528\u8005\u624b\u6307\u62d6\u66f3\u6642\u4e3b\u52d5\u66ab\u505c\uff09\n    LaunchedEffect(pagerState) {\n        while (true) {\n            delay(4500)\n            if (promotions.isNotEmpty() &amp;&amp; !pagerState.isScrollInProgress) {\n                val nextPage = (pagerState.currentPage + 1) % promotions.size\n                pagerState.animateScrollToPage(nextPage)\n            }\n        }\n    }\n\n    Column(modifier = Modifier.fillMaxWidth()) {\n        HorizontalPager(state = pagerState, modifier = Modifier.height(180.dp)) { page -&gt;\n            PromotionCard(promotion = promotions[page], onClick = { onPromotionClick(promotions[page]) })\n        }\n        \/\/ Material 3 \u98a8\u683c\u7d30\u9577\u689d\u9032\u5ea6\u6307\u793a\u9ede\n        CarouselDotsIndicator(total = promotions.size, current = pagerState.currentPage)\n    }\n}\n<\/code><\/pre>\n<p>\u6c92\u6709\u5916\u90e8\u5ee3\u544a SDK \u7684\u81c3\u816b\u8ffd\u8e64\uff0c\u624b\u52e2\u6d41\u66a2\u81ea\u7136\uff0c\u555f\u52d5\u96f6\u5ef6\u9072\u3002<\/p>\n<hr>\n<h2>4. \u6838\u5fc3\u6230\u5f79\u4e09\uff1aRoom \u5feb\u53d6\u8207 MVI SSOT \u7684\u7121\u7e2b\u4e32\u63a5<\/h2>\n<p>\u756b\u9762\u5fa9\u523b\u5f97\u518d\u6f02\u4eae\uff0c\u5982\u679c\u7db2\u8def\u4e00\u65b7\u7dda\u5c31\u6574\u9801\u767d\u5c4f\uff0c\u90a3\u5c31\u662f\u500b\u82b1\u74f6\u3002<\/p>\n<p>\u5728 <code>modern-android<\/code> \u5206\u652f\u88e1\uff0c\u6211\u5011\u70ba\u6240\u6709\u9801\u9762\u88dc\u9f4a\u4e86\u5b8c\u6574\u7684 <strong>Room Database \u672c\u5730\u5feb\u53d6\u5c64<\/strong>\uff0c\u4e26\u8207 MVI \u7ba1\u7dda\u5f62\u6210\u5b8c\u7f8e\u7684\u55ae\u4e00\u771f\u7406\u4f86\u6e90\uff08Single Source of Truth, SSOT\uff09\u5168\u93c8\u8def\u4e32\u63a5\uff1a<\/p>\n<pre><code class=\"language-mermaid\">flowchart LR\n    RemoteAPI[&quot;\u9060\u7aef API&quot;] --&gt;|\u80cc\u666f\u7121\u611f\u540c\u6b65| RoomDB[&quot;Room \u672c\u5730\u8cc7\u6599\u5eab (SSOT)&quot;]\n    RoomDB --&gt;|Flow \u97ff\u61c9\u5f0f\u4e32\u6d41| Repository[&quot;VoteRepository&quot;]\n    Repository --&gt;|\u4e0d\u53ef\u8b8a\u9818\u57df\u5be6\u9ad4| ViewModel[&quot;MVI ViewModel&quot;]\n    ViewModel --&gt;|UiState \u91cd\u7d44| Screen[&quot;Compose \u756b\u9762&quot;]\n<\/code><\/pre>\n<p>\u7576\u4f7f\u7528\u8005\u6253\u958b App\uff1a<\/p>\n<ol>\n<li><strong>0ms \u96e2\u7dda\u79d2\u958b<\/strong>\uff1aUI \u512a\u5148\u5f9e Room \u8cc7\u6599\u5eab\u8f09\u5165\u672c\u5730\u5feb\u53d6\u8cc7\u6599\uff0c\u4f7f\u7528\u8005\u624b\u6307\u4e00\u5283\u7acb\u523b\u6709\u53cd\u61c9\uff1b<\/li>\n<li><strong>\u80cc\u666f\u7121\u611f\u66f4\u65b0<\/strong>\uff1aRepository \u5728\u80cc\u666f\u5354\u7a0b\u767c\u8d77\u7db2\u8def\u8acb\u6c42\uff0c\u8cc7\u6599\u6293\u56de\u4f86\u5f8c\u6084\u6084\u5beb\u5165 Room\uff1b<\/li>\n<li><strong>Room \u81ea\u52d5\u9a45\u52d5 UI \u91cd\u7d44<\/strong>\uff1aRoom \u7684 Flow \u5075\u6e2c\u5230\u8cc7\u6599\u5eab\u8b8a\u52d5\uff0c\u81ea\u52d5\u767c\u5c04\u6700\u65b0\u6e05\u55ae\uff0cUI \u5e73\u6ed1\u66f4\u65b0\uff0c\u4f7f\u7528\u8005\u5b8c\u5168\u611f\u53d7\u4e0d\u5230\u8f09\u5165\u83ca\u82b1\u7684\u5b58\u5728\u3002<\/li>\n<\/ol>\n<p>\u9019\u624d\u662f\u6211\u5011\u8981\u7684\u6020\u60f0\u5883\u754c\u2014\u2014\u8b93\u6846\u67b6\u81ea\u5df1\u53bb\u6253\u67b6\uff0c\u4f7f\u7528\u8005\u958b\u5fc3\u6ed1\uff0c\u6211\u5011\u5b89\u5fc3\u770b\u5287\u3002<\/p>\n<hr>\n<h2>5. \u5b8c\u7f8e\u756b\u9762\u7684\u6b7b\u5c40\u8207\u6536\u5de5<\/h2>\n<p>\u628a\u5168\u5957 5 \u5927\u93c8\u8def\u3001\u5341\u5e7e\u5f35\u756b\u9762\u8207\u7d30\u7bc0\u5f48\u7a97 100% \u5fa9\u523b\u9032 Material 3 \u4e4b\u5f8c\uff0c\u6211\u770b\u8457\u624b\u4e0a\u9019\u96bb\u904b\u884c\u7684 Funny Vote\uff0c\u6210\u5c31\u611f\u7c21\u76f4\u7206\u68da\u3002<\/p>\n<p>\u5b83\u64c1\u6709\u9802\u7d1a\u7684 MVI \u55ae\u5411\u6d41\u3001\u6975\u901f\u7684 120Hz Compose \u7e6a\u88fd\u3001\u7121\u5ee3\u544a\u7684\u7d14\u6de8\u8996\u89ba\u3001\u4ee5\u53ca\u96e2\u7dda\u53ef\u7528\u7684 Room \u5feb\u53d6\u3002<\/p>\n<p><strong>\u4f46\u5c31\u5728\u9019\u6642\uff0c\u4e00\u500b\u6b98\u9177\u7684\u73fe\u5be6\u72e0\u72e0\u7d66\u4e86\u6211\u4e00\u8a18\u60b6\u68cd\uff1a<\/strong><\/p>\n<p>\u9019\u96bb App \u7684\u6240\u6709\u8cc7\u6599\uff0c\u5e95\u5c64\u9023\u7684\u4f9d\u7136\u662f 2016 \u5e74\u90a3\u53f0\u65e9\u5df2\u505c\u6a5f\u9664\u5f79\u7684\u8001\u4f3a\u670d\u5668\u3002<br \/>\n\u63db\u53e5\u8a71\u8aaa\uff0c\u73fe\u5728\u6574\u96bb App \u96d6\u7136\u76ae\u56ca\u8207\u9aa8\u67b6\u5929\u4e0b\u7121\u6575\uff0c\u4f46\u5b83\u7684\u5fc3\u81df\u662f\u6b7b\u7684\uff01<br \/>\n\u4f7f\u7528\u8005\u9ede\u64ca\u6295\u7968\uff0c\u6c92\u6709\u9060\u7aef\u8cc7\u6599\u5eab\u53ef\u4ee5\u843d\u5eab\uff1b\u60f3\u767c\u8d77\u4e00\u500b\u65b0\u6295\u7968\u7d66\u670b\u53cb\uff0c\u670b\u53cb\u7684\u624b\u6a5f\u6839\u672c\u770b\u4e0d\u5230\uff01<\/p>\n<p>\uff08\u9019\u5c31\u50cf\u4f60\u8cbb\u76e1\u5343\u8f9b\u842c\u82e6\u7d44\u88dd\u4e86\u4e00\u53f0\u9802\u7d1a\u7684\u6cd5\u62c9\u5229\u8dd1\u8eca\uff0c\u7d50\u679c\u767c\u73fe\u6cb9\u7bb1\u88e1\u6c92\u6709\u6cb9\uff0c\u53ea\u80fd\u63a8\u8457\u5b83\u5728\u8eca\u5eab\u88e1\u62cd\u7167\u3002\uff09<\/p>\n<p>\u4e00\u500b\u4e0d\u80fd\u9023\u7dda\u8207\u670b\u53cb\u5373\u6642\u4e92\u52d5\u7684\u6295\u7968 App\uff0c\u6839\u672c\u6c92\u6709\u9748\u9b42\u3002<\/p>\n<p>\u9019\u4e5f\u76f4\u63a5\u903c\u51fa\u4e86\u6211\u5011\u6574\u500b\u5341\u5e74\u6f14\u9032\u53f2\u4e0a\u6700\u760b\u72c2\u7684\u4e00\u5834\u5927\u624b\u8853\uff1a<strong>\u5728\u4e0b\u4e00\u7bc7 <code>feature\/firebase-backend<\/code> \u5206\u652f\u88e1\uff0c\u6211\u5011\u5c07\u5728\u4e0d\u82b1\u4e00\u6bdb\u9322\u4f3a\u670d\u5668\u79df\u91d1\u7684\u524d\u63d0\u4e0b\uff0c\u7528 Firebase Serverless \u70ba Funny Vote \u9032\u884c\u4e00\u5834\u7d42\u6975\u7684\u300c\u96f2\u7aef\u63db\u5fc3\u300d\uff01<\/strong><\/p>\n<p>\u4ee5\u4e0a\u662f\u672c\u6b21\u5c0f\u5f1f\u7684\u611f\u60f3\uff0c\u6211\u771f\u7684\u61f6\u5f97\u8cbc\u843d\u843d\u9577\u7684\u5168\u5c08\u6848 CODE\uff0c\u5404\u4f4d\u771f\u7684\u60f3\u770b\u5b8c\u6574\u5fa9\u523b\u7d30\u7bc0\uff0c\u53bb GitHub \u7ffb <code>modern-android<\/code> \u5206\u652f\u5427\uff0c\u901a\u901a\u90fd\u653e\u5728\u90a3\uff0c\u4e0d\u6d6a\u8cbb\u7248\u9762\u3002<\/p>\n<p>\u611f\u8b1d\u89c0\u770b\uff0c\u6536\u5de5\u3002<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n<div class=\"wp-block-group\" style=\"border:1px solid #e2e8f0;background:#ffffff;padding:18px 20px;margin-top:32px;margin-bottom:24px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,0.05)\">\n<p style=\"margin:0 0 12px 0;font-size:0.9em;color:#888;font-weight:bold;text-transform:uppercase;letter-spacing:1px\">\u300aFunny Vote \u5341\u5e74\u6f14\u9032\u7cfb\u5217\u300b\u7ae0\u7bc0\u5c0e\u822a<\/p>\n<div style=\"display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;font-size:0.95em\">\n    <a href=\"https:\/\/boochlin.com\/?p=1297\" style=\"color:#C14420;text-decoration:none;font-weight:500\">\u2190 \u4e0a\u4e00\u7bc7\uff1a\u76f4\u4e0a MVI \u8207\u62ef\u6551\u767d\u5c4f\u707d\u96e3\uff08\u4e94\uff09<\/a><br \/>\n    <a href=\"https:\/\/boochlin.com\/?page_id=1319\" style=\"color:#666;text-decoration:none;font-size:0.85em;background:#edf2f7;padding:4px 10px;border-radius:4px\">\u7cfb\u5217\u76ee\u9304<\/a><br \/>\n    <a href=\"https:\/\/boochlin.com\/?p=1313\" style=\"color:#C14420;text-decoration:none;font-weight:500\">\u4e0b\u4e00\u7bc7\uff1a\u96f2\u7aef\u63db\u5fc3\u96dc\u8a18\uff08\u4e03\uff09 \u2192<\/a>\n  <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>\u672c\u6587\u70ba\u300aFunny Vote \u5341\u5e74\u6f14\u9032\u7cfb\u5217\u300b\u7b2c 6 \u7bc7\uff08\u5168\u7cfb\u5217\u5171 8 \u7bc7\uff09\uff5c\u67e5\u770b\u5168\u7cfb\u5217\u67b6\u69cb\u5c0e\u89bd \u67b6\u69cb\u642d\u597d\u4e86\uff0c\u72c0 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1298,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[86,169],"tags":[6,172,179,128],"_links":{"self":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/1299"}],"collection":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1299"}],"version-history":[{"count":11,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/1299\/revisions"}],"predecessor-version":[{"id":1493,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/posts\/1299\/revisions\/1493"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=\/wp\/v2\/media\/1298"}],"wp:attachment":[{"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1299"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1299"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/boochlin.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1299"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}