├── lib ├── pages │ ├── message │ │ ├── components │ │ │ ├── vip.dart │ │ │ ├── message_type.dart │ │ │ ├── account_type_tag.dart │ │ │ └── message_item.dart │ │ └── message.dart │ ├── user │ │ └── user.dart │ ├── home │ │ ├── components │ │ │ ├── hot_item.dart │ │ │ ├── ask_menu.dart │ │ │ ├── hot_choiceness.dart │ │ │ ├── follows_recommend.dart │ │ │ ├── ask_menu_item.dart │ │ │ ├── user_item.dart │ │ │ ├── choiceness_item.dart │ │ │ ├── ask_item.dart │ │ │ ├── hot_list.dart │ │ │ └── speech_item.dart │ │ ├── follows.dart │ │ ├── job.dart │ │ ├── recommend.dart │ │ ├── hot.dart │ │ ├── ask.dart │ │ └── home.dart │ ├── owner │ │ ├── components │ │ │ ├── owner_detail_line.dart │ │ │ ├── owner_detail_item.dart │ │ │ ├── owner_menu.dart │ │ │ ├── owner_menu_item.dart │ │ │ └── owner_info.dart │ │ └── owner.dart │ ├── post_detail │ │ └── post_detail.dart │ ├── contact │ │ ├── components │ │ │ ├── contact_menu.dart │ │ │ ├── friend_msg_item.dart │ │ │ ├── friend_msg.dart │ │ │ ├── request_list.dart │ │ │ ├── contact_item.dart │ │ │ └── discover_more.dart │ │ └── contact.dart │ └── chance │ │ ├── components │ │ ├── company_list.dart │ │ ├── follow_market.dart │ │ ├── chance_notice.dart │ │ └── post_item.dart │ │ └── chance.dart ├── enums.dart ├── types.dart ├── 404.dart ├── components │ ├── safe_area.dart │ ├── no_more.dart │ ├── vip_badge.dart │ ├── tag.dart │ ├── card_container.dart │ ├── empty.dart │ ├── app_bar_action.dart │ ├── menu_item.dart │ ├── badge.dart │ ├── customer_tab_item.dart │ ├── column_menu_item.dart │ ├── app_ad.dart │ ├── app_navbar.dart │ ├── search_field.dart │ └── navbar_item.dart ├── mock │ ├── ad.dart │ ├── message.dart │ ├── contact.dart │ ├── company.dart │ └── home.dart ├── common │ ├── common.dart │ └── customer_tab_size_indicator.dart ├── router.dart ├── constants.dart ├── main.dart └── main copy.dart ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── .gitignore └── Podfile ├── assets ├── icons │ └── vip.png ├── fonts │ └── iconfont.ttf └── images │ ├── avatar.png │ └── empty.png ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_maimai │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── .metadata ├── README.md ├── .gitignore ├── test └── widget_test.dart ├── pubspec.yaml └── pubspec.lock /lib/pages/message/components/vip.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/enums.dart: -------------------------------------------------------------------------------- 1 | enum NavbarMessageType { 2 | dot, 3 | number, 4 | } 5 | -------------------------------------------------------------------------------- /assets/icons/vip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/assets/icons/vip.png -------------------------------------------------------------------------------- /assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/assets/images/avatar.png -------------------------------------------------------------------------------- /assets/images/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/assets/images/empty.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BingKui/flutter_maimai/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutter_maimai/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_maimai 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/types.dart: -------------------------------------------------------------------------------- 1 | // 帖子详情参数 2 | class PostDetailParam { 3 | final int postId; 4 | PostDetailParam(this.postId); 5 | } 6 | 7 | // 用户详情参数 8 | class UserDetailParam { 9 | final int userId; 10 | UserDetailParam(this.userId); 11 | } 12 | -------------------------------------------------------------------------------- /lib/404.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Error extends StatelessWidget { 4 | const Error({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/pages/user/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class User extends StatelessWidget { 4 | const User({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /lib/pages/home/components/hot_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HotItem extends StatelessWidget { 4 | const HotItem({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/components/safe_area.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SafeAreaBottom extends StatelessWidget { 4 | const SafeAreaBottom({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container( 9 | height: 200, 10 | color: Colors.transparent, 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/pages/owner/components/owner_detail_line.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class OwnerDetailLine extends StatelessWidget { 4 | const OwnerDetailLine({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container( 9 | width: 1, 10 | height: 50, 11 | color: Colors.black12, 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/mock/ad.dart: -------------------------------------------------------------------------------- 1 | const String ChanceImageAdUrl = 2 | 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F4b77dd153586a7139d5c1cf610fc2bc177346d3641059-6NW7Xk_fw658&refer=http%3A%2F%2Fhbimg.b0.upaiyun.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631703668&t=b8605ed942ec1f53eacb3523c74fa65d'; 3 | 4 | const String AskImageAdUrl = 'https://img0.baidu.com/it/u=512655854,4064430547&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500'; 5 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/components/no_more.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class NoMore extends StatelessWidget { 5 | const NoMore({ 6 | Key? key, 7 | this.tip = '没有更多了', 8 | }) : super(key: key); 9 | 10 | final String tip; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | padding: EdgeInsets.all(iDefaultPadding), 16 | child: Text( 17 | tip, 18 | style: TextStyle(fontSize: 16, color: Colors.black45), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_maimai 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /lib/pages/post_detail/post_detail.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/types.dart'; 3 | 4 | class PostDetail extends StatefulWidget { 5 | const PostDetail({Key? key}) : super(key: key); 6 | @override 7 | _PostDetailState createState() => _PostDetailState(); 8 | } 9 | 10 | class _PostDetailState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | final args = ModalRoute.of(context)!.settings.arguments as PostDetailParam; 14 | return Scaffold( 15 | appBar: AppBar(), 16 | body: Text('传过来的ID为${args.postId}'), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /lib/components/vip_badge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class VipBadge extends StatelessWidget { 5 | const VipBadge({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | width: 14, 11 | height: 14, 12 | // padding: EdgeInsets.symmetric(horizontal: 4, vertical: 4), 13 | decoration: BoxDecoration( 14 | color: iPrimaryColor.withOpacity(0.4), 15 | borderRadius: BorderRadius.all(Radius.circular(4)), 16 | ), 17 | child: Center( 18 | child: Image.asset( 19 | 'assets/icons/vip.png', 20 | width: 10, 21 | height: 10, 22 | ), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/pages/home/follows.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/empty.dart'; 3 | import 'package:flutter_maimai/components/no_more.dart'; 4 | import 'package:flutter_maimai/components/safe_area.dart'; 5 | import 'package:flutter_maimai/pages/home/components/follows_recommend.dart'; 6 | 7 | class Follows extends StatelessWidget { 8 | const Follows({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return SingleChildScrollView( 13 | child: Column( 14 | children: [ 15 | Empty( 16 | title: '还没有关注任何人呢', 17 | tip: '关注你感兴趣的人,他们发布的内容会在这里展示', 18 | ), 19 | FollowsRecommend(), 20 | NoMore(), 21 | SafeAreaBottom(), 22 | ], 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/pages/owner/components/owner_detail_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class OwnerDetailItem extends StatelessWidget { 4 | const OwnerDetailItem({ 5 | Key? key, 6 | required this.label, 7 | required this.value, 8 | }) : super(key: key); 9 | 10 | final String label; 11 | final int value; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Expanded( 16 | child: Column( 17 | children: [ 18 | Text( 19 | '$value', 20 | style: TextStyle(fontSize: 22, color: Colors.black, fontWeight: FontWeight.bold), 21 | ), 22 | SizedBox(height: 5), 23 | Text( 24 | label, 25 | style: TextStyle(color: Colors.black38, fontSize: 14), 26 | ), 27 | ], 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/pages/home/job.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | import 'package:flutter_maimai/mock/home.dart'; 4 | import 'package:flutter_maimai/pages/home/components/speech_item.dart'; 5 | 6 | class Job extends StatefulWidget { 7 | const Job({Key? key}) : super(key: key); 8 | 9 | @override 10 | _JobState createState() => _JobState(); 11 | } 12 | 13 | class _JobState extends State { 14 | List speechList = SpeechMockList; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Container( 19 | padding: EdgeInsets.only(top: iDefaultPadding / 2), 20 | child: ListView.builder( 21 | itemBuilder: (context, index) { 22 | return SpeechItem(speech: speechList[index]); 23 | }, 24 | itemCount: speechList.length, 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/common/common.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CustomSliverDelegate extends SliverPersistentHeaderDelegate { 5 | CustomSliverDelegate({ 6 | required this.minHeight, 7 | required this.maxHeight, 8 | required this.child, 9 | }); 10 | 11 | final double minHeight; 12 | final double maxHeight; 13 | final Widget child; 14 | 15 | @override 16 | double get minExtent => minHeight; 17 | 18 | @override 19 | double get maxExtent => max(maxHeight, minHeight); 20 | 21 | @override 22 | Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { 23 | return new SizedBox.expand(child: child); 24 | } 25 | 26 | @override 27 | bool shouldRebuild(CustomSliverDelegate oldDelegate) { 28 | return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight || child != oldDelegate.child; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /lib/components/tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class Tag extends StatelessWidget { 5 | const Tag(this.value, {Key? key, this.plain = false}) : super(key: key); 6 | 7 | final String value; 8 | final bool plain; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2), 14 | decoration: BoxDecoration( 15 | color: plain ? Colors.transparent : iTagBackgroundColor, 16 | borderRadius: BorderRadius.all(Radius.circular(4)), 17 | border: new Border.all(width: 1, color: iTagBackgroundColor), 18 | ), 19 | child: Text( 20 | value, 21 | style: TextStyle( 22 | color: iTagTextColor, 23 | fontSize: 12, 24 | fontWeight: FontWeight.w500, 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/pages/contact/components/contact_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/card_container.dart'; 3 | import 'package:flutter_maimai/components/menu_item.dart'; 4 | import 'package:flutter_maimai/components/self_icon.dart'; 5 | 6 | class ContactMenu extends StatelessWidget { 7 | const ContactMenu({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return CardContainer( 12 | child: Row( 13 | mainAxisAlignment: MainAxisAlignment.spaceAround, 14 | children: [ 15 | MenuItem(name: '我的好友', press: () {}, icon: SelfIcon.ContactFilling), 16 | MenuItem(name: '人脉探索', press: () {}, icon: SelfIcon.Search), 17 | MenuItem(name: '校友群', press: () {}, icon: SelfIcon.Layers), 18 | MenuItem(name: '极速找人', press: () {}, icon: SelfIcon.Operation), 19 | ], 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/pages/home/recommend.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | import 'package:flutter_maimai/mock/home.dart'; 4 | import 'package:flutter_maimai/pages/home/components/speech_item.dart'; 5 | 6 | class Recommend extends StatefulWidget { 7 | const Recommend({Key? key}) : super(key: key); 8 | 9 | @override 10 | _RecommendState createState() => _RecommendState(); 11 | } 12 | 13 | class _RecommendState extends State { 14 | List speechList = SpeechMockList; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Container( 19 | padding: EdgeInsets.only(top: iDefaultPadding / 2, bottom: iDefaultPadding * 4), 20 | child: ListView.builder( 21 | itemBuilder: (context, index) { 22 | return SpeechItem(speech: speechList[index]); 23 | }, 24 | itemCount: speechList.length, 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/components/card_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | const EdgeInsets _margin = EdgeInsets.only( 5 | left: iDefaultPadding, 6 | right: iDefaultPadding, 7 | bottom: iDefaultPadding, 8 | ); 9 | 10 | const EdgeInsets _padding = EdgeInsets.symmetric( 11 | vertical: iDefaultPadding, 12 | ); 13 | 14 | class CardContainer extends StatelessWidget { 15 | const CardContainer({ 16 | Key? key, 17 | this.child, 18 | this.margin = _margin, 19 | this.padding = _padding, 20 | this.height, 21 | }) : super(key: key); 22 | 23 | final Widget? child; 24 | final double? height; 25 | final EdgeInsets margin, padding; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Container( 30 | margin: margin, 31 | padding: padding, 32 | height: height, 33 | decoration: BoxDecoration( 34 | color: Colors.white, 35 | borderRadius: iBorderRadius, 36 | ), 37 | child: child, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/pages/message/components/message_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class MessageType extends StatelessWidget { 5 | const MessageType({ 6 | Key? key, 7 | required this.text, 8 | required this.press, 9 | }) : super(key: key); 10 | 11 | final String text; 12 | final VoidCallback press; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GestureDetector( 17 | onTap: press, 18 | child: Container( 19 | height: 35, 20 | margin: EdgeInsets.only(right: iDefaultPadding / 2), 21 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding / 2), 22 | decoration: BoxDecoration( 23 | color: Colors.white, 24 | borderRadius: BorderRadius.all(Radius.circular(35)), 25 | ), 26 | child: Center( 27 | child: Text( 28 | text, 29 | style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: iTipColor), 30 | )), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/pages/message/components/account_type_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class AccountTypeTag extends StatelessWidget { 5 | const AccountTypeTag({ 6 | Key? key, 7 | this.isOfficial = false, 8 | this.isCompany = false, 9 | }) : super(key: key); 10 | 11 | final bool isOfficial, isCompany; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | bool _isNoEmpty = isCompany || isOfficial; 16 | String _tagValue = isCompany ? '企业' : (isOfficial ? '官方' : ''); 17 | return _isNoEmpty 18 | ? Container( 19 | margin: EdgeInsets.only(left: iDefaultPadding / 2), 20 | padding: EdgeInsets.symmetric(horizontal: 5, vertical: 1), 21 | decoration: BoxDecoration( 22 | border: Border.all(color: Colors.black12, width: 1), 23 | borderRadius: BorderRadius.all(Radius.circular(4)), 24 | ), 25 | child: Text( 26 | _tagValue, 27 | style: TextStyle(fontSize: 12, color: Colors.black26), 28 | ), 29 | ) 30 | : Text(''); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/pages/home/hot.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/pages/home/components/hot_choiceness.dart'; 3 | import 'package:flutter_maimai/pages/home/components/hot_list.dart'; 4 | import 'package:flutter_maimai/mock/home.dart'; 5 | import 'package:flutter_maimai/pages/home/components/speech_item.dart'; 6 | 7 | class Hot extends StatelessWidget { 8 | const Hot({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | List speechList = SpeechMockList; 13 | return Container( 14 | color: Colors.white, 15 | child: ListView.builder( 16 | shrinkWrap: true, 17 | itemBuilder: (context, index) { 18 | List _list = []; 19 | if (index == 0) { 20 | _list.add(HotList()); 21 | _list.add(HotChoiceness()); 22 | _list.add(SpeechItem(speech: speechList[index])); 23 | return Column( 24 | children: _list, 25 | ); 26 | } 27 | return SpeechItem(speech: speechList[index]); 28 | }, 29 | itemCount: speechList.length, 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_maimai/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/components/empty.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class Empty extends StatelessWidget { 5 | const Empty({ 6 | Key? key, 7 | required this.title, 8 | this.tip = '', 9 | }) : super(key: key); 10 | 11 | final String title, tip; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | Size size = MediaQuery.of(context).size; 16 | return Container( 17 | width: size.width * 0.8, 18 | margin: EdgeInsets.all(iDefaultPadding), 19 | child: Column( 20 | mainAxisAlignment: MainAxisAlignment.center, 21 | crossAxisAlignment: CrossAxisAlignment.center, 22 | children: [ 23 | Image.asset( 24 | 'assets/images/empty.png', 25 | width: 160, 26 | height: 160, 27 | ), 28 | Padding( 29 | padding: EdgeInsets.only(top: iDefaultPadding, bottom: iDefaultPadding / 2), 30 | child: Text(title, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, color: Colors.black)), 31 | ), 32 | Text(tip, style: TextStyle(fontSize: 14, color: Colors.black54)), 33 | ], 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/pages/contact/components/friend_msg_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class FriendMsgItem extends StatelessWidget { 5 | const FriendMsgItem({ 6 | Key? key, 7 | required this.message, 8 | required this.press, 9 | }) : super(key: key); 10 | 11 | final message; 12 | final VoidCallback press; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | height: 30, 18 | margin: EdgeInsets.only(bottom: iDefaultPadding / 2), 19 | child: Row( 20 | children: [ 21 | // Text('12123'), 22 | CircleAvatar( 23 | radius: 15, 24 | backgroundImage: NetworkImage(message['avatar']), 25 | ), 26 | SizedBox(width: iDefaultPadding / 2), 27 | Text( 28 | message['nickname'] + ':', 29 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.black), 30 | ), 31 | Expanded( 32 | child: Text( 33 | message['tip'], 34 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), 35 | ), 36 | ), 37 | ], 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/components/app_bar_action.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | const double actionSize = 40.0; 5 | 6 | class AppBarAction extends StatelessWidget { 7 | const AppBarAction({ 8 | Key? key, 9 | required this.icon, 10 | required this.press, 11 | this.left = 0, 12 | this.right = 0, 13 | }) : super(key: key); 14 | 15 | final IconData icon; 16 | final VoidCallback press; 17 | final double left, right; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Stack( 22 | alignment: AlignmentDirectional.center, 23 | children: [ 24 | Container( 25 | width: actionSize, 26 | height: actionSize, 27 | margin: EdgeInsets.only(left: left, right: right), 28 | decoration: BoxDecoration( 29 | color: Colors.white, 30 | borderRadius: BorderRadius.all(Radius.circular(actionSize)), 31 | ), 32 | child: GestureDetector( 33 | onTap: press, 34 | child: Center( 35 | child: Icon( 36 | icon, 37 | color: Colors.black, 38 | ), 39 | ), 40 | ), 41 | ), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/router.dart: -------------------------------------------------------------------------------- 1 | // 路由配置 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_maimai/404.dart'; 4 | 5 | // 路由配置信息 6 | import 'package:flutter_maimai/pages/post_detail/post_detail.dart'; 7 | import 'package:flutter_maimai/pages/user/user.dart'; 8 | 9 | final Map iPageRouter = { 10 | '/postDetail': (context) => PostDetail(), 11 | '/user': (context) => User(), 12 | }; 13 | 14 | // Route iPageRouterController(RouteSettings settings) { 15 | // if (settings.name == ) 16 | // } 17 | 18 | // 路由传参配置方法 19 | // Route pageRouterController(RouteSettings settings) { 20 | // final name = settings.name; 21 | // final Function? pageContentBuilder = iPageRouter[name]; 22 | // print('运行到这里'); 23 | // print(pageContentBuilder); 24 | // if (pageContentBuilder != null) { 25 | // if (settings.arguments != null) { 26 | // print('运行到这里'); 27 | 28 | // final Route route = MaterialPageRoute(builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); 29 | // return route; 30 | // } else { 31 | // final Route route = MaterialPageRoute(builder: (context) => pageContentBuilder(context)); 32 | // return route; 33 | // } 34 | // } else { 35 | // return MaterialPageRoute(builder: (context) => Error()); 36 | // } 37 | // } 38 | -------------------------------------------------------------------------------- /lib/pages/owner/components/owner_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | import 'package:flutter_maimai/pages/owner/components/owner_menu_item.dart'; 5 | 6 | class OwnerMenu extends StatelessWidget { 7 | const OwnerMenu({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | margin: EdgeInsets.only(bottom: iDefaultPadding, left: iDefaultPadding, right: iDefaultPadding), 13 | child: Row( 14 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 15 | children: [ 16 | OwnerMenuItem( 17 | icon: SelfIcon.FavoriteFilling, 18 | name: '我的圈子', 19 | press: () {}, 20 | ), 21 | OwnerMenuItem( 22 | icon: SelfIcon.FileCommonFilling, 23 | name: '我的关注', 24 | press: () {}, 25 | ), 26 | OwnerMenuItem( 27 | icon: SelfIcon.ClockFilling, 28 | name: '最近浏览', 29 | press: () {}, 30 | ), 31 | OwnerMenuItem( 32 | icon: SelfIcon.CommentFilling, 33 | name: '我的互动', 34 | press: () {}, 35 | ), 36 | ], 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/components/menu_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class MenuItem extends StatelessWidget { 5 | const MenuItem({ 6 | Key? key, 7 | required this.name, 8 | required this.press, 9 | required this.icon, 10 | }) : super(key: key); 11 | 12 | final String name; 13 | final VoidCallback press; 14 | final IconData icon; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return GestureDetector( 19 | onTap: press, 20 | child: Column( 21 | mainAxisAlignment: MainAxisAlignment.center, 22 | crossAxisAlignment: CrossAxisAlignment.center, 23 | children: [ 24 | Container( 25 | height: iFollowMenuHeight, 26 | width: iFollowMenuHeight, 27 | margin: EdgeInsets.only(bottom: iDefaultPadding / 2), 28 | decoration: BoxDecoration( 29 | color: Color(0xFFF9FAFC), 30 | borderRadius: BorderRadius.all(Radius.circular(iFollowMenuHeight)), 31 | ), 32 | child: Icon(icon), 33 | ), 34 | Text( 35 | name, 36 | style: TextStyle( 37 | fontSize: 14, 38 | fontWeight: FontWeight.w500, 39 | ), 40 | ) 41 | ], 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/pages/owner/components/owner_menu_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class OwnerMenuItem extends StatelessWidget { 5 | const OwnerMenuItem({ 6 | Key? key, 7 | required this.icon, 8 | required this.name, 9 | required this.press, 10 | }) : super(key: key); 11 | 12 | final IconData icon; 13 | final String name; 14 | final VoidCallback press; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | Size size = MediaQuery.of(context).size; 19 | double _width = (size.width - iDefaultPadding * 2 - 30) / 4; 20 | return Container( 21 | width: _width, 22 | height: _width, 23 | decoration: BoxDecoration( 24 | color: Colors.white, 25 | borderRadius: iBorderRadius, 26 | ), 27 | child: Stack( 28 | children: [ 29 | Positioned( 30 | left: iDefaultPadding / 2, 31 | top: iDefaultPadding / 2, 32 | child: Icon( 33 | icon, 34 | size: 26, 35 | ), 36 | ), 37 | Positioned( 38 | bottom: iDefaultPadding / 2, 39 | left: 0, 40 | right: 0, 41 | child: Center( 42 | child: Text(name, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), 43 | ), 44 | ) 45 | ], 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // 定义基本颜色 4 | const iAppBackgroundColor = Color(0xFFF4F5FA); 5 | const iAppBarBackgroundColor = Color(0xFFF4F5FA); 6 | 7 | // 定义在 app 中用到的颜色 8 | const iPrimaryColor = Color(0xFF2150F4); // 主题色 9 | const iTextColor = Color(0xFF3C4046); 10 | const iTipColor = Color(0xFF7C7D81); 11 | const iBackgroundColor = Color(0xFFF9F8FD); 12 | const iAppBottomBarColor = Color(0xFF555763); 13 | const iTagBackgroundColor = Color(0xFFF3F4FA); 14 | const iTagTextColor = Color(0xFF7B7D84); 15 | // 定义边界padding 16 | const double iDefaultPadding = 20.0; 17 | const BorderRadiusGeometry iBorderRadius = BorderRadius.all(Radius.circular(10)); 18 | 19 | // 定义基本样式变量 20 | const List iBoxShadow = [ 21 | BoxShadow( 22 | color: Colors.black12, 23 | offset: Offset(0, 0), //阴影y轴偏移量 24 | blurRadius: 5, //阴影模糊程度 25 | spreadRadius: 5, //阴影扩散程度 26 | ), 27 | ]; 28 | 29 | // 定义基本组件的尺寸 30 | const double iSearchHeight = 54.0; 31 | const double iModalHeaderHeight = 65.0; 32 | const double iColumnMenuHeight = 60.0; 33 | 34 | // appbar 35 | const TextStyle iAppBarTextStyle = TextStyle( 36 | color: Colors.black, 37 | fontSize: 24, 38 | fontWeight: FontWeight.bold, 39 | ); 40 | 41 | // app-navbar 42 | const double iNavBarHeight = 65.0; 43 | const double iNavBarIconSize = 40.0; 44 | const iNavbarColor = Color(0xFFAAACB8); 45 | const iNavbarActiveColor = Colors.white; 46 | 47 | // chance page 48 | const double iFollowMenuHeight = 50.0; 49 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/components/badge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | const double badgeSize = 20.0; 5 | const double badgeDotSize = 10.0; 6 | 7 | class SelfBadge extends StatelessWidget { 8 | const SelfBadge( 9 | this.value, { 10 | Key? key, 11 | this.right, 12 | this.top, 13 | this.left, 14 | this.bottom, 15 | this.color = Colors.red, 16 | this.dot = false, 17 | this.fixed = true, 18 | }) : super(key: key); 19 | 20 | final double? right, top, left, bottom; 21 | final bool dot, fixed; 22 | final Color? color; 23 | final String value; 24 | 25 | Widget _buildContent() { 26 | TextStyle _style = TextStyle(fontSize: 12, color: Colors.white); 27 | return Container( 28 | padding: EdgeInsets.symmetric(horizontal: 5), 29 | height: dot ? badgeDotSize : badgeSize, 30 | decoration: BoxDecoration( 31 | color: color, 32 | borderRadius: BorderRadius.all(Radius.circular(dot ? badgeDotSize : badgeSize)), 33 | ), 34 | child: dot 35 | ? Text('') 36 | : Align( 37 | child: Text(value, style: _style), 38 | ), 39 | ); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return fixed 45 | ? Positioned( 46 | left: left, 47 | right: right, 48 | top: top, 49 | bottom: bottom, 50 | child: _buildContent(), 51 | ) 52 | : _buildContent(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/pages/home/components/ask_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | import 'package:flutter_maimai/pages/home/components/ask_menu_item.dart'; 5 | 6 | class AskMenu extends StatelessWidget { 7 | const AskMenu({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | margin: EdgeInsets.all(iDefaultPadding / 2), 13 | child: Column( 14 | children: [ 15 | AskMenuItem( 16 | title: '去发现感兴趣的公司', 17 | desc: '暂无关注的公司', 18 | icon: SelfIcon.Home, 19 | isLink: true, 20 | press: () {}, 21 | ), 22 | SizedBox(height: iDefaultPadding / 2), 23 | Row( 24 | children: [ 25 | Expanded( 26 | child: AskMenuItem( 27 | title: '大厂职级揭秘', 28 | desc: '洞察大环境', 29 | icon: SelfIcon.Home, 30 | press: () {}, 31 | ), 32 | ), 33 | SizedBox(width: iDefaultPadding / 2), 34 | Expanded( 35 | child: AskMenuItem( 36 | title: '员工认可榜单', 37 | desc: '甄选好机遇', 38 | icon: SelfIcon.Home, 39 | press: () {}, 40 | ), 41 | ), 42 | ], 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/pages/home/components/hot_choiceness.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | import 'package:flutter_maimai/mock/home.dart'; 4 | import 'package:flutter_maimai/pages/home/components/choiceness_item.dart'; 5 | 6 | class HotChoiceness extends StatefulWidget { 7 | const HotChoiceness({Key? key}) : super(key: key); 8 | 9 | @override 10 | _HotChoicenessState createState() => _HotChoicenessState(); 11 | } 12 | 13 | class _HotChoicenessState extends State { 14 | List columnList = HotChoicenessColumnMock; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | Size size = MediaQuery.of(context).size; 19 | return Container( 20 | width: size.width, 21 | height: 160, 22 | margin: EdgeInsets.only(left: iDefaultPadding), 23 | child: Column( 24 | crossAxisAlignment: CrossAxisAlignment.start, 25 | children: [ 26 | Padding( 27 | padding: EdgeInsets.only(bottom: iDefaultPadding / 2), 28 | child: Text( 29 | '精选专栏', 30 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), 31 | ), 32 | ), 33 | Container( 34 | height: 120, 35 | child: ListView.builder( 36 | shrinkWrap: true, 37 | scrollDirection: Axis.horizontal, 38 | itemBuilder: (context, index) { 39 | return ChoicenessItem(item: columnList[index]); 40 | }, 41 | itemCount: columnList.length, 42 | ), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/pages/home/ask.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/app_ad.dart'; 3 | import 'package:flutter_maimai/components/safe_area.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | import 'package:flutter_maimai/mock/ad.dart'; 6 | import 'package:flutter_maimai/mock/home.dart'; 7 | import 'package:flutter_maimai/pages/home/components/ask_item.dart'; 8 | import 'package:flutter_maimai/pages/home/components/ask_menu.dart'; 9 | 10 | class Ask extends StatefulWidget { 11 | const Ask({Key? key}) : super(key: key); 12 | 13 | @override 14 | _AskState createState() => _AskState(); 15 | } 16 | 17 | class _AskState extends State { 18 | List askList = AskMockList; 19 | @override 20 | Widget build(BuildContext context) { 21 | Size size = MediaQuery.of(context).size; 22 | return Container( 23 | height: size.height, 24 | child: ListView.builder( 25 | shrinkWrap: true, 26 | itemBuilder: (context, index) { 27 | if (index == 0) { 28 | return AskMenu(); 29 | } 30 | if (index == 1) { 31 | return ImageAd( 32 | AskImageAdUrl, 33 | margin: EdgeInsets.only( 34 | left: iDefaultPadding / 2, 35 | right: iDefaultPadding / 2, 36 | bottom: iDefaultPadding / 2, 37 | ), 38 | ); 39 | } 40 | if (index == askList.length + 2) { 41 | return SafeAreaBottom(); 42 | } 43 | return AskItem(askList[index - 2]); 44 | }, 45 | itemCount: askList.length + 3, 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/components/customer_tab_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/badge.dart'; 3 | 4 | class CustomerTabItem extends StatelessWidget { 5 | const CustomerTabItem({ 6 | Key? key, 7 | required this.title, 8 | required this.active, 9 | required this.press, 10 | this.badge = 'new', 11 | this.haveBadge = false, 12 | this.haveBorder = false, 13 | this.isScale = false, 14 | }) : super(key: key); 15 | 16 | final String title, badge; 17 | final bool active, haveBadge, haveBorder, isScale; 18 | final VoidCallback press; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | TextStyle _style = TextStyle( 23 | fontSize: isScale ? (active ? 20 : 18) : 18, 24 | color: active ? Colors.black : Colors.black38, 25 | fontWeight: FontWeight.w500, 26 | ); 27 | return GestureDetector( 28 | onTap: press, 29 | child: Container( 30 | height: 30, 31 | child: Stack( 32 | clipBehavior: Clip.none, 33 | children: [ 34 | haveBorder 35 | ? Column( 36 | children: [ 37 | Text(title, style: _style), 38 | Container( 39 | height: 2, 40 | width: 20, 41 | decoration: BoxDecoration( 42 | color: active ? Colors.black : Colors.transparent, 43 | ), 44 | ), 45 | ], 46 | ) 47 | : Text(title, style: _style), 48 | haveBadge ? SelfBadge(badge, top: -8, right: -30) : Text(''), 49 | ], 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/pages/chance/components/company_list.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class CompanyList extends StatelessWidget { 6 | const CompanyList({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | child: Column( 12 | children: [ 13 | SliverPersistentHeader( 14 | pinned: true, //是否固定在顶部 15 | floating: true, 16 | delegate: _SliverAppBarDelegate( 17 | minHeight: 50, //收起的高度 18 | maxHeight: 50, //展开的最大高度 19 | child: Container( 20 | padding: EdgeInsets.only(left: 16), 21 | color: Colors.pink, 22 | alignment: Alignment.centerLeft, 23 | child: Text("浮动", style: TextStyle(fontSize: 18)), 24 | )), 25 | ), 26 | ], 27 | ), 28 | ); 29 | } 30 | } 31 | 32 | class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { 33 | _SliverAppBarDelegate({ 34 | required this.minHeight, 35 | required this.maxHeight, 36 | required this.child, 37 | }); 38 | 39 | final double minHeight; 40 | final double maxHeight; 41 | final Widget child; 42 | 43 | @override 44 | double get minExtent => minHeight; 45 | 46 | @override 47 | double get maxExtent => max(maxHeight, minHeight); 48 | 49 | @override 50 | Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { 51 | return new SizedBox.expand(child: child); 52 | } 53 | 54 | @override 55 | bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { 56 | return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight || child != oldDelegate.child; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/components/column_menu_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | 5 | const _defaultExt = Text(''); 6 | 7 | class ColumnMenuItem extends StatelessWidget { 8 | const ColumnMenuItem({ 9 | Key? key, 10 | this.ext = _defaultExt, 11 | required this.value, 12 | required this.press, 13 | }) : super(key: key); 14 | 15 | final Widget ext; 16 | final String value; 17 | final VoidCallback press; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return InkWell( 22 | onTap: press, 23 | child: Container( 24 | height: iColumnMenuHeight, 25 | decoration: BoxDecoration( 26 | color: Colors.transparent, 27 | border: new Border( 28 | bottom: BorderSide(color: Colors.black.withOpacity(0.03), width: 1), 29 | ), 30 | ), 31 | child: Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 33 | children: [ 34 | Text( 35 | value, 36 | style: TextStyle( 37 | fontSize: 16, 38 | fontWeight: FontWeight.w500, 39 | ), 40 | ), 41 | Expanded( 42 | flex: 1, 43 | child: Row( 44 | mainAxisAlignment: MainAxisAlignment.end, 45 | children: [ 46 | ext, 47 | ], 48 | ), 49 | ), 50 | SizedBox(width: iDefaultPadding / 4), 51 | Icon( 52 | SelfIcon.ArrowRightFilling, 53 | size: 12, 54 | color: Colors.black26, 55 | ), 56 | ], 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_maimai 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/pages/contact/components/friend_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/badge.dart'; 3 | import 'package:flutter_maimai/components/card_container.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | import 'package:flutter_maimai/mock/contact.dart'; 6 | import 'package:flutter_maimai/pages/contact/components/friend_msg_item.dart'; 7 | 8 | class FriendMsg extends StatefulWidget { 9 | const FriendMsg({Key? key}) : super(key: key); 10 | 11 | @override 12 | _FriendMsgState createState() => _FriendMsgState(); 13 | } 14 | 15 | class _FriendMsgState extends State { 16 | List msgList = FriendMsgList; 17 | 18 | Widget _buildTitle() { 19 | return Padding( 20 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding), 21 | child: Row( 22 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 23 | children: [ 24 | Text('好友动态', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.black)), 25 | SelfBadge('', dot: true, fixed: false), 26 | ], 27 | ), 28 | ); 29 | } 30 | 31 | Widget _buildMsgList() { 32 | return SizedBox( 33 | height: 100, 34 | child: ListView.builder( 35 | itemBuilder: (context, index) { 36 | return FriendMsgItem( 37 | message: msgList[index], 38 | press: () {}, 39 | ); 40 | }, 41 | itemCount: msgList.length, 42 | ), 43 | ); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return CardContainer( 49 | padding: EdgeInsets.only(left: iDefaultPadding, right: iDefaultPadding, bottom: iDefaultPadding), 50 | child: Column( 51 | children: [ 52 | _buildTitle(), 53 | _buildMsgList(), 54 | ], 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/components/app_ad.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | const EdgeInsets _margin = EdgeInsets.only( 5 | left: iDefaultPadding, 6 | right: iDefaultPadding, 7 | bottom: iDefaultPadding, 8 | ); 9 | 10 | class BannerAd extends StatelessWidget { 11 | const BannerAd({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container(); 16 | } 17 | } 18 | 19 | class ImageAd extends StatelessWidget { 20 | const ImageAd( 21 | this.image, { 22 | Key? key, 23 | this.margin = _margin, 24 | }) : super(key: key); 25 | 26 | final String image; 27 | final EdgeInsets margin; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | Size size = MediaQuery.of(context).size; 32 | return Container( 33 | height: 120, 34 | width: size.width, 35 | margin: margin, 36 | decoration: BoxDecoration( 37 | borderRadius: iBorderRadius, 38 | image: new DecorationImage( 39 | image: new NetworkImage(image), 40 | fit: BoxFit.cover, 41 | ), 42 | ), 43 | child: Stack( 44 | children: [ 45 | Positioned( 46 | top: 0, 47 | right: 0, 48 | child: Container( 49 | height: 18, 50 | width: 30, 51 | decoration: BoxDecoration( 52 | color: Colors.white.withOpacity(0.4), 53 | borderRadius: BorderRadius.only(bottomLeft: Radius.circular(10)), 54 | ), 55 | child: Center( 56 | child: Text( 57 | '广告', 58 | style: TextStyle(color: Colors.white, fontSize: 10), 59 | ), 60 | ), 61 | ), 62 | ), 63 | ], 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/components/app_navbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | 5 | class AppNavBar extends StatelessWidget { 6 | const AppNavBar({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Padding( 11 | padding: EdgeInsets.only( 12 | left: iDefaultPadding, 13 | // bottom: iDefaultPadding, 14 | right: iDefaultPadding, 15 | ), 16 | child: Container( 17 | decoration: BoxDecoration( 18 | color: iAppBottomBarColor, 19 | borderRadius: BorderRadius.circular(15), 20 | // boxShadow: [ 21 | // BoxShadow( 22 | // offset: Offset(0, -10), 23 | // blurRadius: 35, 24 | // color: iPrimaryColor.withOpacity(0.38), 25 | // ), 26 | // ], 27 | ), 28 | child: Row( 29 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 30 | children: [ 31 | IconButton( 32 | onPressed: () {}, 33 | icon: SvgPicture.asset('assets/icons/flower.svg'), 34 | ), 35 | IconButton( 36 | onPressed: () {}, 37 | icon: SvgPicture.asset('assets/icons/heart-icon.svg'), 38 | ), 39 | IconButton( 40 | onPressed: () {}, 41 | icon: SvgPicture.asset('assets/icons/heart-icon.svg'), 42 | ), 43 | IconButton( 44 | onPressed: () {}, 45 | icon: SvgPicture.asset('assets/icons/heart-icon.svg'), 46 | ), 47 | IconButton( 48 | onPressed: () {}, 49 | icon: SvgPicture.asset('assets/icons/user-icon.svg'), 50 | ), 51 | ], 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.example.flutter_maimai" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /lib/pages/contact/components/request_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/card_container.dart'; 3 | import 'package:flutter_maimai/components/self_icon.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | 6 | class RequestList extends StatelessWidget { 7 | const RequestList({Key? key}) : super(key: key); 8 | 9 | Widget _buildTitle() { 10 | return Padding( 11 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding), 12 | child: Row( 13 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 14 | children: [ 15 | Text( 16 | '待处理请求', 17 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.black), 18 | ), 19 | Row( 20 | children: [ 21 | Text( 22 | '管理我的请求', 23 | style: TextStyle(fontSize: 16, color: Colors.black38), 24 | ), 25 | Icon( 26 | SelfIcon.ArrowRightFilling, 27 | color: Colors.black26, 28 | size: 12, 29 | ), 30 | ], 31 | ), 32 | ], 33 | ), 34 | ); 35 | } 36 | 37 | Widget _buildContent() { 38 | return Padding( 39 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding / 2), 40 | child: Center( 41 | child: Text( 42 | '暂无好友请求,先来添加认识的好友吧!', 43 | style: TextStyle( 44 | fontSize: 14, 45 | color: Colors.black26, 46 | ), 47 | ), 48 | ), 49 | ); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return CardContainer( 55 | padding: EdgeInsets.only(left: iDefaultPadding, right: iDefaultPadding, bottom: iDefaultPadding), 56 | child: Column( 57 | children: [ 58 | _buildTitle(), 59 | _buildContent(), 60 | ], 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/mock/message.dart: -------------------------------------------------------------------------------- 1 | List MessageMockList = [ 2 | { 3 | "avatar": "https://wap.duoziwang.com/uploads/1512/1-1512261552020-L.jpg", 4 | "name": "红包小助手", 5 | "isOfficial": true, 6 | "company": "", 7 | "isCompany": false, 8 | "tip": "这个是消息的提示内容可", 9 | "date": "08-16", 10 | "vip": false, 11 | }, 12 | { 13 | "avatar": "https://img2.baidu.com/it/u=1879129369,3648280576&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=400", 14 | "name": "脉脉小助手", 15 | "isOfficial": true, 16 | "company": "", 17 | "isCompany": false, 18 | "tip": "这个是消息的提示内容可", 19 | "date": "08-16", 20 | "vip": false, 21 | }, 22 | { 23 | "avatar": 24 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180520%2F0473e00bdfd2476fbe0c228a45a1652c.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631705350&t=925858ee5e392535d8f58d30b2219701", 25 | "name": "机遇小助手", 26 | "isOfficial": true, 27 | "company": "", 28 | "isCompany": false, 29 | "tip": "这个是消息的提示内容可", 30 | "date": "08-16", 31 | "vip": false, 32 | }, 33 | { 34 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/13625975163/1000.jpg", 35 | "name": "活动运营官", 36 | "isOfficial": true, 37 | "company": "", 38 | "isCompany": false, 39 | "tip": "这个是消息的提示内容可", 40 | "date": "08-16", 41 | "vip": false, 42 | }, 43 | { 44 | "avatar": 45 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fq_70%2Cc_zoom%2Cw_640%2Fimages%2F20180831%2F5055651e9ece4799b3baa4dddd4d1e25.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631705350&t=8aef574e96578cdfafb80d70c800c5b9", 46 | "name": "好友XXX", 47 | "isOfficial": true, 48 | "company": "", 49 | "isCompany": false, 50 | "tip": "这个是消息的提示内容可", 51 | "date": "08-16", 52 | "vip": false, 53 | }, 54 | ]; 55 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_maimai 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | cupertino_icons: ^1.0.2 27 | # it helps us to use SVG 28 | flutter_svg: ^0.22.0 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | assets: 47 | - assets/icons/ 48 | - assets/images/ 49 | 50 | fonts: 51 | - family: iconfont 52 | fonts: 53 | - asset: assets/fonts/iconfont.ttf 54 | # 55 | # For details regarding fonts from package dependencies, 56 | # see https://flutter.dev/custom-fonts/#from-packages 57 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/mock/contact.dart: -------------------------------------------------------------------------------- 1 | List FriendMsgList = [ 2 | { 3 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659087/1000", 4 | "nickname": "张三", 5 | "tip": "#项目经理", 6 | }, 7 | { 8 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659094/1000", 9 | "nickname": "李四", 10 | "tip": "#Web前端", 11 | }, 12 | { 13 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659379/1000", 14 | "nickname": "王五", 15 | "tip": "正在招聘", 16 | }, 17 | { 18 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659613/1000", 19 | "nickname": "赵六", 20 | "tip": "#PM", 21 | }, 22 | ]; 23 | 24 | String QinghuaLogo = 25 | 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic4.zhimg.com%2F50%2Fv2-f2707167a67b3b71cc5b450e3246862d_hd.jpg&refer=http%3A%2F%2Fpic4.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631769511&t=e16517404c5d40388daa0e2fa8919fc8'; 26 | 27 | List ContactMockList = [ 28 | { 29 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659087/1000", 30 | "name": "张三", 31 | "post": "杭州CCC公司 前端开发", 32 | "vip": false, 33 | "tag": ["正在招聘", "10家大公司人脉"], 34 | "other": ["云计算/计算机"], 35 | }, 36 | { 37 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659094/1000", 38 | "name": "李四", 39 | "post": "杭州XXX网络有限责任公司前端开发工程师", 40 | "vip": false, 41 | "tag": ["正在招聘", "企业级软件"], 42 | "other": ["有超过2000位好友"], 43 | }, 44 | { 45 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659379/1000", 46 | "name": "王五", 47 | "post": "杭州CCC公司 前端开发", 48 | "vip": false, 49 | "tag": ["正在招聘", "10家大公司人脉"], 50 | "other": ["云计算/计算机"], 51 | }, 52 | { 53 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659087/1000", 54 | "name": "张三", 55 | "post": "杭州CCC公司 前端开发", 56 | "vip": false, 57 | "tag": ["正在招聘", "10家大公司人脉"], 58 | "other": ["云计算/计算机"], 59 | }, 60 | { 61 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659094/1000", 62 | "name": "李四", 63 | "post": "杭州XXX网络有限责任公司前端开发工程师", 64 | "vip": false, 65 | "tag": ["正在招聘", "企业级软件"], 66 | "other": ["有超过2000位好友"], 67 | }, 68 | { 69 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659379/1000", 70 | "name": "王五", 71 | "post": "杭州CCC公司 前端开发", 72 | "vip": false, 73 | "tag": ["正在招聘", "10家大公司人脉"], 74 | "other": ["云计算/计算机"], 75 | }, 76 | ]; 77 | -------------------------------------------------------------------------------- /lib/pages/home/components/follows_recommend.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/card_container.dart'; 3 | import 'package:flutter_maimai/components/self_icon.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | import 'package:flutter_maimai/mock/home.dart'; 6 | import 'package:flutter_maimai/pages/home/components/user_item.dart'; 7 | 8 | class FollowsRecommend extends StatefulWidget { 9 | const FollowsRecommend({Key? key}) : super(key: key); 10 | 11 | @override 12 | _FollowsRecommendState createState() => _FollowsRecommendState(); 13 | } 14 | 15 | class _FollowsRecommendState extends State { 16 | List userList = FollowsRecommendMockList; 17 | 18 | Widget _buildTitle() { 19 | return Container( 20 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding / 2), 21 | child: Text( 22 | '为你推荐', 23 | style: TextStyle(fontSize: 20, color: Colors.black, fontWeight: FontWeight.w500), 24 | ), 25 | ); 26 | } 27 | 28 | Widget _buildContent() { 29 | List _list = []; 30 | for (int i = 0; i < userList.length; i++) { 31 | _list.add(UserItem(userList[i])); 32 | } 33 | return Column( 34 | children: _list, 35 | ); 36 | } 37 | 38 | Widget _buildFooter() { 39 | return Container( 40 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding / 1.5), 41 | decoration: BoxDecoration( 42 | border: new Border(top: BorderSide(width: 0.3, color: Colors.black12)), 43 | ), 44 | child: Row( 45 | mainAxisAlignment: MainAxisAlignment.center, 46 | crossAxisAlignment: CrossAxisAlignment.center, 47 | children: [ 48 | Text('查看更多', style: TextStyle(fontSize: 16, color: Colors.black54)), 49 | SizedBox(width: iDefaultPadding / 4), 50 | Icon(SelfIcon.ArrowRightFilling, size: 14, color: Colors.black45), 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return CardContainer( 59 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding), 60 | child: Column( 61 | crossAxisAlignment: CrossAxisAlignment.start, 62 | children: [ 63 | _buildTitle(), 64 | _buildContent(), 65 | _buildFooter(), 66 | ], 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/pages/chance/components/follow_market.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/card_container.dart'; 3 | import 'package:flutter_maimai/components/menu_item.dart'; 4 | import 'package:flutter_maimai/components/self_icon.dart'; 5 | import 'package:flutter_maimai/constants.dart'; 6 | 7 | class FollowMarket extends StatelessWidget { 8 | const FollowMarket({Key? key}) : super(key: key); 9 | 10 | Widget _buildTitle() { 11 | return Padding( 12 | padding: EdgeInsets.all(iDefaultPadding), 13 | child: Row( 14 | crossAxisAlignment: CrossAxisAlignment.center, 15 | children: [ 16 | CircleAvatar( 17 | radius: 15, 18 | backgroundImage: AssetImage('assets/images/avatar.png'), 19 | ), 20 | Container( 21 | margin: EdgeInsets.only(left: iDefaultPadding / 2), 22 | child: Row( 23 | crossAxisAlignment: CrossAxisAlignment.center, 24 | children: [ 25 | Text( 26 | '关注行情', 27 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), 28 | ), 29 | Icon( 30 | SelfIcon.ArrowRightFilling, 31 | size: 12, 32 | ), 33 | ], 34 | ), 35 | ), 36 | ], 37 | ), 38 | ); 39 | } 40 | 41 | Widget _buildMenuList() { 42 | return Row( 43 | mainAxisAlignment: MainAxisAlignment.spaceAround, 44 | children: [ 45 | MenuItem( 46 | name: '我的简历', 47 | press: () {}, 48 | icon: SelfIcon.FolderFilling, 49 | ), 50 | MenuItem( 51 | name: '我的申请', 52 | press: () {}, 53 | icon: SelfIcon.HistoryFilling, 54 | ), 55 | MenuItem( 56 | name: '职位收藏', 57 | press: () {}, 58 | icon: SelfIcon.FavoriteFilling, 59 | ), 60 | MenuItem( 61 | name: '更多', 62 | press: () {}, 63 | icon: SelfIcon.DynamicFilling, 64 | ), 65 | ], 66 | ); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return CardContainer( 72 | padding: EdgeInsets.only(bottom: iDefaultPadding), 73 | child: Column( 74 | children: [ 75 | _buildTitle(), 76 | // Spacer(), 77 | _buildMenuList(), 78 | ], 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/components/search_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | 5 | class SearchField extends StatelessWidget { 6 | const SearchField({ 7 | Key? key, 8 | this.placeholder = '输入搜索内容', 9 | this.btnText = '搜索', 10 | this.haveButton = false, 11 | this.haveGap = true, 12 | }) : super(key: key); 13 | 14 | final String placeholder, btnText; 15 | final bool haveButton, haveGap; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Container( 20 | margin: EdgeInsets.all(haveGap ? iDefaultPadding : 0), 21 | padding: EdgeInsets.only(right: haveGap ? 5 : 0, left: haveGap ? 10 : 0), 22 | height: 45, 23 | decoration: BoxDecoration( 24 | color: Colors.white, 25 | borderRadius: BorderRadius.all(Radius.circular(45)), 26 | ), 27 | child: Row( 28 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 29 | children: [ 30 | Icon( 31 | SelfIcon.Search, 32 | size: 20, 33 | ), 34 | Expanded( 35 | flex: 1, 36 | child: Padding( 37 | padding: EdgeInsets.symmetric(horizontal: 10), 38 | child: TextField( 39 | onChanged: (value) {}, 40 | decoration: InputDecoration( 41 | hintText: placeholder, 42 | hintStyle: TextStyle(color: Colors.black45, fontSize: 16), 43 | enabledBorder: InputBorder.none, 44 | focusedBorder: InputBorder.none, 45 | ), 46 | ), 47 | ), 48 | ), 49 | haveButton 50 | ? ElevatedButton( 51 | onPressed: () {}, 52 | child: Text(btnText), 53 | style: ButtonStyle( 54 | padding: MaterialStateProperty.all(EdgeInsets.all(1)), 55 | textStyle: MaterialStateProperty.all(TextStyle( 56 | fontSize: 14, 57 | )), 58 | backgroundColor: MaterialStateProperty.all(iPrimaryColor), 59 | shape: MaterialStateProperty.all( 60 | RoundedRectangleBorder( 61 | borderRadius: BorderRadius.all(Radius.circular(20)), 62 | ), 63 | ), 64 | ), 65 | ) 66 | : Text(''), 67 | ], 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/pages/message/components/message_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | import 'package:flutter_maimai/pages/message/components/account_type_tag.dart'; 4 | 5 | const double itemHeight = 60.0; 6 | 7 | class MessageItem extends StatelessWidget { 8 | const MessageItem({ 9 | Key? key, 10 | required this.message, 11 | required this.press, 12 | }) : super(key: key); 13 | 14 | final message; 15 | final VoidCallback press; 16 | 17 | Widget _buildTitle(Size size) { 18 | TextStyle _titleStyle = TextStyle(color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold); 19 | TextStyle _dateStyle = TextStyle(color: Colors.black26, fontSize: 14, fontWeight: FontWeight.w400); 20 | return Container( 21 | width: size.width * 0.7, 22 | child: Row( 23 | // mainAxisAlignment: MainAxisAlignment.start, 24 | children: [ 25 | Text(message['name'], style: _titleStyle), 26 | AccountTypeTag(isCompany: message['isCompany'], isOfficial: message['isOfficial']), 27 | Spacer(), 28 | Text(message['date'], style: _dateStyle), 29 | ], 30 | ), 31 | ); 32 | } 33 | 34 | Widget _buildContent(Size size) { 35 | TextStyle _tipStyle = TextStyle(color: Colors.black45, fontSize: 16, fontWeight: FontWeight.w500); 36 | return Container( 37 | width: size.width * 0.7, 38 | child: Text( 39 | message['tip'], 40 | style: _tipStyle, 41 | overflow: TextOverflow.ellipsis, 42 | maxLines: 1, 43 | ), 44 | ); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | Size size = MediaQuery.of(context).size; 50 | return GestureDetector( 51 | onTap: press, 52 | child: Container( 53 | color: Colors.white, 54 | width: size.width, 55 | height: itemHeight + iDefaultPadding * 2, 56 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding, vertical: iDefaultPadding), 57 | child: Row( 58 | children: [ 59 | CircleAvatar( 60 | radius: itemHeight / 2, 61 | backgroundImage: NetworkImage(message['avatar']), 62 | ), 63 | SizedBox(width: 10), 64 | Column( 65 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 66 | crossAxisAlignment: CrossAxisAlignment.start, 67 | children: [ 68 | _buildTitle(size), 69 | _buildContent(size), 70 | ], 71 | ), 72 | ], 73 | ), 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/pages/home/components/ask_menu_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/badge.dart'; 3 | import 'package:flutter_maimai/components/self_icon.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | 6 | class AskMenuItem extends StatelessWidget { 7 | const AskMenuItem({ 8 | Key? key, 9 | required this.title, 10 | required this.desc, 11 | required this.icon, 12 | required this.press, 13 | this.isLink = false, 14 | }) : super(key: key); 15 | 16 | final String title, desc; 17 | final IconData icon; 18 | final VoidCallback press; 19 | final bool isLink; 20 | @override 21 | Widget build(BuildContext context) { 22 | return Container( 23 | height: 80, 24 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding / 2, vertical: iDefaultPadding / 2), 25 | decoration: BoxDecoration( 26 | borderRadius: iBorderRadius, 27 | color: Colors.white, 28 | ), 29 | child: Row( 30 | children: [ 31 | Container( 32 | width: 60, 33 | height: 60, 34 | margin: EdgeInsets.only(right: iDefaultPadding / 2), 35 | child: Icon( 36 | icon, 37 | size: 40, 38 | ), 39 | ), 40 | Expanded( 41 | child: Container( 42 | height: 45, 43 | child: Column( 44 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 45 | crossAxisAlignment: CrossAxisAlignment.start, 46 | children: [ 47 | Text( 48 | title, 49 | style: TextStyle( 50 | fontSize: 16, 51 | fontWeight: FontWeight.w500, 52 | color: Colors.black, 53 | ), 54 | ), 55 | Text( 56 | desc, 57 | style: TextStyle( 58 | fontSize: 14, 59 | fontWeight: FontWeight.w400, 60 | color: Colors.black38, 61 | ), 62 | ), 63 | ], 64 | ), 65 | ), 66 | ), 67 | isLink 68 | ? Row( 69 | children: [ 70 | SelfBadge('', dot: true, fixed: false), 71 | SizedBox(width: iDefaultPadding / 4), 72 | Icon(SelfIcon.ArrowRight, size: 16, color: Colors.black38), 73 | ], 74 | ) 75 | : Text(''), 76 | ], 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/pages/owner/components/owner_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/card_container.dart'; 3 | import 'package:flutter_maimai/components/self_icon.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | import 'package:flutter_maimai/pages/owner/components/owner_detail_item.dart'; 6 | import 'package:flutter_maimai/pages/owner/components/owner_detail_line.dart'; 7 | 8 | class OwnerInfo extends StatelessWidget { 9 | const OwnerInfo({Key? key}) : super(key: key); 10 | 11 | Widget _buildInfo() { 12 | TextStyle _style = TextStyle(color: Colors.black87, fontSize: 16, fontWeight: FontWeight.w500); 13 | return Row( 14 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | children: [ 17 | CircleAvatar( 18 | radius: 40, 19 | backgroundImage: AssetImage('assets/images/avatar.png'), 20 | ), 21 | Expanded( 22 | flex: 1, 23 | child: Padding( 24 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding / 2), 25 | child: Column( 26 | crossAxisAlignment: CrossAxisAlignment.start, 27 | children: [ 28 | Text( 29 | '小码农', 30 | style: TextStyle(fontSize: 25, color: Colors.black, fontWeight: FontWeight.bold), 31 | ), 32 | Text('悠点互娱', style: _style), 33 | Text('CEO', style: _style), 34 | ], 35 | ), 36 | ), 37 | ), 38 | Row( 39 | children: [ 40 | Text('个人主页'), 41 | Icon(SelfIcon.ArrowRightFilling, size: 12), 42 | ], 43 | ), 44 | ], 45 | ); 46 | } 47 | 48 | Widget _buildDetail() { 49 | return Padding( 50 | padding: EdgeInsets.only(top: iDefaultPadding), 51 | child: Row( 52 | children: [ 53 | OwnerDetailItem(label: '好友', value: 1), 54 | OwnerDetailLine(), 55 | OwnerDetailItem(label: '影响力', value: 22), 56 | OwnerDetailLine(), 57 | OwnerDetailItem(label: '访客', value: 10), 58 | OwnerDetailLine(), 59 | OwnerDetailItem(label: '内容发布', value: 0), 60 | ], 61 | ), 62 | ); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return CardContainer( 68 | margin: EdgeInsets.only(left: iDefaultPadding, right: iDefaultPadding, bottom: iDefaultPadding, top: iDefaultPadding / 2), 69 | padding: EdgeInsets.all(iDefaultPadding), 70 | child: Column( 71 | children: [ 72 | _buildInfo(), 73 | _buildDetail(), 74 | ], 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/pages/home/components/user_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/vip_badge.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | 5 | class UserItem extends StatelessWidget { 6 | const UserItem(this.userInfo, {Key? key}) : super(key: key); 7 | 8 | final userInfo; 9 | 10 | Widget _buildPostInfo(Size size) { 11 | String _company = userInfo['company']; 12 | String _post = userInfo['post']; 13 | bool _isVip = userInfo['vip']; 14 | return Row( 15 | mainAxisAlignment: MainAxisAlignment.start, 16 | children: [ 17 | SizedBox( 18 | width: size.width * 0.4, 19 | child: Text( 20 | '$_company$_post', 21 | overflow: TextOverflow.ellipsis, 22 | style: TextStyle(color: Colors.black45, fontSize: 14), 23 | ), 24 | ), 25 | _isVip ? VipBadge() : Text(''), 26 | ], 27 | ); 28 | } 29 | 30 | Widget _buildMoreInfo() { 31 | int _count = userInfo['msgCount']; 32 | return _count > 0 ? Text('近30天发布了$_count个动态', style: TextStyle(color: Colors.black45, fontSize: 14)) : Text(''); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | Size size = MediaQuery.of(context).size; 38 | return Container( 39 | margin: EdgeInsets.symmetric(vertical: iDefaultPadding / 2), 40 | child: Row( 41 | children: [ 42 | CircleAvatar( 43 | radius: 25, 44 | backgroundImage: NetworkImage(userInfo['avatar']), 45 | ), 46 | SizedBox(width: iDefaultPadding / 2), 47 | Expanded( 48 | child: Column( 49 | mainAxisAlignment: MainAxisAlignment.center, 50 | crossAxisAlignment: CrossAxisAlignment.start, 51 | children: [ 52 | Text( 53 | userInfo['name'], 54 | style: TextStyle(fontSize: 18, color: Colors.black, fontWeight: FontWeight.w500), 55 | ), 56 | _buildPostInfo(size), 57 | _buildMoreInfo(), 58 | ], 59 | ), 60 | ), 61 | TextButton( 62 | onPressed: () {}, 63 | child: Text( 64 | '关注', 65 | style: TextStyle(color: Colors.white), 66 | ), 67 | style: ButtonStyle( 68 | backgroundColor: MaterialStateProperty.all(iPrimaryColor), 69 | padding: MaterialStateProperty.all(EdgeInsets.symmetric(horizontal: iDefaultPadding)), 70 | shape: MaterialStateProperty.all( 71 | RoundedRectangleBorder( 72 | borderRadius: BorderRadius.all(Radius.circular(20)), 73 | ), 74 | ), 75 | ), 76 | ), 77 | ], 78 | ), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/pages/home/components/choiceness_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/constants.dart'; 3 | 4 | class ChoicenessItem extends StatelessWidget { 5 | const ChoicenessItem({ 6 | Key? key, 7 | required this.item, 8 | }) : super(key: key); 9 | 10 | final item; 11 | 12 | Widget _buildTag() { 13 | String _tag = item['tag']; 14 | if (_tag == '') return Text(''); 15 | return Positioned( 16 | right: iDefaultPadding / 2, 17 | top: iDefaultPadding / 2, 18 | child: Container( 19 | width: 25, 20 | height: 14, 21 | // padding: EdgeInsets.symmetric(horizontal: 5, ), 22 | decoration: BoxDecoration( 23 | color: Colors.red, 24 | borderRadius: BorderRadius.all(Radius.circular(4)), 25 | ), 26 | child: Center( 27 | child: Text(_tag, style: TextStyle(color: Colors.white, fontSize: 10, fontStyle: FontStyle.italic)), 28 | ), 29 | ), 30 | ); 31 | } 32 | 33 | Widget _buildTopImage() { 34 | return Container( 35 | width: 170, 36 | height: 60, 37 | decoration: BoxDecoration( 38 | borderRadius: BorderRadius.only( 39 | topLeft: Radius.circular(5), 40 | topRight: Radius.circular(5), 41 | ), 42 | image: DecorationImage( 43 | image: NetworkImage(item['picture']), 44 | fit: BoxFit.cover, 45 | ), 46 | ), 47 | child: Stack( 48 | children: [ 49 | Positioned( 50 | bottom: iDefaultPadding / 4, 51 | left: iDefaultPadding / 4, 52 | child: Text( 53 | item['name'], 54 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.white), 55 | ), 56 | ), 57 | _buildTag(), 58 | ], 59 | ), 60 | ); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Container( 66 | width: 170, 67 | // height: 120, 68 | margin: EdgeInsets.only(right: iDefaultPadding / 2), 69 | decoration: BoxDecoration( 70 | // color: Colors.yellow, 71 | border: Border.all(width: 0.3, color: Colors.black38), 72 | borderRadius: BorderRadius.all(Radius.circular(5)), 73 | ), 74 | child: Column( 75 | crossAxisAlignment: CrossAxisAlignment.start, 76 | children: [ 77 | _buildTopImage(), 78 | Text( 79 | item['desc'], 80 | style: TextStyle(fontSize: 12, color: Colors.black38), 81 | ), 82 | Padding( 83 | padding: EdgeInsets.only(left: iDefaultPadding / 4), 84 | child: Text( 85 | item['content'], 86 | overflow: TextOverflow.ellipsis, 87 | maxLines: 2, 88 | style: TextStyle(), 89 | ), 90 | ), 91 | ], 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/common/customer_tab_size_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class CustomerTabSizeIndicator extends Decoration { 5 | final double width; 6 | final BorderSide borderSide; 7 | final EdgeInsetsGeometry insets; 8 | 9 | const CustomerTabSizeIndicator({ 10 | this.borderSide = const BorderSide(width: 2.0, color: Colors.blue), 11 | this.insets = EdgeInsets.zero, 12 | this.width = 20, 13 | }) : assert(borderSide != null), 14 | assert(insets != null), 15 | assert(width != null); 16 | @override 17 | Decoration lerpFrom(Decoration? a, double t) { 18 | if (a is UnderlineTabIndicator) { 19 | return UnderlineTabIndicator( 20 | borderSide: BorderSide.lerp(a.borderSide, borderSide, t), 21 | insets: EdgeInsetsGeometry.lerp(a.insets, insets, t) as EdgeInsetsGeometry, 22 | ); 23 | } 24 | return super.lerpFrom(a, t) as Decoration; 25 | } 26 | 27 | @override 28 | Decoration lerpTo(Decoration? b, double t) { 29 | if (b is CustomerTabSizeIndicator) { 30 | return CustomerTabSizeIndicator( 31 | borderSide: BorderSide.lerp(borderSide, b.borderSide, t), 32 | insets: EdgeInsetsGeometry.lerp(insets, b.insets, t) as EdgeInsetsGeometry, 33 | ); 34 | } 35 | return super.lerpTo(b, t) as Decoration; 36 | } 37 | 38 | @override 39 | _MyUnderlinePainter createBoxPainter([VoidCallback? onChanged]) { 40 | return _MyUnderlinePainter(this, width, onChanged!); 41 | } 42 | } 43 | 44 | class _MyUnderlinePainter extends BoxPainter { 45 | final double width; 46 | _MyUnderlinePainter(this.decoration, this.width, VoidCallback onChanged) 47 | : assert(decoration != null), 48 | super(onChanged); 49 | 50 | final CustomerTabSizeIndicator decoration; 51 | 52 | BorderSide get borderSide => decoration.borderSide; 53 | EdgeInsetsGeometry get insets => decoration.insets; 54 | 55 | Rect _indicatorRectFor(Rect rect, TextDirection textDirection) { 56 | assert(rect != null); 57 | assert(textDirection != null); 58 | final Rect indicator = insets.resolve(textDirection).deflateRect(rect); 59 | 60 | //希望的宽度 61 | // double width = 20; 62 | //取中间坐标 63 | double cw = (indicator.left + indicator.right) / 2; 64 | return Rect.fromLTWH(cw - width / 2, indicator.bottom - borderSide.width, width, borderSide.width); 65 | } 66 | 67 | @override 68 | void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { 69 | assert(configuration != null); 70 | assert(configuration.size != null); 71 | final Rect rect = offset & (configuration.size as Size); 72 | final TextDirection textDirection = configuration.textDirection as TextDirection; 73 | final Rect indicator = _indicatorRectFor(rect, textDirection).deflate(borderSide.width / 2.0); 74 | // 规定绘制为圆角 75 | final Paint paint = borderSide.toPaint()..strokeCap = StrokeCap.round; 76 | canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/pages/home/components/ask_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/card_container.dart'; 3 | import 'package:flutter_maimai/components/tag.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | 6 | class AskItem extends StatelessWidget { 7 | const AskItem(this.askInfo, {Key? key}) : super(key: key); 8 | 9 | final askInfo; 10 | 11 | Widget _buildMention() { 12 | List _mentionData = askInfo['mention']; 13 | List _mentionList = []; 14 | for (int i = 0; i < _mentionData.length; i++) { 15 | _mentionList.add(Padding( 16 | padding: EdgeInsets.only(top: iDefaultPadding / 4, right: iDefaultPadding / 2), 17 | child: Text( 18 | '@${_mentionData[i]}', 19 | style: TextStyle(color: iPrimaryColor, fontSize: 14), 20 | ))); 21 | } 22 | return Row( 23 | children: _mentionList, 24 | ); 25 | } 26 | 27 | Widget _buildAnswer() { 28 | var _answer = askInfo['answer']; 29 | return Column( 30 | children: [ 31 | Padding( 32 | padding: EdgeInsets.only(top: iDefaultPadding, bottom: iDefaultPadding / 2), 33 | child: Row( 34 | children: [ 35 | CircleAvatar( 36 | radius: 10, 37 | backgroundImage: NetworkImage(_answer['logo']), 38 | ), 39 | Padding( 40 | padding: EdgeInsets.only(left: iDefaultPadding / 4), 41 | child: Text( 42 | _answer["name"], 43 | style: TextStyle(fontSize: 14), 44 | ), 45 | ), 46 | Padding( 47 | padding: EdgeInsets.symmetric(horizontal: 4), 48 | child: Tag('ID', plain: true), 49 | ), 50 | Text( 51 | '回答:', 52 | style: TextStyle(color: Colors.black54), 53 | ), 54 | ], 55 | ), 56 | ), 57 | Text( 58 | _answer['content'], 59 | softWrap: true, 60 | overflow: TextOverflow.ellipsis, 61 | maxLines: 2, 62 | style: TextStyle(fontSize: 18, color: Colors.black54), 63 | ) 64 | ], 65 | ); 66 | } 67 | 68 | Widget _buildFooter() { 69 | int _count = askInfo['count']; 70 | int _likes = askInfo['likes']; 71 | return Padding( 72 | padding: EdgeInsets.only(top: iDefaultPadding / 2), 73 | child: Text('$_count 回答 · $_likes 人觉得靠谱', style: TextStyle(color: Colors.black38)), 74 | ); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return CardContainer( 80 | margin: EdgeInsets.only(left: iDefaultPadding / 2, right: iDefaultPadding / 2, bottom: iDefaultPadding / 2), 81 | padding: EdgeInsets.all(iDefaultPadding), 82 | child: Column( 83 | crossAxisAlignment: CrossAxisAlignment.start, 84 | children: [ 85 | Text( 86 | askInfo['title'], 87 | style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, color: Colors.black), 88 | ), 89 | _buildMention(), 90 | _buildAnswer(), 91 | _buildFooter(), 92 | ], 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/components/navbar_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/badge.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | import 'package:flutter_maimai/enums.dart'; 5 | 6 | class NavbarItem extends StatelessWidget { 7 | const NavbarItem({ 8 | Key? key, 9 | required this.isActive, 10 | required this.name, 11 | required this.icon, 12 | this.num = 0, 13 | this.type = NavbarMessageType.number, 14 | required this.press, 15 | }) : super(key: key); 16 | 17 | final bool isActive; 18 | final String name; 19 | final IconData icon; 20 | final int num; 21 | final NavbarMessageType type; 22 | final VoidCallback press; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | Size size = MediaQuery.of(context).size; 27 | var itemWidth = (size.width * 0.9) / 5; 28 | bool isDot = type == NavbarMessageType.dot; 29 | // return 30 | return InkWell( 31 | onTap: press, 32 | child: Container( 33 | width: itemWidth, 34 | child: Stack( 35 | children: [ 36 | SizedBox( 37 | width: itemWidth, 38 | child: Column( 39 | mainAxisAlignment: MainAxisAlignment.center, 40 | crossAxisAlignment: CrossAxisAlignment.center, 41 | children: [ 42 | SizedBox( 43 | width: 40, 44 | height: 40, 45 | child: Icon( 46 | icon, 47 | color: isActive ? iNavbarActiveColor : iNavbarColor, 48 | ), 49 | ), 50 | Text( 51 | name, 52 | style: TextStyle( 53 | fontSize: 12, 54 | color: isActive ? iNavbarActiveColor : iNavbarColor, 55 | ), 56 | ) 57 | ], 58 | ), 59 | ), 60 | num > 0 61 | ? SelfBadge( 62 | '$num', 63 | dot: isDot, 64 | right: isDot ? itemWidth * 0.3 : itemWidth * 0.16, 65 | top: isDot ? 10 : 4, 66 | ) 67 | : Text(''), 68 | ], 69 | ), 70 | ), 71 | ); 72 | } 73 | } 74 | 75 | class NavbarItemBadge extends StatelessWidget { 76 | const NavbarItemBadge({ 77 | Key? key, 78 | required this.type, 79 | this.num = 0, 80 | }) : super(key: key); 81 | 82 | final int num; 83 | final NavbarMessageType type; 84 | 85 | Widget createBadge() { 86 | bool isDot = type == NavbarMessageType.dot; 87 | return Container( 88 | width: isDot ? 8 : 18, 89 | height: isDot ? 8 : 18, 90 | child: isDot 91 | ? Text('') 92 | : Center( 93 | child: Text('$num', style: TextStyle(fontSize: 10.0, color: Colors.white)), 94 | ), 95 | decoration: BoxDecoration( 96 | color: Colors.red, 97 | borderRadius: new BorderRadius.all( 98 | const Radius.circular(16.0), 99 | ), 100 | ), 101 | ); 102 | } 103 | 104 | @override 105 | Widget build(BuildContext context) { 106 | return num > 0 ? createBadge() : Text(''); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/pages/contact/components/contact_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | 5 | class ContactItem extends StatelessWidget { 6 | const ContactItem({ 7 | Key? key, 8 | required this.contact, 9 | required this.press, 10 | }) : super(key: key); 11 | 12 | final contact; 13 | final VoidCallback press; 14 | 15 | Widget _buildActionBtn() { 16 | return TextButton( 17 | onPressed: () {}, 18 | style: ButtonStyle( 19 | backgroundColor: MaterialStateProperty.all(iPrimaryColor), 20 | padding: MaterialStateProperty.all(EdgeInsets.symmetric(horizontal: iDefaultPadding)), 21 | shape: MaterialStateProperty.all( 22 | RoundedRectangleBorder( 23 | borderRadius: BorderRadius.all(Radius.circular(20)), 24 | ), 25 | ), 26 | ), 27 | child: Row( 28 | children: [ 29 | Icon( 30 | SelfIcon.Add, 31 | size: 16, 32 | color: Colors.white, 33 | ), 34 | SizedBox(width: iDefaultPadding / 4), 35 | Text( 36 | '好友', 37 | style: TextStyle(color: Colors.white), 38 | ), 39 | ], 40 | ), 41 | ); 42 | } 43 | 44 | Widget _buildInfo() { 45 | List _tagsWidget = []; 46 | List _otherWidget = []; 47 | List _tags = contact['tag']; 48 | List _other = contact['other']; 49 | const _style = TextStyle(fontSize: 14, color: Colors.black38, fontWeight: FontWeight.w500); 50 | for (int i = 0; i < _tags.length; i++) { 51 | String item = _tags[i]; 52 | _tagsWidget.add(Text( 53 | item, 54 | style: _style, 55 | )); 56 | _tagsWidget.add(SizedBox(width: 5)); 57 | } 58 | for (int i = 0; i < _other.length; i++) { 59 | String item = _other[i]; 60 | _otherWidget.add(Text( 61 | item, 62 | style: _style, 63 | )); 64 | } 65 | return Expanded( 66 | child: Padding( 67 | padding: EdgeInsets.only(left: iDefaultPadding / 2), 68 | child: Column( 69 | crossAxisAlignment: CrossAxisAlignment.start, 70 | children: [ 71 | Text( 72 | contact['name'], 73 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.black), 74 | ), 75 | Text( 76 | contact['post'], 77 | softWrap: true, 78 | style: TextStyle(fontSize: 14, color: Color(0xFFA9B2D6), fontWeight: FontWeight.w500), 79 | ), 80 | Row(children: _tagsWidget), 81 | Column(children: _otherWidget), 82 | ], 83 | ), 84 | ), 85 | ); 86 | } 87 | 88 | @override 89 | Widget build(BuildContext context) { 90 | return Container( 91 | padding: EdgeInsets.all(iDefaultPadding), 92 | color: Colors.white, 93 | child: Row( 94 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 95 | crossAxisAlignment: CrossAxisAlignment.start, 96 | children: [ 97 | CircleAvatar( 98 | radius: 30, 99 | backgroundImage: NetworkImage(contact['avatar']), 100 | ), 101 | _buildInfo(), 102 | _buildActionBtn(), 103 | ], 104 | ), 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/navbar_item.dart'; 3 | import 'package:flutter_maimai/components/self_icon.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | import 'package:flutter_maimai/enums.dart'; 6 | import 'package:flutter_maimai/pages/chance/chance.dart'; 7 | import 'package:flutter_maimai/pages/contact/contact.dart'; 8 | import 'package:flutter_maimai/pages/home/home.dart'; 9 | import 'package:flutter_maimai/pages/message/message.dart'; 10 | import 'package:flutter_maimai/pages/owner/owner.dart'; 11 | import 'package:flutter_maimai/router.dart'; 12 | 13 | void main() { 14 | runApp(MyApp()); 15 | } 16 | 17 | class MyApp extends StatelessWidget { 18 | @override 19 | Widget build(BuildContext context) { 20 | return MaterialApp( 21 | title: 'Flutter Maimai', 22 | debugShowCheckedModeBanner: false, 23 | home: MyHomePage(), 24 | routes: iPageRouter, 25 | // onGenerateRoute: pageRouterController, 26 | ); 27 | } 28 | } 29 | 30 | class MyHomePage extends StatefulWidget { 31 | MyHomePage({Key? key}) : super(key: key); 32 | 33 | @override 34 | _MyHomePageState createState() => _MyHomePageState(); 35 | } 36 | 37 | class _MyHomePageState extends State { 38 | int navIdx = 0; 39 | List pageList = [ 40 | Home(), 41 | Contact(), 42 | Chance(), 43 | Message(), 44 | Owner(), 45 | ]; 46 | 47 | void changeNavbar(int idx) { 48 | setState(() { 49 | navIdx = idx; 50 | }); 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | Size size = MediaQuery.of(context).size; 56 | return Scaffold( 57 | body: pageList[navIdx], 58 | backgroundColor: iAppBackgroundColor, 59 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 60 | floatingActionButton: Container( 61 | width: size.width * 0.9, 62 | height: iNavBarHeight, 63 | decoration: BoxDecoration( 64 | color: iAppBottomBarColor, 65 | borderRadius: BorderRadius.circular(20), 66 | ), 67 | child: Row( 68 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 69 | children: [ 70 | NavbarItem( 71 | isActive: navIdx == 0, 72 | name: '首页', 73 | icon: SelfIcon.HomeFilling, 74 | press: () => changeNavbar(0), 75 | ), 76 | NavbarItem( 77 | isActive: navIdx == 1, 78 | name: '人脉', 79 | icon: SelfIcon.ContactFilling, 80 | num: 10, 81 | type: NavbarMessageType.dot, 82 | press: () => changeNavbar(1), 83 | ), 84 | NavbarItem( 85 | isActive: navIdx == 2, 86 | name: '机遇', 87 | num: 10, 88 | icon: SelfIcon.WorkFilling, 89 | press: () => changeNavbar(2), 90 | ), 91 | NavbarItem( 92 | isActive: navIdx == 3, 93 | name: '消息', 94 | num: 99, 95 | icon: SelfIcon.NotificationFilling, 96 | press: () => changeNavbar(3), 97 | ), 98 | NavbarItem( 99 | isActive: navIdx == 4, 100 | name: '我', 101 | icon: SelfIcon.UserFilling, 102 | press: () => changeNavbar(4), 103 | ), 104 | ], 105 | ), 106 | ), // This trailing comma makes auto-formatting nicer for build methods. 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/pages/chance/components/chance_notice.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | 5 | class ChanceNotice extends StatelessWidget { 6 | const ChanceNotice({ 7 | Key? key, 8 | required this.count, 9 | required this.press, 10 | }) : super(key: key); 11 | 12 | final int count; 13 | final VoidCallback press; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return GestureDetector( 18 | onTap: press, 19 | child: Container( 20 | margin: EdgeInsets.only( 21 | left: iDefaultPadding, 22 | right: iDefaultPadding, 23 | bottom: iDefaultPadding, 24 | ), 25 | padding: EdgeInsets.all(iDefaultPadding), 26 | decoration: BoxDecoration( 27 | color: Colors.white, 28 | borderRadius: iBorderRadius, 29 | ), 30 | child: Row( 31 | children: [ 32 | Container( 33 | width: 50, 34 | height: 50, 35 | decoration: BoxDecoration( 36 | color: iPrimaryColor.withOpacity(0.7), 37 | borderRadius: BorderRadius.all( 38 | Radius.circular(50), 39 | ), 40 | ), 41 | child: Icon( 42 | SelfIcon.NotificationFilling, 43 | size: 40, 44 | color: Colors.white, 45 | ), 46 | ), 47 | Expanded( 48 | flex: 1, 49 | child: Container( 50 | height: 50, 51 | child: Row( 52 | children: [ 53 | Expanded( 54 | child: Padding( 55 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding / 2), 56 | child: Column( 57 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 58 | crossAxisAlignment: CrossAxisAlignment.start, 59 | children: [ 60 | Text( 61 | '机会来了', 62 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), 63 | ), 64 | Text( 65 | '又有$count家公司主动找你,想要聊聊,想要聊,想要聊聊', 66 | overflow: TextOverflow.ellipsis, 67 | maxLines: 1, 68 | style: TextStyle(fontSize: 14, color: Colors.black54), 69 | ), 70 | ], 71 | ), 72 | ), 73 | ), 74 | Container( 75 | height: 20, 76 | width: 20, 77 | child: Center( 78 | child: Text( 79 | '$count', 80 | textAlign: TextAlign.center, 81 | style: TextStyle(color: Colors.white, fontSize: 10), 82 | ), 83 | ), 84 | decoration: BoxDecoration( 85 | color: Colors.red, 86 | borderRadius: BorderRadius.all(Radius.circular(10)), 87 | ), 88 | ), 89 | ], 90 | ), 91 | ), 92 | ), 93 | Icon( 94 | SelfIcon.ArrowRight, 95 | size: 25, 96 | color: Colors.black12, 97 | ), 98 | ], 99 | ), 100 | ), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/pages/contact/components/discover_more.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | import 'package:flutter_maimai/mock/contact.dart'; 5 | 6 | class DiscoverMore extends StatelessWidget { 7 | const DiscoverMore({Key? key}) : super(key: key); 8 | 9 | Widget _buildActionBtn() { 10 | return Row( 11 | mainAxisAlignment: MainAxisAlignment.end, 12 | children: [ 13 | TextButton( 14 | onPressed: () {}, 15 | child: Text( 16 | '再说吧', 17 | style: TextStyle(color: Colors.black45), 18 | ), 19 | style: ButtonStyle( 20 | backgroundColor: MaterialStateProperty.all(Colors.white), 21 | ), 22 | ), 23 | SizedBox(width: iDefaultPadding / 2), 24 | TextButton( 25 | onPressed: () {}, 26 | child: Text( 27 | '去看看', 28 | style: TextStyle(color: Colors.white), 29 | ), 30 | style: ButtonStyle( 31 | backgroundColor: MaterialStateProperty.all(iPrimaryColor.withOpacity(0.9)), 32 | ), 33 | ), 34 | ], 35 | ); 36 | } 37 | 38 | Widget _buildContentCard(Size size) { 39 | return Container( 40 | width: size.width - iDefaultPadding * 2, 41 | margin: EdgeInsets.only(top: iDefaultPadding / 2), 42 | padding: EdgeInsets.all(iDefaultPadding / 2), 43 | decoration: BoxDecoration( 44 | color: Color(0xFFF8F7FA), 45 | borderRadius: BorderRadius.all(Radius.circular(4)), 46 | ), 47 | child: Row( 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | CircleAvatar( 51 | radius: 20, 52 | backgroundImage: NetworkImage(QinghuaLogo), 53 | ), 54 | SizedBox(width: 10), 55 | Container( 56 | width: size.width - iDefaultPadding * 3 - 50, 57 | child: Column( 58 | children: [ 59 | Text( 60 | '我们为你推荐了一批你可能认识的同学,看看他们的职场近况吧', 61 | textAlign: TextAlign.left, 62 | softWrap: true, 63 | style: TextStyle( 64 | fontSize: 18, 65 | fontWeight: FontWeight.w500, 66 | ), 67 | ), 68 | Padding( 69 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding / 5), 70 | child: Row( 71 | children: [ 72 | Icon( 73 | SelfIcon.UserFilling, 74 | color: Colors.black26, 75 | size: 18, 76 | ), 77 | SizedBox(width: 5), 78 | Text( 79 | '张三、李四、王五、赵六', 80 | style: TextStyle(fontSize: 14, color: Colors.black38), 81 | ), 82 | ], 83 | ), 84 | ), 85 | _buildActionBtn(), 86 | ], 87 | ), 88 | ), 89 | ], 90 | ), 91 | ); 92 | } 93 | 94 | @override 95 | Widget build(BuildContext context) { 96 | Size size = MediaQuery.of(context).size; 97 | return Container( 98 | width: size.width, 99 | color: Colors.white, 100 | padding: EdgeInsets.all(iDefaultPadding), 101 | child: Column( 102 | crossAxisAlignment: CrossAxisAlignment.start, 103 | children: [ 104 | Text( 105 | '发现更多', 106 | style: TextStyle(fontSize: 16, color: Colors.black), 107 | ), 108 | _buildContentCard(size), 109 | ], 110 | ), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/pages/contact/contact.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/common/common.dart'; 3 | import 'package:flutter_maimai/components/app_bar_action.dart'; 4 | import 'package:flutter_maimai/components/customer_tab_item.dart'; 5 | import 'package:flutter_maimai/components/search_field.dart'; 6 | import 'package:flutter_maimai/components/self_icon.dart'; 7 | import 'package:flutter_maimai/constants.dart'; 8 | import 'package:flutter_maimai/mock/contact.dart'; 9 | import 'package:flutter_maimai/pages/contact/components/contact_item.dart'; 10 | import 'package:flutter_maimai/pages/contact/components/contact_menu.dart'; 11 | import 'package:flutter_maimai/pages/contact/components/discover_more.dart'; 12 | import 'package:flutter_maimai/pages/contact/components/friend_msg.dart'; 13 | import 'package:flutter_maimai/pages/contact/components/request_list.dart'; 14 | 15 | class Contact extends StatefulWidget { 16 | const Contact({Key? key}) : super(key: key); 17 | 18 | @override 19 | _ContactState createState() => _ContactState(); 20 | } 21 | 22 | class _ContactState extends State { 23 | List contactList = ContactMockList; 24 | 25 | AppBar _buildAppBar() { 26 | return AppBar( 27 | centerTitle: true, 28 | title: Text('人脉', style: iAppBarTextStyle), 29 | actions: [ 30 | AppBarAction(icon: SelfIcon.User, press: () {}, right: iDefaultPadding * 0.8), 31 | ], 32 | shadowColor: Colors.transparent, 33 | backgroundColor: iAppBarBackgroundColor, 34 | ); 35 | } 36 | 37 | Widget _buildTop() { 38 | return SliverToBoxAdapter( 39 | child: Column( 40 | children: [ 41 | SearchField( 42 | placeholder: '找同学:XXX大学 XX专业', 43 | ), 44 | ContactMenu(), 45 | FriendMsg(), 46 | RequestList(), 47 | DiscoverMore(), 48 | ], 49 | ), 50 | ); 51 | } 52 | 53 | Widget _buildTabContent() { 54 | return Row( 55 | children: [ 56 | CustomerTabItem(title: '推荐', active: true, press: () {}), 57 | SizedBox(width: iDefaultPadding), 58 | CustomerTabItem(title: '同事', active: false, press: () {}), 59 | SizedBox(width: iDefaultPadding), 60 | CustomerTabItem(title: '校友', active: false, press: () {}), 61 | SizedBox(width: iDefaultPadding), 62 | CustomerTabItem(title: '同乡', active: false, press: () {}), 63 | SizedBox(width: iDefaultPadding), 64 | CustomerTabItem(title: '同行', active: false, press: () {}), 65 | ], 66 | ); 67 | } 68 | 69 | Widget _buildStickBar() { 70 | return SliverPersistentHeader( 71 | pinned: true, //是否固定在顶部 72 | floating: true, 73 | delegate: CustomSliverDelegate( 74 | minHeight: 50, //收起的高度 75 | maxHeight: 50, //展开的最大高度 76 | child: Container( 77 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding), 78 | color: Colors.white, 79 | alignment: Alignment.centerLeft, 80 | child: _buildTabContent(), 81 | ), 82 | ), 83 | ); 84 | } 85 | 86 | Widget _buildList() { 87 | return SliverList( 88 | delegate: SliverChildBuilderDelegate( 89 | (context, index) { 90 | return ContactItem( 91 | contact: contactList[index], 92 | press: () {}, 93 | ); 94 | }, 95 | childCount: contactList.length, 96 | ), 97 | ); 98 | } 99 | 100 | @override 101 | Widget build(BuildContext context) { 102 | Size size = MediaQuery.of(context).size; 103 | return Scaffold( 104 | backgroundColor: iAppBackgroundColor, 105 | appBar: _buildAppBar(), 106 | body: CustomScrollView( 107 | slivers: [ 108 | _buildTop(), 109 | _buildStickBar(), 110 | _buildList(), 111 | SliverPadding( 112 | padding: EdgeInsets.only(bottom: 200), 113 | ), 114 | ], 115 | ), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/pages/chance/chance.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/common/common.dart'; 3 | import 'package:flutter_maimai/components/app_ad.dart'; 4 | import 'package:flutter_maimai/components/app_bar_action.dart'; 5 | import 'package:flutter_maimai/components/customer_tab_item.dart'; 6 | import 'package:flutter_maimai/components/search_field.dart'; 7 | import 'package:flutter_maimai/components/self_icon.dart'; 8 | import 'package:flutter_maimai/constants.dart'; 9 | import 'package:flutter_maimai/mock/ad.dart'; 10 | import 'package:flutter_maimai/mock/company.dart'; 11 | import 'package:flutter_maimai/pages/chance/components/chance_notice.dart'; 12 | import 'package:flutter_maimai/pages/chance/components/follow_market.dart'; 13 | import 'package:flutter_maimai/pages/chance/components/post_item.dart'; 14 | 15 | class Chance extends StatefulWidget { 16 | const Chance({Key? key}) : super(key: key); 17 | 18 | @override 19 | _ChanceState createState() => _ChanceState(); 20 | } 21 | 22 | class _ChanceState extends State { 23 | List postList = CompanyPostMockList; 24 | 25 | AppBar _buildAppBar() { 26 | return AppBar( 27 | centerTitle: true, 28 | title: Text('看机会', style: iAppBarTextStyle), 29 | actions: [ 30 | AppBarAction(icon: SelfIcon.Add, press: () {}, right: iDefaultPadding * 0.8), 31 | ], 32 | shadowColor: Colors.transparent, 33 | backgroundColor: iAppBarBackgroundColor, 34 | ); 35 | } 36 | 37 | Widget _buildTop() { 38 | return SliverToBoxAdapter( 39 | child: Column( 40 | children: [ 41 | SearchField( 42 | haveButton: true, 43 | placeholder: '搜索感兴趣的职位', 44 | btnText: '搜索', 45 | ), 46 | ImageAd(ChanceImageAdUrl), 47 | FollowMarket(), 48 | ChanceNotice( 49 | count: 1, 50 | press: () {}, 51 | ), 52 | ], 53 | ), 54 | ); 55 | } 56 | 57 | Widget _buildTabContent() { 58 | return Row( 59 | children: [ 60 | CustomerTabItem(title: '推荐', active: true, press: () {}, haveBorder: true), 61 | SizedBox(width: iDefaultPadding), 62 | CustomerTabItem(title: '最新', active: false, press: () {}, haveBadge: true, haveBorder: true), 63 | Spacer(), 64 | Row( 65 | children: [ 66 | Icon( 67 | SelfIcon.EditFilling, 68 | size: 16, 69 | ), 70 | Text( 71 | '职位偏好', 72 | style: TextStyle( 73 | fontSize: 14, 74 | fontWeight: FontWeight.w500, 75 | ), 76 | ) 77 | ], 78 | ), 79 | ], 80 | ); 81 | } 82 | 83 | Widget _buildStickBar() { 84 | return SliverPersistentHeader( 85 | pinned: true, //是否固定在顶部 86 | floating: true, 87 | delegate: CustomSliverDelegate( 88 | minHeight: 50, //收起的高度 89 | maxHeight: 50, //展开的最大高度 90 | child: Container( 91 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding), 92 | color: Colors.white, 93 | alignment: Alignment.centerLeft, 94 | child: _buildTabContent(), 95 | ), 96 | ), 97 | ); 98 | } 99 | 100 | Widget _buildList() { 101 | return SliverList( 102 | delegate: SliverChildBuilderDelegate( 103 | (context, index) { 104 | return PostItem( 105 | itemInfo: postList[index], 106 | press: () {}, 107 | ); 108 | }, 109 | childCount: postList.length, 110 | ), 111 | ); 112 | } 113 | 114 | @override 115 | Widget build(BuildContext context) { 116 | return Scaffold( 117 | backgroundColor: iAppBackgroundColor, 118 | appBar: _buildAppBar(), 119 | body: CustomScrollView( 120 | slivers: [ 121 | _buildTop(), 122 | _buildStickBar(), 123 | _buildList(), 124 | SliverPadding( 125 | padding: EdgeInsets.only(bottom: 200), 126 | ), 127 | ], 128 | ), 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/pages/chance/components/post_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/components/tag.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | 6 | class PostItem extends StatelessWidget { 7 | const PostItem({ 8 | Key? key, 9 | required this.itemInfo, 10 | required this.press, 11 | }) : super(key: key); 12 | 13 | final itemInfo; 14 | final VoidCallback press; 15 | 16 | String _dealSalaryText() { 17 | var obj = itemInfo['salary']; 18 | if (obj == null) return ''; 19 | int minNum = obj['min']; 20 | int maxNum = obj['max']; 21 | int monthNum = itemInfo['month']; 22 | return '$minNum' + 'k-' + '$maxNum' + 'k·$monthNum薪'; 23 | } 24 | 25 | Widget _buildLogo() { 26 | return Container( 27 | width: 60, 28 | height: 60, 29 | decoration: BoxDecoration( 30 | borderRadius: iBorderRadius, 31 | border: new Border.all( 32 | color: Colors.black12, 33 | ), 34 | image: new DecorationImage( 35 | image: new NetworkImage(itemInfo['companyLogo']), 36 | ), 37 | ), 38 | ); 39 | } 40 | 41 | Widget _buildHeader() { 42 | return Row( 43 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 44 | children: [ 45 | Text( 46 | itemInfo['postName'], 47 | style: TextStyle( 48 | color: Colors.black, 49 | fontSize: 18, 50 | fontWeight: FontWeight.bold, 51 | ), 52 | ), 53 | // Spacer(flex: 1,), 54 | Text( 55 | _dealSalaryText(), 56 | style: TextStyle( 57 | color: iPrimaryColor, 58 | fontSize: 18, 59 | fontWeight: FontWeight.bold, 60 | ), 61 | ), 62 | ], 63 | ); 64 | } 65 | 66 | Widget _buildTagList() { 67 | return Container( 68 | margin: EdgeInsets.symmetric(vertical: 10), 69 | child: Row( 70 | children: [ 71 | Tag(itemInfo['location']), 72 | SizedBox(width: 10), 73 | Tag(itemInfo['education']), 74 | SizedBox(width: 10), 75 | Tag(itemInfo['workYear']), 76 | ], 77 | ), 78 | ); 79 | } 80 | 81 | Widget _buildExtInfo() { 82 | TextStyle _style = TextStyle( 83 | color: Colors.black45, 84 | fontSize: 14, 85 | fontWeight: FontWeight.w500, 86 | ); 87 | return Row( 88 | children: [ 89 | Text(itemInfo['companyName'], style: _style), 90 | SizedBox(width: 5), 91 | Text(itemInfo['status'], style: _style), 92 | SizedBox(width: 5), 93 | Text(itemInfo['people'], style: _style), 94 | Spacer(), 95 | InkWell( 96 | onTap: press, 97 | child: Icon( 98 | SelfIcon.Close, 99 | size: 15, 100 | color: Colors.black26, 101 | ), 102 | ), 103 | ], 104 | ); 105 | } 106 | 107 | Widget _buildContent() { 108 | return Expanded( 109 | child: Container( 110 | margin: EdgeInsets.only(left: iDefaultPadding / 2), 111 | child: Column( 112 | children: [ 113 | _buildHeader(), 114 | _buildTagList(), 115 | _buildExtInfo(), 116 | ], 117 | ), 118 | ), 119 | ); 120 | } 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | // print("数据为:$itemInfo.pay['pay']"); 125 | return Container( 126 | decoration: BoxDecoration( 127 | color: Colors.white, 128 | ), 129 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding), 130 | child: Container( 131 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding), 132 | decoration: BoxDecoration( 133 | border: new Border( 134 | bottom: BorderSide( 135 | color: Colors.black.withOpacity(0.05), 136 | ), 137 | ), 138 | ), 139 | child: Row( 140 | crossAxisAlignment: CrossAxisAlignment.start, 141 | children: [ 142 | _buildLogo(), 143 | _buildContent(), 144 | ], 145 | ), 146 | ), 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/pages/owner/owner.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/app_bar_action.dart'; 3 | import 'package:flutter_maimai/components/card_container.dart'; 4 | import 'package:flutter_maimai/components/column_menu_item.dart'; 5 | import 'package:flutter_maimai/components/safe_area.dart'; 6 | import 'package:flutter_maimai/components/self_icon.dart'; 7 | import 'package:flutter_maimai/constants.dart'; 8 | import 'package:flutter_maimai/pages/owner/components/owner_info.dart'; 9 | import 'package:flutter_maimai/pages/owner/components/owner_menu.dart'; 10 | 11 | class Owner extends StatelessWidget { 12 | const Owner({Key? key}) : super(key: key); 13 | 14 | AppBar _buildAppBar() { 15 | return AppBar( 16 | actions: [ 17 | AppBarAction( 18 | icon: SelfIcon.Scanning, 19 | press: () {}, 20 | right: iDefaultPadding * 0.8, 21 | ), 22 | ], 23 | shadowColor: Colors.transparent, 24 | backgroundColor: iAppBarBackgroundColor, 25 | ); 26 | } 27 | 28 | Widget _buildMenuList() { 29 | TextStyle _style = TextStyle(fontSize: 14, color: Colors.black38); 30 | TextStyle _styleYellow = TextStyle(fontSize: 14, color: Colors.yellow[700]); 31 | return CardContainer( 32 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding), 33 | child: Column( 34 | children: [ 35 | ColumnMenuItem( 36 | value: '我的社区身份', 37 | press: () {}, 38 | ), 39 | ColumnMenuItem( 40 | value: '我的收藏', 41 | press: () {}, 42 | ), 43 | ColumnMenuItem( 44 | value: '关注内容', 45 | press: () {}, 46 | ), 47 | ColumnMenuItem( 48 | value: '历史通知', 49 | press: () {}, 50 | ), 51 | ColumnMenuItem( 52 | value: '成为认证用户', 53 | press: () {}, 54 | ext: Text('完善资料', style: _style), 55 | ), 56 | ColumnMenuItem( 57 | value: '成为会员', 58 | press: () {}, 59 | ext: Text('限时免费使用', style: _styleYellow), 60 | ), 61 | ColumnMenuItem( 62 | value: '附件简历', 63 | press: () {}, 64 | ext: Text('去上传', style: _style), 65 | ), 66 | ColumnMenuItem( 67 | value: '加好友额度中心', 68 | press: () {}, 69 | ext: Text('本月待领取0', style: _style), 70 | ), 71 | ColumnMenuItem( 72 | value: '钱包', 73 | press: () {}, 74 | ext: Text('查看额度', style: _style), 75 | ), 76 | ColumnMenuItem( 77 | value: '人气周报', 78 | press: () {}, 79 | ext: Text('相比上周下降了27%', style: _style), 80 | ), 81 | ColumnMenuItem( 82 | value: '职趣实验室', 83 | press: () {}, 84 | ), 85 | ColumnMenuItem( 86 | value: '职场福利', 87 | press: () {}, 88 | ), 89 | ], 90 | ), 91 | ); 92 | } 93 | 94 | Widget _buildSettingMenuList() { 95 | return CardContainer( 96 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding), 97 | child: Column( 98 | children: [ 99 | ColumnMenuItem( 100 | value: '意见反馈', 101 | press: () {}, 102 | ), 103 | ColumnMenuItem( 104 | value: '在线客服', 105 | press: () {}, 106 | ), 107 | ColumnMenuItem( 108 | value: '隐私', 109 | press: () {}, 110 | ), 111 | ColumnMenuItem( 112 | value: '第三方SDK列表', 113 | press: () {}, 114 | ), 115 | ColumnMenuItem( 116 | value: '设置', 117 | press: () {}, 118 | ), 119 | ], 120 | ), 121 | ); 122 | } 123 | 124 | @override 125 | Widget build(BuildContext context) { 126 | return Scaffold( 127 | backgroundColor: iAppBackgroundColor, 128 | appBar: _buildAppBar(), 129 | body: SingleChildScrollView( 130 | child: Column( 131 | children: [ 132 | OwnerInfo(), 133 | OwnerMenu(), 134 | _buildMenuList(), 135 | _buildSettingMenuList(), 136 | SafeAreaBottom(), 137 | ], 138 | ), 139 | ), 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/pages/home/components/hot_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/self_icon.dart'; 3 | import 'package:flutter_maimai/constants.dart'; 4 | import 'package:flutter_maimai/mock/home.dart'; 5 | 6 | class HotList extends StatefulWidget { 7 | const HotList({Key? key}) : super(key: key); 8 | 9 | @override 10 | _HotListState createState() => _HotListState(); 11 | } 12 | 13 | class _HotListState extends State { 14 | List hotList = HotListMock; 15 | 16 | Widget _buildTitle() { 17 | return Row( 18 | children: [ 19 | Text( 20 | '脉脉', 21 | style: TextStyle(fontSize: 25, color: iPrimaryColor, fontWeight: FontWeight.w900), 22 | ), 23 | Text( 24 | '热榜', 25 | style: TextStyle(fontSize: 25, color: Colors.black, fontWeight: FontWeight.w900), 26 | ), 27 | ], 28 | ); 29 | } 30 | 31 | Widget _buildListContent() { 32 | List _list = []; 33 | for (int i = 0; i < hotList.length; i++) { 34 | _list.add(HotListItem(item: hotList[i], idx: i + 1)); 35 | } 36 | return Column( 37 | children: _list, 38 | ); 39 | } 40 | 41 | Widget _buildMoreAction() { 42 | return Padding( 43 | padding: EdgeInsets.symmetric(vertical: iDefaultPadding / 2), 44 | child: Row( 45 | mainAxisAlignment: MainAxisAlignment.center, 46 | crossAxisAlignment: CrossAxisAlignment.center, 47 | children: [ 48 | Text('查看更多', style: TextStyle(color: iPrimaryColor, fontSize: 14)), 49 | Icon( 50 | SelfIcon.ArrowRight, 51 | size: 14, 52 | ), 53 | ], 54 | ), 55 | ); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return Container( 61 | margin: EdgeInsets.symmetric(vertical: iDefaultPadding / 2, horizontal: iDefaultPadding), 62 | padding: EdgeInsets.all(iDefaultPadding / 1.5), 63 | decoration: BoxDecoration( 64 | color: iAppBackgroundColor, 65 | borderRadius: iBorderRadius, 66 | ), 67 | child: Column( 68 | children: [ 69 | _buildTitle(), 70 | _buildListContent(), 71 | GestureDetector( 72 | onTap: () {}, 73 | child: _buildMoreAction(), 74 | ), 75 | ], 76 | ), 77 | ); 78 | } 79 | } 80 | 81 | class HotListItem extends StatelessWidget { 82 | const HotListItem({ 83 | Key? key, 84 | required this.item, 85 | required this.idx, 86 | }) : super(key: key); 87 | 88 | final item; 89 | final int idx; 90 | 91 | Widget _buildTag() { 92 | bool _isNew = item['isNew']; 93 | Color _color = _isNew ? iPrimaryColor : Colors.red; 94 | return Container( 95 | height: 20, 96 | width: 20, 97 | decoration: BoxDecoration( 98 | borderRadius: BorderRadius.all(Radius.circular(4)), 99 | border: new Border.all(width: 1, color: _color), 100 | ), 101 | child: Center( 102 | child: Text( 103 | _isNew ? '新' : '爆', 104 | style: TextStyle(color: _color, fontSize: 12), 105 | ), 106 | ), 107 | ); 108 | } 109 | 110 | Widget _buildIdx() { 111 | Color _color = Colors.black; 112 | switch (idx) { 113 | case 1: 114 | _color = Colors.red[500] as Color; 115 | break; 116 | case 2: 117 | _color = Colors.yellow[900] as Color; 118 | break; 119 | case 3: 120 | _color = Colors.yellow[700] as Color; 121 | break; 122 | } 123 | return Container( 124 | width: 16, 125 | height: 16, 126 | decoration: BoxDecoration( 127 | color: _color, 128 | borderRadius: BorderRadius.all(Radius.circular(3)), 129 | ), 130 | child: Center( 131 | child: Text( 132 | '$idx', 133 | style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500), 134 | ), 135 | ), 136 | ); 137 | } 138 | 139 | @override 140 | Widget build(BuildContext context) { 141 | return GestureDetector( 142 | onTap: () {}, 143 | child: Container( 144 | margin: EdgeInsets.only(top: iDefaultPadding / 4), 145 | child: Row( 146 | children: [ 147 | _buildIdx(), 148 | Expanded( 149 | child: Padding( 150 | padding: EdgeInsets.symmetric(horizontal: 5), 151 | child: Text( 152 | item['name'], 153 | overflow: TextOverflow.ellipsis, 154 | style: TextStyle(fontSize: 16, color: Colors.black), 155 | ), 156 | ), 157 | ), 158 | _buildTag(), 159 | ], 160 | ), 161 | ), 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /lib/main copy.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/navbar_item.dart'; 3 | import 'package:flutter_maimai/components/self_icon.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | import 'package:flutter_maimai/enums.dart'; 6 | import 'package:flutter_maimai/pages/chance/chance.dart'; 7 | import 'package:flutter_maimai/pages/contact/contact.dart'; 8 | import 'package:flutter_maimai/pages/home/home.dart'; 9 | import 'package:flutter_maimai/pages/message/message.dart'; 10 | import 'package:flutter_maimai/pages/owner/owner.dart'; 11 | 12 | void main() { 13 | runApp(MyApp()); 14 | } 15 | 16 | class MyApp extends StatelessWidget { 17 | @override 18 | Widget build(BuildContext context) { 19 | return MaterialApp( 20 | title: 'Flutter Maimai', 21 | debugShowCheckedModeBanner: false, 22 | home: MyHomePage(), 23 | ); 24 | } 25 | } 26 | 27 | class MyHomePage extends StatefulWidget { 28 | MyHomePage({Key? key}) : super(key: key); 29 | 30 | @override 31 | _MyHomePageState createState() => _MyHomePageState(); 32 | } 33 | 34 | class _MyHomePageState extends State { 35 | int navIdx = 0; 36 | List pageList = [ 37 | Home(), 38 | // Contact(), 39 | // StickyPage(), 40 | Chance(), 41 | Message(), 42 | Owner(), 43 | ]; 44 | 45 | void changeNavbar(int idx) { 46 | setState(() { 47 | navIdx = idx; 48 | }); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | Size size = MediaQuery.of(context).size; 54 | return Scaffold( 55 | body: pageList[navIdx], 56 | backgroundColor: iAppBackgroundColor, 57 | floatingActionButtonLocation: CustomFloatingActionButtonLocation(FloatingActionButtonLocation.centerDocked, 0, 50), 58 | floatingActionButton: Container( 59 | width: size.width, 60 | height: 100, 61 | padding: EdgeInsets.only( 62 | left: iDefaultPadding, 63 | right: iDefaultPadding, 64 | ), 65 | decoration: BoxDecoration( 66 | gradient: LinearGradient( 67 | begin: Alignment.topCenter, 68 | end: Alignment.bottomCenter, 69 | colors: [ 70 | Colors.white.withOpacity(0.2), 71 | Colors.white.withOpacity(0.8), 72 | ], 73 | ), 74 | color: Colors.white.withOpacity(0.8), 75 | ), 76 | child: Stack( 77 | children: [ 78 | Container( 79 | width: size.width * 0.9, 80 | height: iNavBarHeight, 81 | decoration: BoxDecoration( 82 | color: iAppBottomBarColor, 83 | borderRadius: BorderRadius.circular(20), 84 | ), 85 | child: Row( 86 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 87 | children: [ 88 | NavbarItem( 89 | isActive: navIdx == 0, 90 | name: '首页', 91 | icon: SelfIcon.HomeFilling, 92 | press: () => changeNavbar(0), 93 | ), 94 | NavbarItem( 95 | isActive: navIdx == 1, 96 | name: '人脉', 97 | icon: SelfIcon.ContactFilling, 98 | num: 10, 99 | type: NavbarMessageType.dot, 100 | press: () => changeNavbar(1), 101 | ), 102 | NavbarItem( 103 | isActive: navIdx == 2, 104 | name: '机遇', 105 | num: 10, 106 | icon: SelfIcon.WorkFilling, 107 | press: () => changeNavbar(2), 108 | ), 109 | NavbarItem( 110 | isActive: navIdx == 3, 111 | name: '消息', 112 | num: 99, 113 | icon: SelfIcon.NotificationFilling, 114 | press: () => changeNavbar(3), 115 | ), 116 | NavbarItem( 117 | isActive: navIdx == 4, 118 | name: '我', 119 | icon: SelfIcon.UserFilling, 120 | press: () => changeNavbar(4), 121 | ), 122 | ], 123 | ), 124 | ), 125 | ], 126 | ), 127 | ), 128 | ); 129 | } 130 | } 131 | 132 | class CustomFloatingActionButtonLocation extends FloatingActionButtonLocation { 133 | FloatingActionButtonLocation location; 134 | double offsetX; // X方向的偏移量 135 | double offsetY; // Y方向的偏移量 136 | CustomFloatingActionButtonLocation(this.location, this.offsetX, this.offsetY); 137 | 138 | @override 139 | Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) { 140 | Offset offset = location.getOffset(scaffoldGeometry); 141 | return Offset(offset.dx + offsetX, offset.dy + offsetY); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/pages/message/message.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/components/app_bar_action.dart'; 3 | import 'package:flutter_maimai/components/self_icon.dart'; 4 | import 'package:flutter_maimai/constants.dart'; 5 | import 'package:flutter_maimai/mock/message.dart'; 6 | import 'package:flutter_maimai/pages/message/components/message_item.dart'; 7 | import 'package:flutter_maimai/pages/message/components/message_type.dart'; 8 | 9 | class Message extends StatefulWidget { 10 | const Message({Key? key}) : super(key: key); 11 | 12 | @override 13 | _MessageState createState() => _MessageState(); 14 | } 15 | 16 | class _MessageState extends State { 17 | List messageList = MessageMockList; 18 | 19 | AppBar _buildAppBar() { 20 | return AppBar( 21 | centerTitle: true, 22 | title: Text('消息', style: iAppBarTextStyle), 23 | actions: [ 24 | AppBarAction( 25 | icon: SelfIcon.Add, 26 | right: iDefaultPadding * 0.8, 27 | press: () {}, 28 | ), 29 | ], 30 | leading: AppBarAction( 31 | icon: SelfIcon.Search, 32 | left: iDefaultPadding * 0.6, 33 | press: () {}, 34 | ), 35 | shadowColor: Colors.transparent, 36 | backgroundColor: iAppBarBackgroundColor, 37 | ); 38 | } 39 | 40 | Widget _buildModalHeader(String title) { 41 | return Container( 42 | height: iModalHeaderHeight, 43 | child: Row( 44 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 45 | children: [ 46 | GestureDetector( 47 | onTap: () { 48 | Navigator.pop(context); 49 | }, 50 | child: SizedBox( 51 | width: iModalHeaderHeight, 52 | height: iModalHeaderHeight, 53 | child: Center( 54 | child: Icon(SelfIcon.CloseBold, size: 20), 55 | ), 56 | ), 57 | ), 58 | Expanded( 59 | flex: 1, 60 | child: Center( 61 | child: Text( 62 | title, 63 | style: TextStyle(fontSize: 18, color: Colors.black, fontWeight: FontWeight.w500), 64 | ), 65 | ), 66 | ), 67 | SizedBox(width: iModalHeaderHeight, height: iModalHeaderHeight), 68 | ], 69 | ), 70 | ); 71 | } 72 | 73 | Widget _buildModelSheetContent(Size size, String title) { 74 | Radius _radius = Radius.circular(20); 75 | return Container( 76 | height: size.height * 0.88, 77 | decoration: BoxDecoration( 78 | color: Colors.white, 79 | borderRadius: BorderRadius.only(topLeft: _radius, topRight: _radius), 80 | ), 81 | child: Column( 82 | children: [ 83 | _buildModalHeader(title), 84 | Expanded( 85 | child: ListView.builder( 86 | itemBuilder: (context, index) { 87 | return MessageItem(message: messageList[index], press: () {}); 88 | }, 89 | itemCount: messageList.length, 90 | ), 91 | ), 92 | ], 93 | ), 94 | ); 95 | } 96 | 97 | void _showModalSheetAction(Size size, String title) { 98 | showModalBottomSheet( 99 | isDismissible: false, 100 | isScrollControlled: true, 101 | context: context, 102 | elevation: 10, 103 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), 104 | builder: (BuildContext context) { 105 | return _buildModelSheetContent(size, title); 106 | }, 107 | ); 108 | } 109 | 110 | Widget _buildMessageType(Size size) { 111 | return SliverToBoxAdapter( 112 | child: Container( 113 | color: iAppBackgroundColor, 114 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding, vertical: iDefaultPadding / 2), 115 | child: Row( 116 | children: [ 117 | MessageType( 118 | text: '我主动沟通', 119 | press: () { 120 | _showModalSheetAction(size, '我主动沟通'); 121 | }, 122 | ), 123 | MessageType( 124 | text: '只看未读', 125 | press: () { 126 | _showModalSheetAction(size, '只看未读'); 127 | }, 128 | ), 129 | MessageType( 130 | text: '好友消息', 131 | press: () { 132 | _showModalSheetAction(size, '好友消息'); 133 | }, 134 | ), 135 | ], 136 | ), 137 | ), 138 | ); 139 | } 140 | 141 | Widget _buildMessageList() { 142 | return SliverList( 143 | delegate: SliverChildBuilderDelegate( 144 | (context, index) { 145 | return MessageItem(message: messageList[index], press: () {}); 146 | }, 147 | childCount: messageList.length, 148 | ), 149 | ); 150 | } 151 | 152 | @override 153 | Widget build(BuildContext context) { 154 | Size size = MediaQuery.of(context).size; 155 | return Scaffold( 156 | backgroundColor: iAppBackgroundColor, 157 | appBar: _buildAppBar(), 158 | body: CustomScrollView( 159 | slivers: [ 160 | _buildMessageType(size), 161 | _buildMessageList(), 162 | ], 163 | ), 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/mock/company.dart: -------------------------------------------------------------------------------- 1 | List CompanyMockList = [{}]; 2 | 3 | List CompanyPostMockList = [ 4 | { 5 | "postId": 1, 6 | "companyName": "公司A名称", 7 | "companyLogo": 8 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F27%2F20210727200514_f11e2.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678447184&t=88f3f49fe9cdaa106da0ea9bd98b8f95", 9 | "postName": "web前端工程师", 10 | "salary": { 11 | "min": 12, 12 | "max": 24, 13 | }, 14 | "month": 13, 15 | "status": "不需要融资", 16 | "people": "1000-5000人", 17 | "location": "杭州", 18 | "education": "本科及以上", 19 | "workYear": "5-10年" 20 | }, 21 | { 22 | "postId": 2, 23 | "companyName": "公司B名称", 24 | "companyLogo": "https://img1.baidu.com/it/u=642615975,3013253527&fm=253&fmt=auto&app=138&f=JPEG?w=501&h=500", 25 | "postName": "高级前端工程师", 26 | "salary": { 27 | "min": 20, 28 | "max": 40, 29 | }, 30 | "month": 14, 31 | "status": "A轮", 32 | "people": "10-50人", 33 | "location": "杭州", 34 | "education": "本科及以上", 35 | "workYear": "3-5年" 36 | }, 37 | { 38 | "postId": 1, 39 | "companyName": "公司A名称", 40 | "companyLogo": 41 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F27%2F20210727200514_f11e2.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678447184&t=88f3f49fe9cdaa106da0ea9bd98b8f95", 42 | "postName": "Web前端开发工程师", 43 | "salary": { 44 | "min": 12, 45 | "max": 24, 46 | }, 47 | "month": 13, 48 | "status": "不需要融资", 49 | "people": "1000-5000人", 50 | "location": "杭州", 51 | "education": "本科及以上", 52 | "workYear": "5-10年" 53 | }, 54 | { 55 | "postId": 2, 56 | "companyName": "公司B名称", 57 | "companyLogo": "https://img1.baidu.com/it/u=642615975,3013253527&fm=253&fmt=auto&app=138&f=JPEG?w=501&h=500", 58 | "postName": "高级前端工程师", 59 | "salary": { 60 | "min": 20, 61 | "max": 40, 62 | }, 63 | "month": 14, 64 | "status": "A轮", 65 | "people": "10-50人", 66 | "location": "杭州", 67 | "education": "本科及以上", 68 | "workYear": "3-5年" 69 | }, 70 | { 71 | "postId": 1, 72 | "companyName": "公司A名称", 73 | "companyLogo": 74 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F27%2F20210727200514_f11e2.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678447184&t=88f3f49fe9cdaa106da0ea9bd98b8f95", 75 | "postName": "Web前端开发工程师", 76 | "salary": { 77 | "min": 12, 78 | "max": 24, 79 | }, 80 | "month": 13, 81 | "status": "不需要融资", 82 | "people": "1000-5000人", 83 | "location": "杭州", 84 | "education": "本科及以上", 85 | "workYear": "5-10年" 86 | }, 87 | { 88 | "postId": 2, 89 | "companyName": "公司B名称", 90 | "companyLogo": "https://img1.baidu.com/it/u=642615975,3013253527&fm=253&fmt=auto&app=138&f=JPEG?w=501&h=500", 91 | "postName": "高级前端工程师", 92 | "salary": { 93 | "min": 20, 94 | "max": 40, 95 | }, 96 | "month": 14, 97 | "status": "A轮", 98 | "people": "10-50人", 99 | "location": "杭州", 100 | "education": "本科及以上", 101 | "workYear": "3-5年" 102 | }, 103 | { 104 | "postId": 1, 105 | "companyName": "公司A名称", 106 | "companyLogo": 107 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F27%2F20210727200514_f11e2.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678447184&t=88f3f49fe9cdaa106da0ea9bd98b8f95", 108 | "postName": "Web前端开发工程师", 109 | "salary": { 110 | "min": 12, 111 | "max": 24, 112 | }, 113 | "month": 13, 114 | "status": "不需要融资", 115 | "people": "1000-5000人", 116 | "location": "杭州", 117 | "education": "本科及以上", 118 | "workYear": "5-10年" 119 | }, 120 | { 121 | "postId": 2, 122 | "companyName": "公司B名称", 123 | "companyLogo": "https://img1.baidu.com/it/u=642615975,3013253527&fm=253&fmt=auto&app=138&f=JPEG?w=501&h=500", 124 | "postName": "高级前端工程师", 125 | "salary": { 126 | "min": 20, 127 | "max": 40, 128 | }, 129 | "month": 14, 130 | "status": "A轮", 131 | "people": "10-50人", 132 | "location": "杭州", 133 | "education": "本科及以上", 134 | "workYear": "3-5年" 135 | }, 136 | { 137 | "postId": 1, 138 | "companyName": "公司A名称", 139 | "companyLogo": 140 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F27%2F20210727200514_f11e2.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678447184&t=88f3f49fe9cdaa106da0ea9bd98b8f95", 141 | "postName": "Web前端开发工程师", 142 | "salary": { 143 | "min": 12, 144 | "max": 24, 145 | }, 146 | "month": 13, 147 | "status": "不需要融资", 148 | "people": "1000-5000人", 149 | "location": "杭州", 150 | "education": "本科及以上", 151 | "workYear": "5-10年" 152 | }, 153 | { 154 | "postId": 2, 155 | "companyName": "公司B名称", 156 | "companyLogo": "https://img1.baidu.com/it/u=642615975,3013253527&fm=253&fmt=auto&app=138&f=JPEG?w=501&h=500", 157 | "postName": "高级前端工程师", 158 | "salary": { 159 | "min": 20, 160 | "max": 40, 161 | }, 162 | "month": 14, 163 | "status": "A轮", 164 | "people": "10-50人", 165 | "location": "杭州", 166 | "education": "本科及以上", 167 | "workYear": "3-5年" 168 | }, 169 | { 170 | "postId": 1, 171 | "companyName": "公司A名称", 172 | "companyLogo": 173 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F27%2F20210727200514_f11e2.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678447184&t=88f3f49fe9cdaa106da0ea9bd98b8f95", 174 | "postName": "Web前端开发工程师", 175 | "salary": { 176 | "min": 12, 177 | "max": 24, 178 | }, 179 | "month": 13, 180 | "status": "不需要融资", 181 | "people": "1000-5000人", 182 | "location": "杭州", 183 | "education": "本科及以上", 184 | "workYear": "5-10年" 185 | }, 186 | { 187 | "postId": 2, 188 | "companyName": "公司B名称", 189 | "companyLogo": "https://img1.baidu.com/it/u=642615975,3013253527&fm=253&fmt=auto&app=138&f=JPEG?w=501&h=500", 190 | "postName": "高级前端工程师", 191 | "salary": { 192 | "min": 20, 193 | "max": 40, 194 | }, 195 | "month": 14, 196 | "status": "A轮", 197 | "people": "10-50人", 198 | "location": "杭州", 199 | "education": "本科及以上", 200 | "workYear": "3-5年" 201 | }, 202 | ]; 203 | -------------------------------------------------------------------------------- /lib/pages/home/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_maimai/common/customer_tab_size_indicator.dart'; 3 | import 'package:flutter_maimai/components/app_bar_action.dart'; 4 | import 'package:flutter_maimai/components/customer_tab_item.dart'; 5 | import 'package:flutter_maimai/components/search_field.dart'; 6 | import 'package:flutter_maimai/components/self_icon.dart'; 7 | import 'package:flutter_maimai/constants.dart'; 8 | import 'package:flutter_maimai/pages/home/ask.dart'; 9 | import 'package:flutter_maimai/pages/home/follows.dart'; 10 | import 'package:flutter_maimai/pages/home/hot.dart'; 11 | import 'package:flutter_maimai/pages/home/job.dart'; 12 | import 'package:flutter_maimai/pages/home/recommend.dart'; 13 | import 'package:flutter_maimai/types.dart'; 14 | 15 | class Home extends StatefulWidget { 16 | const Home({Key? key}) : super(key: key); 17 | 18 | @override 19 | _HomeState createState() => _HomeState(); 20 | } 21 | 22 | class _HomeState extends State with SingleTickerProviderStateMixin { 23 | late TabController _tabController; 24 | int idx = 0; 25 | final List _tabList = ['关注', '推荐', '热门', '职言', '问员工']; 26 | @override 27 | void initState() { 28 | super.initState(); 29 | _tabController = new TabController(vsync: this, length: 5, initialIndex: 1); 30 | } 31 | 32 | void _changeActiveIdx(int index) { 33 | print('修改idx:$index'); 34 | setState(() { 35 | idx = index; 36 | }); 37 | } 38 | 39 | AppBar _buildAppBarHistory() { 40 | return AppBar( 41 | title: SearchField( 42 | haveGap: false, 43 | placeholder: '找同学:清华大学计算机专业', 44 | ), 45 | centerTitle: false, 46 | actions: [ 47 | AppBarAction( 48 | icon: SelfIcon.AddBold, 49 | press: () {}, 50 | right: iDefaultPadding / 2, 51 | ), 52 | AppBarAction( 53 | icon: SelfIcon.AddBold, 54 | press: () {}, 55 | right: iDefaultPadding * 0.8, 56 | ), 57 | ], 58 | shadowColor: Colors.transparent, 59 | backgroundColor: iAppBarBackgroundColor, 60 | flexibleSpace: Container( 61 | // height: 30, 62 | // color: Colors.red, 63 | ), 64 | bottom: PreferredSize( 65 | preferredSize: const Size.fromHeight(48.0), 66 | child: Container( 67 | height: 40, 68 | // color: Colors.amber, 69 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding), 70 | child: Row( 71 | children: [ 72 | CustomerTabItem( 73 | title: '关注', 74 | active: idx == 0, 75 | press: () { 76 | _changeActiveIdx(0); 77 | }, 78 | isScale: true, 79 | haveBorder: true, 80 | ), 81 | SizedBox(width: iDefaultPadding), 82 | CustomerTabItem( 83 | title: '推荐', 84 | active: idx == 1, 85 | press: () { 86 | _changeActiveIdx(1); 87 | }, 88 | isScale: true, 89 | haveBorder: true, 90 | ), 91 | SizedBox(width: iDefaultPadding), 92 | CustomerTabItem( 93 | title: '热门', 94 | active: idx == 2, 95 | press: () { 96 | _changeActiveIdx(2); 97 | }, 98 | isScale: true, 99 | haveBorder: true, 100 | ), 101 | SizedBox(width: iDefaultPadding), 102 | CustomerTabItem( 103 | title: '职言', 104 | active: idx == 3, 105 | press: () { 106 | _changeActiveIdx(3); 107 | }, 108 | isScale: true, 109 | haveBorder: true, 110 | ), 111 | SizedBox(width: iDefaultPadding), 112 | CustomerTabItem( 113 | title: '问员工', 114 | active: idx == 4, 115 | press: () { 116 | _changeActiveIdx(4); 117 | }, 118 | isScale: true, 119 | haveBorder: true, 120 | ), 121 | ], 122 | ), 123 | ), 124 | ), 125 | ); 126 | } 127 | 128 | AppBar _buildAppBar() { 129 | return AppBar( 130 | title: SearchField( 131 | haveGap: false, 132 | placeholder: '找同学:清华大学计算机专业', 133 | ), 134 | centerTitle: false, 135 | actions: [ 136 | AppBarAction( 137 | icon: SelfIcon.AddBold, 138 | press: () {}, 139 | right: iDefaultPadding / 2, 140 | ), 141 | AppBarAction( 142 | icon: SelfIcon.AddBold, 143 | press: () {}, 144 | right: iDefaultPadding * 0.8, 145 | ), 146 | ], 147 | shadowColor: Colors.transparent, 148 | backgroundColor: iAppBarBackgroundColor, 149 | bottom: PreferredSize( 150 | preferredSize: const Size.fromHeight(48.0), 151 | child: Container( 152 | alignment: Alignment.centerLeft, 153 | height: 40, 154 | child: TabBar( 155 | tabs: _tabList.map((f) { 156 | return Text(f); 157 | }).toList(), 158 | indicator: CustomerTabSizeIndicator( 159 | borderSide: BorderSide( 160 | width: 3, 161 | ), 162 | width: 20, 163 | ), 164 | controller: _tabController, 165 | indicatorColor: Colors.black, 166 | isScrollable: true, 167 | labelColor: Colors.black, 168 | indicatorWeight: 3.0, 169 | labelStyle: TextStyle(fontSize: 20, fontWeight: FontWeight.w700), 170 | unselectedLabelColor: Colors.black38, 171 | unselectedLabelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), 172 | ), 173 | ), 174 | ), 175 | ); 176 | } 177 | 178 | Widget _buildBtn() { 179 | return TextButton( 180 | onPressed: () { 181 | print('点击'); 182 | Navigator.pushNamed( 183 | context, 184 | '/postDetail', 185 | arguments: PostDetailParam(1), 186 | ); 187 | }, 188 | child: Text('去详情'), 189 | ); 190 | } 191 | 192 | @override 193 | Widget build(BuildContext context) { 194 | return Scaffold( 195 | appBar: _buildAppBar(), 196 | backgroundColor: iAppBackgroundColor, 197 | body: TabBarView( 198 | controller: _tabController, 199 | children: [ 200 | Follows(), 201 | Recommend(), 202 | Hot(), 203 | Job(), 204 | Ask(), 205 | ], 206 | ), 207 | ); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.17.1" 44 | cupertino_icons: 45 | dependency: "direct main" 46 | description: 47 | name: cupertino_icons 48 | sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.0.5" 52 | fake_async: 53 | dependency: transitive 54 | description: 55 | name: fake_async 56 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.1" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_svg: 66 | dependency: "direct main" 67 | description: 68 | name: flutter_svg 69 | sha256: "9ac1967e2f72a08af11b05b39167920f90d043cf67163d13a544a358c8f31afa" 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "0.22.0" 73 | flutter_test: 74 | dependency: "direct dev" 75 | description: flutter 76 | source: sdk 77 | version: "0.0.0" 78 | js: 79 | dependency: transitive 80 | description: 81 | name: js 82 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "0.6.7" 86 | matcher: 87 | dependency: transitive 88 | description: 89 | name: matcher 90 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "0.12.15" 94 | material_color_utilities: 95 | dependency: transitive 96 | description: 97 | name: material_color_utilities 98 | sha256: "586678f20e112219ed0f73215f01bcdf1d769824ba2ebae45ad918a9bfde9bdb" 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "0.3.0" 102 | meta: 103 | dependency: transitive 104 | description: 105 | name: meta 106 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "1.9.1" 110 | path: 111 | dependency: transitive 112 | description: 113 | name: path 114 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "1.8.3" 118 | path_drawing: 119 | dependency: transitive 120 | description: 121 | name: path_drawing 122 | sha256: "3bdd251dae9ffaef944450b73f168610db7e968e7b20daf0c3907f8b4aafc8a2" 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "0.5.1+1" 126 | path_parsing: 127 | dependency: transitive 128 | description: 129 | name: path_parsing 130 | sha256: ee5c47c1058ad66b4a41746ec3996af9593d0858872807bcd64ac118f0700337 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "0.2.1" 134 | petitparser: 135 | dependency: transitive 136 | description: 137 | name: petitparser 138 | sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 139 | url: "https://pub.dev" 140 | source: hosted 141 | version: "5.4.0" 142 | sky_engine: 143 | dependency: transitive 144 | description: flutter 145 | source: sdk 146 | version: "0.0.99" 147 | source_span: 148 | dependency: transitive 149 | description: 150 | name: source_span 151 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "1.10.0" 155 | stack_trace: 156 | dependency: transitive 157 | description: 158 | name: stack_trace 159 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.11.0" 163 | stream_channel: 164 | dependency: transitive 165 | description: 166 | name: stream_channel 167 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "2.1.1" 171 | string_scanner: 172 | dependency: transitive 173 | description: 174 | name: string_scanner 175 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "1.2.0" 179 | term_glyph: 180 | dependency: transitive 181 | description: 182 | name: term_glyph 183 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "1.2.1" 187 | test_api: 188 | dependency: transitive 189 | description: 190 | name: test_api 191 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "0.5.1" 195 | vector_math: 196 | dependency: transitive 197 | description: 198 | name: vector_math 199 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "2.1.4" 203 | xml: 204 | dependency: transitive 205 | description: 206 | name: xml 207 | sha256: "80d494c09849dc3f899d227a78c30c5b949b985ededf884cb3f3bcd39f4b447a" 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "5.4.1" 211 | sdks: 212 | dart: ">=3.0.0-0 <4.0.0" 213 | flutter: ">=1.24.0-7.0" 214 | -------------------------------------------------------------------------------- /lib/mock/home.dart: -------------------------------------------------------------------------------- 1 | List SpeechMockList = [ 2 | { 3 | "id": 1, 4 | "userId": 1, 5 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659613/1000", 6 | "name": "张三", 7 | "tag": "活跃作者", 8 | "post": "CEO", 9 | "company": "宇宙科技有限公司", 10 | "isOn": true, 11 | "content": "狂飙很好看啊,是近些年最好的反腐剧!", 12 | "imgList": [ 13 | "https://img2.baidu.com/it/u=912677202,3742700272&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889", 14 | ], 15 | "tagList": ["设计师", "UI"], 16 | "topic": "你觉得狂飙怎么样?", 17 | "link": "https://uiseed.cn", 18 | "share": 1, 19 | "comment": 10, 20 | "praise": 2, 21 | "vip": true, 22 | }, 23 | { 24 | "id": 1, 25 | "userId": 1, 26 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659616/1000", 27 | "name": "李四", 28 | "tag": "活跃用户", 29 | "post": "CFO", 30 | "company": "宇宙科技有限公司", 31 | "isOn": false, 32 | "content": "在线接活,抓紧时间,先到先得!!!!", 33 | "imgList": [ 34 | "https://img2.baidu.com/it/u=2021072444,2926411073&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889", 35 | ], 36 | "tagList": ["前端", "Flutter"], 37 | "topic": "你听听你说的是什么?", 38 | "link": "https://uiseed.cn", 39 | "share": 1, 40 | "comment": 10, 41 | "praise": 2, 42 | "vip": false, 43 | }, 44 | { 45 | "id": 1, 46 | "userId": 1, 47 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659874/1000", 48 | "name": "王二狗", 49 | "tag": "签约作者", 50 | "post": "首席运营", 51 | "company": "宇宙科技有限公司", 52 | "isOn": false, 53 | "content": "没啥想说的", 54 | "imgList": [ 55 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202002%2F13%2F20200213161934_jruxe.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678445735&t=43c3fb350da630a559dd83747ed9b960", 56 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F201703%2F28%2F20170328205510_S2fwv.thumb.1000_0.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678445735&t=a1763ff70281fb518a64d2075497ce64", 57 | "https://hbimg.huabanimg.com/c3f5073a6da479f6fe762620740315f05ff03fc710f79c-zUCDC0_fw658", 58 | ], 59 | "tagList": ["攻城狮", "JavaScript"], 60 | "topic": "你有什么理想?", 61 | "link": "https://uiseed.cn", 62 | "share": 1, 63 | "comment": 10, 64 | "praise": 2, 65 | "vip": true, 66 | }, 67 | { 68 | "id": 1, 69 | "userId": 1, 70 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659876/1000", 71 | "name": "王五", 72 | "tag": "活跃用户", 73 | "post": "首席运营", 74 | "company": "某恒星科技有限公司", 75 | "isOn": true, 76 | "content": "你看看下边的图片好看吗?", 77 | "imgList": [ 78 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202005%2F30%2F20200530120108_sprwb.thumb.1000_0.png&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678446149&t=294cdfbeb3d30aa597ab270b5ac9e42e", 79 | ], 80 | "tagList": ["设计师", "前端", "UI"], 81 | "topic": "躺平", 82 | "link": "https://uiseed.cn", 83 | "share": 1, 84 | "comment": 10, 85 | "praise": 2, 86 | "vip": false, 87 | }, 88 | { 89 | "id": 1, 90 | "userId": 1, 91 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659879/1000", 92 | "name": "赵六", 93 | "tag": "活跃作者", 94 | "post": "运营经理", 95 | "company": "宇宙科技有限公司", 96 | "isOn": false, 97 | "content": "你瞅瞅,你瞅瞅,看我发的都是啥!", 98 | "imgList": [ 99 | "https://img0.baidu.com/it/u=1308361786,4289025415&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889", 100 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F202005%2F30%2F20200530115853_kpkft.png&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1678446185&t=af206f917cc4d837ddb2775521d639e2", 101 | "https://img1.baidu.com/it/u=536270855,1477431967&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889", 102 | "https://hbimg.b0.upaiyun.com/44621cdd545bea93324659f867c54a53229145a78dc49-oYvA66_fw658", 103 | "https://img2.baidu.com/it/u=3585486134,3253940228&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889", 104 | "https://img2.baidu.com/it/u=2176132136,1039855159&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=1422" 105 | ], 106 | "tagList": ["前端", "UI"], 107 | "topic": "在线摸鱼", 108 | "link": "https://uiseed.cn", 109 | "share": 1, 110 | "comment": 10, 111 | "praise": 2, 112 | "vip": false, 113 | }, 114 | ]; 115 | 116 | List AskMockList = [ 117 | { 118 | "title": "公司福利怎么样?", 119 | "mention": ["公司大佬", "某位大佬", "行业大佬"], 120 | "answer": { 121 | "logo": "https://inews.gtimg.com/newsapp_bt/0/15103660182/1000", 122 | "name": "公司员工", 123 | "content": "公司福利哪肯定是杠杠滴,这还用说嘛!各种节假日福利,只有想不到,没有没福利的节日。根本不用说,福利溜的很!", 124 | }, 125 | "count": 10, 126 | "likes": 22, 127 | }, 128 | { 129 | "title": "公司福利怎么样?", 130 | "mention": ["公司大佬", "某位大佬", "行业大佬"], 131 | "answer": { 132 | "logo": "https://inews.gtimg.com/newsapp_bt/0/15103659087/1000", 133 | "name": "公司员工", 134 | "content": "公司福利哪肯定是杠杠滴,这还用说嘛!各种节假日福利,只有想不到,没有没福利的节日。根本不用说,福利溜的很!", 135 | }, 136 | "count": 10, 137 | "likes": 22, 138 | }, 139 | { 140 | "title": "公司福利怎么样?", 141 | "mention": ["公司大佬", "某位大佬", "行业大佬"], 142 | "answer": { 143 | "logo": "https://inews.gtimg.com/newsapp_bt/0/15103659378/1000", 144 | "name": "公司员工", 145 | "content": "公司福利哪肯定是杠杠滴,这还用说嘛!各种节假日福利,只有想不到,没有没福利的节日。根本不用说,福利溜的很!", 146 | }, 147 | "count": 10, 148 | "likes": 22, 149 | }, 150 | { 151 | "title": "公司福利怎么样?", 152 | "mention": ["公司大佬", "某位大佬", "行业大佬"], 153 | "answer": { 154 | "logo": "https://inews.gtimg.com/newsapp_bt/0/15103659382/1000", 155 | "name": "公司员工", 156 | "content": "公司福利哪肯定是杠杠滴,这还用说嘛!各种节假日福利,只有想不到,没有没福利的节日。根本不用说,福利溜的很!", 157 | }, 158 | "count": 10, 159 | "likes": 22, 160 | }, 161 | { 162 | "title": "公司福利怎么样?", 163 | "mention": ["公司大佬", "某位大佬", "行业大佬"], 164 | "answer": { 165 | "logo": "https://inews.gtimg.com/newsapp_bt/0/15103659613/1000", 166 | "name": "公司员工", 167 | "content": "公司福利哪肯定是杠杠滴,这还用说嘛!各种节假日福利,只有想不到,没有没福利的节日。根本不用说,福利溜的很!", 168 | }, 169 | "count": 10, 170 | "likes": 22, 171 | }, 172 | { 173 | "title": "公司福利怎么样?", 174 | "mention": ["公司大佬", "某位大佬", "行业大佬"], 175 | "answer": { 176 | "logo": "https://inews.gtimg.com/newsapp_bt/0/15103659874/1000", 177 | "name": "公司员工", 178 | "content": "公司福利哪肯定是杠杠滴,这还用说嘛!各种节假日福利,只有想不到,没有没福利的节日。根本不用说,福利溜的很!", 179 | }, 180 | "count": 10, 181 | "likes": 22, 182 | }, 183 | ]; 184 | 185 | List FollowsRecommendMockList = [ 186 | { 187 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659087/1000", 188 | "name": "张三", 189 | "company": "网络科技有限公司", 190 | "post": "架构师", 191 | "msgCount": 0, 192 | "vip": true, 193 | }, 194 | { 195 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659094/1000", 196 | "name": "李四", 197 | "company": "网络科技有限公司", 198 | "post": "架构师", 199 | "msgCount": 10, 200 | "vip": false, 201 | }, 202 | { 203 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659378/1000", 204 | "name": "王五", 205 | "company": "网络科技有限公司", 206 | "post": "架构师", 207 | "msgCount": 128, 208 | "vip": true, 209 | }, 210 | { 211 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659379/1000", 212 | "name": "赵六", 213 | "company": "网络科技有限公司", 214 | "post": "架构师", 215 | "msgCount": 0, 216 | "vip": false, 217 | }, 218 | { 219 | "avatar": "https://inews.gtimg.com/newsapp_bt/0/15103659382/1000", 220 | "name": "李青", 221 | "company": "网络科技有限公司", 222 | "post": "架构师", 223 | "msgCount": 30, 224 | "vip": false, 225 | }, 226 | ]; 227 | 228 | List HotListMock = [ 229 | { 230 | "name": "离开上家公司后,我认识到了什么,今天来好好说说!", 231 | "isNew": false, 232 | }, 233 | { 234 | "name": "哪家公司的食堂这么好,快来围观!", 235 | "isNew": true, 236 | }, 237 | { 238 | "name": "XDM快来看看这些题,保证你会感谢我!", 239 | "isNew": false, 240 | }, 241 | ]; 242 | 243 | List HotTopicListMock = [ 244 | { 245 | "title": "汽车行业,营销比安全更重要?", 246 | "desc": "剖析近期电动车事件,谈一谈汽车安全问题。从另一个方面看待汽车行业的营销套路。", 247 | }, 248 | { 249 | "title": "寻找最美梦之队", 250 | "desc": "人在职场,压力已经能不仅是来自外部的动荡,还有公司的调整,战略改变等。", 251 | }, 252 | ]; 253 | 254 | List HotChoicenessColumnMock = [ 255 | { 256 | "name": "行业内幕", 257 | "picture": 258 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Fback_pic%2F05%2F21%2F85%2F8659e0786b52bd3.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631949785&t=8528fe1626c7ff49f166dbbbaa62a7c4", 259 | "tag": "HOT", 260 | "desc": "行业内大佬都在看", 261 | "content": "行业内幕曝光,快点来看看有没有你的行业内幕。", 262 | }, 263 | { 264 | "name": "技术专区", 265 | "picture": 266 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.51miz.com%2FElement%2F00%2F85%2F74%2F24%2F29be1852_E857424_2cdb7af2.jpg%21%2Fquality%2F90%2Funsharp%2Ftrue%2Fcompress%2Ftrue%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.51miz.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631949808&t=f3e66e074a9828a36b8d06008698f95a", 267 | "tag": "", 268 | "desc": "行业内大佬都在看", 269 | "content": "行业内幕曝光,快点来看看有没有你的行业内幕。", 270 | }, 271 | { 272 | "name": "行业内幕", 273 | "picture": 274 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Fback_pic%2F05%2F21%2F85%2F8659e0786b52bd3.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631949785&t=8528fe1626c7ff49f166dbbbaa62a7c4", 275 | "tag": "NEW", 276 | "desc": "行业内大佬都在看", 277 | "content": "行业内幕曝光,快点来看看有没有你的行业内幕。", 278 | }, 279 | { 280 | "name": "技术专区", 281 | "picture": 282 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.51miz.com%2FElement%2F00%2F85%2F74%2F24%2F29be1852_E857424_2cdb7af2.jpg%21%2Fquality%2F90%2Funsharp%2Ftrue%2Fcompress%2Ftrue%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.51miz.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631949808&t=f3e66e074a9828a36b8d06008698f95a", 283 | "tag": "", 284 | "desc": "行业内大佬都在看", 285 | "content": "行业内幕曝光,快点来看看有没有你的行业内幕。", 286 | }, 287 | { 288 | "name": "行业内幕", 289 | "picture": 290 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Fback_pic%2F05%2F21%2F85%2F8659e0786b52bd3.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631949785&t=8528fe1626c7ff49f166dbbbaa62a7c4", 291 | "tag": "HOT", 292 | "desc": "行业内大佬都在看", 293 | "content": "行业内幕曝光,快点来看看有没有你的行业内幕。", 294 | }, 295 | { 296 | "name": "技术专区", 297 | "picture": 298 | "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.51miz.com%2FElement%2F00%2F85%2F74%2F24%2F29be1852_E857424_2cdb7af2.jpg%21%2Fquality%2F90%2Funsharp%2Ftrue%2Fcompress%2Ftrue%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.51miz.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1631949808&t=f3e66e074a9828a36b8d06008698f95a", 299 | "tag": "HOT", 300 | "desc": "行业内大佬都在看", 301 | "content": "行业内幕曝光,快点来看看有没有你的行业内幕。", 302 | }, 303 | ]; 304 | 305 | List HotContentListMock = []; 306 | -------------------------------------------------------------------------------- /lib/pages/home/components/speech_item.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_maimai/components/card_container.dart'; 4 | import 'package:flutter_maimai/components/self_icon.dart'; 5 | import 'package:flutter_maimai/components/tag.dart'; 6 | import 'package:flutter_maimai/components/vip_badge.dart'; 7 | import 'package:flutter_maimai/constants.dart'; 8 | import 'package:flutter_maimai/types.dart'; 9 | 10 | class SpeechItem extends StatefulWidget { 11 | const SpeechItem({ 12 | Key? key, 13 | required this.speech, 14 | }) : super(key: key); 15 | 16 | final speech; 17 | 18 | @override 19 | _SpeechItemState createState() => _SpeechItemState(); 20 | } 21 | 22 | class _SpeechItemState extends State { 23 | String _dealCompanyInfo() { 24 | String _prefix = widget.speech['isOn'] ? '' : '前'; 25 | String _company = widget.speech['company']; 26 | String _post = widget.speech['post']; 27 | return '$_prefix$_company$_post'; 28 | } 29 | 30 | Widget _renderTag() { 31 | String _tag = widget.speech['tag']; 32 | if (_tag == '') { 33 | return Text(''); 34 | } else { 35 | return Tag(_tag); 36 | } 37 | } 38 | 39 | Widget _buildHeader() { 40 | Size size = MediaQuery.of(context).size; 41 | bool _isVip = widget.speech['vip']; 42 | return Row( 43 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 44 | crossAxisAlignment: CrossAxisAlignment.start, 45 | children: [ 46 | GestureDetector( 47 | onTap: () { 48 | Navigator.pushNamed(context, '/user', arguments: UserDetailParam(widget.speech['userId'])); 49 | }, 50 | child: Row( 51 | children: [ 52 | CircleAvatar( 53 | radius: 20, 54 | backgroundImage: NetworkImage(widget.speech['avatar']), 55 | ), 56 | SizedBox(width: iDefaultPadding / 2), 57 | Column( 58 | crossAxisAlignment: CrossAxisAlignment.start, 59 | children: [ 60 | Row( 61 | children: [ 62 | Text( 63 | widget.speech['name'], 64 | style: TextStyle(fontSize: 18, color: Colors.black, fontWeight: FontWeight.w500), 65 | ), 66 | SizedBox(width: iDefaultPadding / 4), 67 | _renderTag(), 68 | ], 69 | ), 70 | Container( 71 | width: size.width * 0.65, 72 | child: Row( 73 | children: [ 74 | Text( 75 | _dealCompanyInfo(), 76 | overflow: TextOverflow.ellipsis, 77 | style: TextStyle(fontSize: 14, color: Colors.black38), 78 | ), 79 | SizedBox(width: 3), 80 | _isVip ? VipBadge() : Text(''), 81 | ], 82 | ), 83 | ), 84 | ], 85 | ), 86 | ], 87 | ), 88 | ), 89 | InkWell( 90 | onTap: () {}, 91 | child: Transform.rotate( 92 | angle: math.pi / 2, 93 | child: Icon( 94 | SelfIcon.Elipsis, 95 | size: 16, 96 | color: Colors.black26, 97 | ), 98 | ), 99 | ), 100 | ], 101 | ); 102 | } 103 | 104 | // 生成单个tag标签 105 | Widget _buildTagItem(String tag) { 106 | return GestureDetector( 107 | onTap: () { 108 | // 跳转到搜索页面,并把标签内容带入 109 | }, 110 | child: Padding( 111 | padding: EdgeInsets.only(right: iDefaultPadding / 4, top: iDefaultPadding / 2, bottom: iDefaultPadding / 2), 112 | child: Text( 113 | '#$tag', 114 | style: TextStyle(fontSize: 16, color: iPrimaryColor), 115 | ), 116 | ), 117 | ); 118 | } 119 | 120 | // 生成tag列表 121 | Widget _buildTagList() { 122 | List _tagList = []; 123 | List _tagData = widget.speech['tagList']; 124 | for (int i = 0; i < _tagData.length; i++) { 125 | _tagList.add(_buildTagItem(_tagData[i])); 126 | } 127 | return Row( 128 | children: _tagList, 129 | ); 130 | } 131 | 132 | Widget _buildImageItem(String image, bool isSingle) { 133 | Size size = MediaQuery.of(context).size; 134 | double _allWidth = size.width - iDefaultPadding * 4; 135 | double _width = isSingle ? _allWidth / 2 : (_allWidth - iDefaultPadding) / 3; 136 | return InkWell( 137 | onTap: () { 138 | // 点击预览 139 | }, 140 | child: isSingle 141 | ? Container( 142 | width: _width, 143 | margin: EdgeInsets.only(top: iDefaultPadding / 2), 144 | decoration: BoxDecoration( 145 | borderRadius: iBorderRadius, 146 | border: new Border.all(color: Colors.black54, width: 0.1), 147 | ), 148 | child: ClipRRect( 149 | borderRadius: BorderRadius.circular(10), 150 | child: Image.network( 151 | image, 152 | fit: BoxFit.cover, 153 | ), 154 | ), 155 | ) 156 | : Container( 157 | width: _width, 158 | height: _width, 159 | margin: EdgeInsets.only(top: iDefaultPadding / 2), 160 | decoration: BoxDecoration( 161 | borderRadius: iBorderRadius, 162 | border: new Border.all(color: Colors.black54, width: 0.1), 163 | image: new DecorationImage( 164 | image: new NetworkImage(image), 165 | fit: BoxFit.cover, 166 | ), 167 | ), 168 | ), 169 | ); 170 | } 171 | 172 | Widget _buildImageList() { 173 | List _imgList = []; 174 | List _imgData = widget.speech['imgList']; 175 | int len = _imgData.length; 176 | Widget _more = Text(''); 177 | int _targetLen = len; 178 | if (len == 1) { 179 | return _buildImageItem(_imgData[0], true); 180 | } else if (len > 3) { 181 | _targetLen = 3; 182 | _more = Positioned( 183 | right: iDefaultPadding / 2, 184 | bottom: iDefaultPadding, 185 | child: Container( 186 | height: 20, 187 | padding: EdgeInsets.symmetric(horizontal: iDefaultPadding / 4), 188 | decoration: BoxDecoration( 189 | color: Colors.black54, 190 | borderRadius: BorderRadius.all(Radius.circular(4)), 191 | ), 192 | child: Center( 193 | child: Text('+${len - 3}', style: TextStyle(color: Colors.white, fontSize: 12)), 194 | ), 195 | ), 196 | ); 197 | } 198 | 199 | for (int i = 0; i < _targetLen; i++) { 200 | _imgList.add(_buildImageItem(_imgData[i], false)); 201 | if (i != _targetLen - 1) { 202 | _imgList.add(SizedBox(width: iDefaultPadding / 2)); 203 | } 204 | } 205 | return Stack( 206 | children: [ 207 | Row( 208 | children: _imgList, 209 | ), 210 | _more, 211 | ], 212 | ); 213 | } 214 | 215 | // 生成主体内容 216 | Widget _buildContent(Size size) { 217 | String _content = widget.speech['content']; 218 | return GestureDetector( 219 | onTap: () {}, 220 | child: Container( 221 | padding: EdgeInsets.only(top: iDefaultPadding / 2), 222 | constraints: BoxConstraints( 223 | // maxHeight: 300, 224 | minHeight: 30, 225 | ), 226 | decoration: BoxDecoration(), 227 | child: Column( 228 | crossAxisAlignment: CrossAxisAlignment.start, 229 | children: [ 230 | Text( 231 | _content, 232 | overflow: TextOverflow.ellipsis, 233 | maxLines: 4, 234 | style: TextStyle( 235 | fontSize: 18, 236 | color: Colors.black, 237 | ), 238 | ), 239 | _buildImageList(), 240 | _buildTagList(), 241 | ], 242 | ), 243 | ), 244 | ); 245 | } 246 | 247 | Widget _buildFooter() { 248 | int _share = widget.speech['share']; 249 | int _comment = widget.speech['comment']; 250 | int _praise = widget.speech['praise']; 251 | return Padding( 252 | padding: EdgeInsets.only(top: iDefaultPadding), 253 | child: Row( 254 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 255 | children: [ 256 | BottomItem( 257 | icon: SelfIcon.Share, 258 | value: _share, 259 | press: () {}, 260 | ), 261 | BottomItem( 262 | icon: SelfIcon.Comment, 263 | value: _comment, 264 | press: () {}, 265 | ), 266 | BottomItem( 267 | icon: SelfIcon.Good, 268 | value: _praise, 269 | press: () {}, 270 | ), 271 | ], 272 | ), 273 | ); 274 | } 275 | 276 | Widget _buildTopic() { 277 | String _topic = widget.speech['topic']; 278 | if (_topic == '') return Text(''); 279 | return Container( 280 | height: 30, 281 | padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10), 282 | decoration: BoxDecoration( 283 | color: iPrimaryColor.withOpacity(0.05), 284 | borderRadius: BorderRadius.all(Radius.circular(30)), 285 | ), 286 | child: Row( 287 | mainAxisSize: MainAxisSize.min, 288 | children: [ 289 | Container( 290 | width: 15, 291 | height: 15, 292 | margin: EdgeInsets.only(right: iDefaultPadding / 4), 293 | decoration: BoxDecoration( 294 | color: iPrimaryColor.withOpacity(0.5), 295 | borderRadius: BorderRadius.all(Radius.circular(15)), 296 | ), 297 | child: Center( 298 | child: Text( 299 | '#', 300 | style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), 301 | ), 302 | ), 303 | ), 304 | Text( 305 | _topic, 306 | style: TextStyle(fontSize: 12, color: iPrimaryColor.withOpacity(0.5), fontWeight: FontWeight.w500), 307 | ), 308 | ], 309 | ), 310 | ); 311 | } 312 | 313 | @override 314 | Widget build(BuildContext context) { 315 | Size size = MediaQuery.of(context).size; 316 | return CardContainer( 317 | margin: EdgeInsets.only(left: iDefaultPadding / 2, right: iDefaultPadding / 2, bottom: iDefaultPadding / 2), 318 | padding: EdgeInsets.all(iDefaultPadding), 319 | child: Column( 320 | crossAxisAlignment: CrossAxisAlignment.start, 321 | children: [ 322 | _buildHeader(), 323 | _buildContent(size), 324 | _buildTopic(), 325 | _buildFooter(), 326 | ], 327 | ), 328 | ); 329 | } 330 | } 331 | 332 | class BottomItem extends StatelessWidget { 333 | const BottomItem({ 334 | Key? key, 335 | required this.value, 336 | required this.icon, 337 | required this.press, 338 | }) : super(key: key); 339 | 340 | final int value; 341 | final IconData icon; 342 | final VoidCallback press; 343 | 344 | @override 345 | Widget build(BuildContext context) { 346 | return Expanded( 347 | child: InkWell( 348 | onTap: press, 349 | child: Center( 350 | child: Row( 351 | mainAxisAlignment: MainAxisAlignment.center, 352 | crossAxisAlignment: CrossAxisAlignment.center, 353 | children: [ 354 | Icon( 355 | icon, 356 | size: 20, 357 | color: Colors.black54, 358 | ), 359 | SizedBox(width: iDefaultPadding / 2), 360 | Text( 361 | '$value', 362 | style: TextStyle( 363 | fontSize: 16, 364 | fontWeight: FontWeight.w500, 365 | color: Colors.black54, 366 | ), 367 | ), 368 | ], 369 | ), 370 | ), 371 | ), 372 | ); 373 | } 374 | } 375 | --------------------------------------------------------------------------------