Flutter-基类封装
本文最后更新于 927 天前

1. 页面基类

支持网络断开后重试、仿Android生命周期

    import 'dart:async';
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:flutter_basic/flutter_biz_widgets.dart';
    import 'package:flutter_basic/flutter_http_dio.dart';
    import 'package:connectivity/connectivity.dart';
    import 'package:flutter_basic/flutter_utils.dart';
    abstract class BasePage extends StatefulWidget {
      const BasePage({ Key key }) : super(key: key);

      @override
      BasePageState createState() => getState();

      ///子类实现
      BasePageState getState();
    }

    abstract class BasePageState<T extends BasePage> extends State<T> with RouteAware,WidgetsBindingObserver{
      CancelToken cancelToken;

      // 记录当前网络状态 如:当前网络未链接,一定时间后,网络恢复后需要拉起重新链接 此为标识符
      String networkStatus;

      final Connectivity _connectivity = Connectivity();

      StreamSubscription<ConnectivityResult> _connectivitySubscription;

      // 优化
      AppLifecycleState lastState;
      bool isPause = false;

      //网络初始状态
      netListener() {
        _connectivitySubscription =
            _connectivity.onConnectivityChanged.listen((ConnectivityResult result) {
              if(networkStatus==ConnectivityResult.none.toString() && result!=ConnectivityResult.none){
                retry();
              }
              networkStatus = result.toString();
            });
      }

      // 判断网络是否可用
      bool isNetValid(){
        return networkStatus==ConnectivityResult.none.toString();
      }

      Future<bool> checkNetConnect() async {
        var connectivityResult = await (_connectivity.checkConnectivity());
        if (connectivityResult == ConnectivityResult.mobile) {
          return true;
        }
        return false;
      }

      //网络结束监听
      connectivityDispose() {
        _connectivitySubscription.cancel();
      }

      //网络进行监听
      Future<Null> initConnectivity() async {
        //平台消息可能会失败,因此我们使用Try/Catch PlatformException。
        try {
          networkStatus = (await _connectivity.checkConnectivity()).toString();
          netListener();
        } on PlatformException catch (e) {
          print(e.toString());
          networkStatus = 'Failed to get connectivity.';
        }
      }

      @override
      void initState() {
        initConnectivity();
        cancelToken = CancelToken();
        onCreate();
        super.initState();
      }

      @override
      void dispose() {
        cancelToken?.cancel("页面销毁,网络请求自动关闭!");
        connectivityDispose();
        Future.delayed(Duration(milliseconds: 300));
        onDestroy();
        super.dispose();
        IIRouteObserver.instance.unsubscribe(this);
        WidgetsBinding.instance.removeObserver(this);
      }

      @override
      Widget build(BuildContext context) {
      }

      // 网络从不可用到可用时 进行回调 可以用于刷新数据
      void retry(){}

      @override
      void didPushNext() {
        isPause = true;
        onPause();
      }

      @override
      void didPop() {
        onStop();
      }

      @override
      void didPopNext() {
        isPause = false;
        onReStart();
      }

      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
        // 前台到后台  inactive->paused
        // 后台到前台  resumed
        if(lastState!=state && !isPause){
          lastState = state;
          if (state == AppLifecycleState.resumed) {
            onReStart();// onResume(); 也行   onReStart比较合适 因为先进入后台在回到前台 那么这个是 “再”的感觉, 初始化进入 再上面以及执行了 onResume
          } else if (state == AppLifecycleState.inactive) {// 处于非激活状态,无法响应用户输入
            onPause();
          } else if (state == AppLifecycleState.paused) { // 不可见且无法响应用户输入
            onStop();
          } else if (state == AppLifecycleState.detached) {
            onDestroy();
          }
        }
      }

      void onCreate() {}

      void onDestroy() {
        FTShowToastUtils.dismiss();
      }

      void onResume() {}

      void onPause() {}

      void onStop() {}

      void onReStart() {}

      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        WidgetsBinding.instance.addObserver(this);
        IIRouteObserver.instance.subscribe(this, ModalRoute.of(context));
      }
    }

2. 对话框Dialog基类

将间距、圆角等封装

    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';

    abstract class BaseDialog extends StatefulWidget {
      const BaseDialog({Key? key}) : super(key: key);

      @override
      BaseDialogState createState() => getState();

      ///子类实现
      BaseDialogState getState();
    }

    abstract class BaseDialogState<T extends BaseDialog> extends State<T> {
      int milliseconds = 120;
      double width = 0.8.sw;
      double radis = 10;
      double paddingHorizontal = 12.w;
      double paddingVertical = 12.h;

      @override
      void initState() {
        super.initState();
        setDuration(milliseconds);
        setDialogWidth(width);
        setDialogRadis(radis);
        setDialogPdHorizontal(paddingHorizontal);
        setDialogPdVertical(paddingVertical);
      }

      @override
      Widget build(BuildContext context) {
        return Center(
          child: AnimatedContainer(
            width: 0.8.sw,
            duration: Duration(milliseconds: milliseconds),
            curve: Curves.easeInCubic,
            child: Material(
              borderRadius: BorderRadius.circular(10),
              child: Container(
                padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
                child: body(),
              ),
            ),
          ),
        );
      }

      // dialog主页面
      Widget body();

      void setDuration(int milliseconds) {
        this.milliseconds = milliseconds;
      }

      void setDialogWidth(double width) {
        this.width = width;
      }

      void setDialogRadis(double radis) {
        this.radis = radis;
      }

      void setDialogPdHorizontal(double paddingHorizontal) {
        this.paddingHorizontal = paddingHorizontal;
      }

      void setDialogPdVertical(double paddingVertical) {
        this.paddingVertical = paddingVertical;
      }
    }

3. GetX控制器基类封装

3.1 Base

    // 用于设置dio的CancelToken
    import 'dart:io';

    import 'package:dio/dio.dart';
    import 'package:get_storage/get_storage.dart';
    import 'package:get/get.dart';
    import 'package:huadian/net/network_manager.dart';
    import 'package:huadian/net/result.dart';
    import 'package:huadian/tools/log_util.dart';

    class BaseGetXController<T> extends GetxController {
      fin// 用于设置dio的CancelToken
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
import 'package:huadian/ext/extends.dart';
import 'package:huadian/net/network_manager.dart';
import 'package:huadian/net/result.dart';
import 'package:huadian/route/route_config.dart';
import 'package:huadian/tools/alert_toast.dart';
import 'package:huadian/tools/config.dart';
import 'package:huadian/util/hjt_utils.dart';

enum ViewErrorStateType{
  ERROR_CLIENT,
  ERROR_SERVICE,
  ERROR_TIMEOUT,
  ERROR_NETWORK,
  ERROR_PARAMS,
  DEFAULT
}

enum ViewState{
  IDLE,//  空闲中
  BUSY,
  EMPTY,
  ERROR,
  //  仅用于封装内部标记 调用者无需关心干嘛
  SUCCESS
}

abstract class BaseGetXController<T> extends GetxController {
  var _errorStateType = ViewErrorStateType.DEFAULT.obs;
  var _viewState = ViewState.IDLE.obs;

  final sfs = GetStorage();

  CancelToken _cancelToken = CancelToken();

  T get to => Get.find();

  CancelToken get cancelToken => _cancelToken;

  @override
  void onClose() {
    super.onClose();
    _cancelToken.cancel("关闭页面,取消正在的网络请求!");
  }

  Future<Result?> get(String url, Map param,
      {bool needToken = true,bool showLoading = true}) async {
    _viewState.value = ViewState.BUSY;
    Result? response = await HjtDio()
        .get(url, param, token: needToken, cancelToken: _cancelToken,showLoading: showLoading);
    _viewState.value = ViewState.IDLE;
    return handleResponse(response);
  }

  Future<Result?> post<T>(String url, Map param,
      {bool needToken = true,bool showLoading = true}) async {
    _viewState.value = ViewState.BUSY;
    Result? response = await HjtDio()
        .post<T>(url, param, token: needToken, cancelToken: _cancelToken,showLoading: showLoading);
    _viewState.value = ViewState.IDLE;
    return handleResponse(response);
  }

  Result? handleResponse(Result? response){
    Result? result;
    response?.run((){
      switch(response.code){
        case TOKEN_EXPIRE:
          loginExpire();
          break;
        case ERROR_TIMEOUT:
          _errorStateType.value = ViewErrorStateType.ERROR_TIMEOUT;
          _viewState.value = ViewState.ERROR;
          break;
        case ERROR_SERVICE:
          _errorStateType.value = ViewErrorStateType.ERROR_SERVICE;
          _viewState.value = ViewState.ERROR;
          break;
        case ERROR_CLIENT:
          _errorStateType.value = ViewErrorStateType.ERROR_CLIENT;
          _viewState.value = ViewState.ERROR;
          break;
        case ERROR_PARAMS:
          _errorStateType.value = ViewErrorStateType.ERROR_PARAMS;
          _viewState.value = ViewState.ERROR;
          if(response.data is String && !(response.data as String).isNullOrEmpty())
            EasyLoading.showError("{response.data}");
          else
            EasyLoading.showError("{response.msg}");
          break;
        case ERROR_NETWORK:
          _errorStateType.value = ViewErrorStateType.ERROR_NETWORK;
          _viewState.value = ViewState.ERROR;
          break;
        case SUCCESS:
        // 需要 调用者 对数据判断 为"空" 设置状态
          _errorStateType.value = ViewErrorStateType.DEFAULT;
          _viewState.value = ViewState.SUCCESS;
          result = response;
          break;
      }
    });
    return result;
  }

  // 上传单个图片
  uploadSingleImage() async {
    await HjtUtils.uploadSingleImage(cancelToken: _cancelToken);
  }

  // 上传多个多拍
  uploadMultiImage() async {
    await HjtUtils.uploadMultiImage(cancelToken: _cancelToken);
  }

  void loginExpire() {
    AlertToast().showErrorAlertToast("登录状态失效!请重新登录。");
    Future.delayed(Duration(seconds: 1),() {
      Get.toNamed(RouteConfig.login);
    });
  }


  bool get isEmpty => _viewState.value == ViewState.EMPTY;
  bool get isIdle => _viewState.value == ViewState.IDLE;
  bool get isBusy => _viewState.value == ViewState.BUSY;
  bool get isError => _viewState.value == ViewState.ERROR;

  bool get isErrorClient => _errorStateType.value == ViewErrorStateType.ERROR_CLIENT;
  bool get isErrorService => _errorStateType.value == ViewErrorStateType.ERROR_SERVICE;
  bool get isErrorTimeout => _errorStateType.value == ViewErrorStateType.ERROR_TIMEOUT;
  bool get isErrorNetwork => _errorStateType.value == ViewErrorStateType.ERROR_NETWORK;
  bool get isErrorParams => _errorStateType.value == ViewErrorStateType.ERROR_PARAMS;
  bool get isErrorDefault => _errorStateType.value == ViewErrorStateType.DEFAULT;

  void setEmpty() {
    _viewState.value = ViewState.EMPTY;
  }

  get viewState => _viewState.value;
  get errorStateType => _errorStateType.value;

}

3.2 BasePageController继承上诉的用于分页使用

// 用于设置dio的CancelToken
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
import 'package:huadian/base/base_getx_controller.dart';
import 'package:huadian/net/network_manager.dart';
import 'package:huadian/net/result.dart';
import 'package:huadian/tools/log_util.dart';
import 'package:huadian/util/hjt_utils.dart';

abstract class BasePageGetXController<T> extends BaseGetXController<T> {
  // 多 Tab使用 一个Controller,保存每个tab页面的当前分页 page变量。切换tab重置 page

  int _page = 1;

  // 防止 页码参数不是这个 外部设置  一般都是这个除非 xxx
  String _pageParams = "page";

  String get pageParams => _pageParams;

  set pageParams(String value) {
    _pageParams = value;
  }

  int get page => _page;

  set page(int value) {
    _page = value;
  }




  // 一些控制器等
  EasyRefreshController _easyRefreshController = EasyRefreshController();
  ScrollController _scrollController = ScrollController();

  EasyRefreshController get easyRefreshController => _easyRefreshController;
  ScrollController get scrollController => _scrollController;



  Future<Result?> get(String url, Map param,
      {bool needToken = true,bool loadMore = false,bool showLoading = true}) async {
    return await super.get(url, handleLoadMore(param,loadMore), needToken: needToken,showLoading: showLoading);
  }

  Future<Result?> post<T>(String url, Map param,
      {bool needToken = true,bool loadMore = false,bool showLoading = true}) async {
    return await super.post<T>(url, handleLoadMore(param,loadMore), needToken: needToken,showLoading: showLoading);
  }

  Map handleLoadMore(Map param, bool loadMore) {
    if(loadMore){
      page = page + 1;
      param[pageParams] = page;
    }else{
      page = 1;
      param[pageParams] = 1;
    }
    return param;
  }



// 控制器的状态
  void finishRefresh() {
    resetLoadState();
    easyRefreshController.finishRefresh();
  }

  void resetLoadState(){
    easyRefreshController.resetLoadState();
  }

  void finishLoad(bool noMore){
    easyRefreshController.finishLoad(noMore: noMore);
  }
}

4. 网络层

import 'dart:convert';
import 'dart:typed_data';
import 'package:connectivity/connectivity.dart';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http_parser/http_parser.dart';
import 'dart:async';
import 'dart:io';
import 'package:get_storage/get_storage.dart';
import 'package:huadian/ext/env.dart';
import 'package:huadian/net/api.dart';
import 'package:huadian/net/result.dart';
import 'package:huadian/tools/alert_toast.dart';
import 'package:huadian/tools/config.dart';
import 'package:huadian/ext/extends.dart';
import 'package:huadian/tools/log_util.dart';
import 'package:huadian/util/pachage_version.dart';
import 'package:huadian/util/user_manager.dart';

class HjtDio {
static final HjtDio _manager = HjtDio._init();
static late Dio _dio;

final box = GetStorage();

HjtDio._init() {
  BaseOptions options = BaseOptions(
    baseUrl: GlobalConfig.saasUrl(),
    connectTimeout: 1000 * 30,
    // 连接服务器超时时间,单位是毫秒.
    receiveTimeout: 1000 * 30,
    // 响应流上前后两次接受到数据的间隔,单位为毫秒, 这并不是接收数据的总时限.
    responseType: ResponseType.json,
    contentType: "application/json",
  );
  PackageVersionInfo.instance!.init();
  _dio = Dio(options);

  // 添加拦截器
  // if (GlobalConfig.env != Env.Formal) {
  _dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
    print("\n================== 请求数据 ==========================");
    print("url = {options.uri.toString()}");
    print("headers ={options.headers}");
    LogUtil.d("data = {options.data}");
    LogUtil.d("params ={options.queryParameters}");
    return handler.next(options);
  }, onResponse: (response, handler) {
    print("\n================== 响应数据 ==========================");
    print("code = {response.statusCode}");
    LogUtil.d(response);
    print("\n");
    return handler.next(response);
  }));

  // 这里加了 下面就捕获不到不能处理了
  /*
  , onError: (error, handler) {
    print("\n================== 错误响应数据 ======================");
    print("type ={error.type}");
    print("message = {error.message}");
    print("stackTrace ={error.stackTrace}");
    print("\n");
  }
   */
  // }

  if (GlobalConfig.isProxyChecked) {
    (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
        (client) {
      client.badCertificateCallback =
          (X509Certificate cert, String host, int port) {
        return GlobalConfig.isProxyChecked;
      };
      client.findProxy = (url) {
        return 'PROXY {GlobalConfig.proxy}';
      };
      return client;
    };
  }
}

factory HjtDio() {
  return _manager;
}

Future<Result?> get<T>(
  url,
  param, {
  bool token = true,
  bool showLoading = true,
  CancelToken? cancelToken,
  String? contentType
}) async {
  return await request<T>(url,
      queryParameters: param, method: Method.get, token: token,cancelToken: cancelToken,contentType: contentType);
}

Future<Result?> post<T>(
  url,
  param, {
  bool token = true,
  bool showLoading = true,
  CancelToken? cancelToken,
  String? contentType
}) async {
  return await request<T>(url,
      data: param,
      method: Method.post,
      token: token,
      showLoading: showLoading,
      cancelToken: cancelToken,contentType: contentType);
}

Future<Result?> request<T>(String path,
    {String method = 'GET',
    String? contentType = "application/x-www-form-urlencoded;charset=utf-8",
    // String contentType = "application/json; charset=UTF-8",
    bool token = true,
    bool showLoading = true,
    dynamic data,
    CancelToken? cancelToken,
    Map<String, dynamic>? queryParameters,
    Options? options}) async {
  var connectivityResult = await (new Connectivity().checkConnectivity());
  // 无网
  if (connectivityResult == ConnectivityResult.none) {
    return Result(ERROR_NETWORK,"无网络连接!请检查网络设置。",null);
  }

  Response response;

  if (token) {
    String? token = UserManager().getToken();
    print("token =token");
    if (token == null) {
      return Result(TOKEN_EXPIRE,"登录状态失效!请重新登录。",null);
    }
    _dio.options.headers.addAll({'token': token});
  }

  _dio.options.contentType = contentType;
  _dio.options.method = method;

  // 默认 网络请求自动展示加载中
  if (showLoading) EasyLoading.show();

  try {
    response = await _dio.request(path,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        data: data);

    EasyLoading.dismiss();

    // 不用判断也可 必为200
    if (response.statusCode == 200) { // 服务器正常给 结果
      try {
        Result result = Result.fromJson(response.data);
        // 可以在这 对不同的code码 进行处理  如:登录失效等
        if (result.code == TOKEN_EXPIRE){
          result.code = TOKEN_EXPIRE;
        } else if(result.code != SUCCESS){
          result.code = ERROR_PARAMS;
        } // 其他额外的code
        return result;
      } catch (e,s) {
        return Result(ERROR_CLIENT,"客户端错误!",null);
      }
    }
    return Result(ERROR_CLIENT,"客户端错误!",null);
  } catch (error, s) {
    var result = Result(ERROR_CLIENT,"未知错误!",null);
    if (error is DioError) {

      debugPrint("\n================== 错误响应数据 ======================");
      debugPrint("type = {error.type}");
      debugPrint("message ={error.message}");
      debugPrint("stackTrace = {error.stackTrace}");
      debugPrint("\n");

      switch (error.type) {
        case DioErrorType.connectTimeout:
        case DioErrorType.sendTimeout:
        case DioErrorType.receiveTimeout:
          result =  Result(ERROR_TIMEOUT,"网络请求超时错误!",null);
          break;
        case DioErrorType.response: // 服务器非200返回
        // 非正常请求服务器200
          if (showLoading)
            AlertToast().showErrorAlertToast(error.message);
          result = Result(ERROR_SERVICE,"服务器错误!",null);
          break;
        case DioErrorType.cancel:
          result = Result(ERROR_CLIENT,"网络请求取消!",null);
          break;
        case DioErrorType.other:
          if (showLoading)
            AlertToast().showErrorAlertToast(error.message);
          break;
      }
    }
    return result;
  }
}

// 不可用
Future<Result?> uploadImage({required String? path,CancelToken? cancelToken}) async {
  if(path.isNullOrEmpty()){
    return Result(ERROR_CLIENT,"图片路径为空!",null);
  }
  var name = path!.substring(path.lastIndexOf("/") + 1, path.length);
  FormData formData = FormData.fromMap({
    "color": "#e3e3e3",
    "size": 16,
    "iswatermark": 0,
    // 没有水印
    "file": MultipartFile.fromFile(path, filename: name),
  });
  // return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "application/octet-stream", method: Method.post,);
  return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "multipart/form-data", method: Method.post,);
}

Future<Result?> uploadMemoryImage({required Uint8List uint8list,CancelToken? cancelToken,bool showLoading = true}) async {
  FormData formData = FormData.fromMap({
    "color": "#e3e3e3",
    "size": 16,
    "iswatermark": 0,
    "file": MultipartFile.fromBytes(
      uint8list,
      // 文件名
      filename: 'gallery.jpg',
      // 文件类型
      contentType: MediaType("image", "jpg"),
    )
  });
  // return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "application/octet-stream", method: Method.post,);
  return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "multipart/form-data", method: Method.post,showLoading: showLoading);
}

int failUploadNum = 0;
// 多张分开一个一个发  效果上实现 多图上传
Future<Result?> uploadMemoryImageByForge({required List<Uint8List> fileList,CancelToken? cancelToken}) async {
  failUploadNum = 0;
  int len = fileList.length;
  for (int i = 0; ii/len) \n 失败个数:failUploadNum");
    try{ // 某一张出错 不影响其他的
      Result? result = await uploadMemoryImage(uint8list: fileList[i],cancelToken: cancelToken,showLoading: false);
      if(result?.code!=200){
        failUploadNum = failUploadNum + 1;
      }
    }catch(e){}
  }
  EasyLoading.showToast("上传成功 {len -failUploadNum}个 失败failUploadNum个",duration: Duration(milliseconds: 1500));
}

// 后端不支持 多张一起上传
Future<Result?> uploadMemoryImageList({required List<Uint8List> fileList,CancelToken? cancelToken}) async {
  List<MultipartFile> files = [];
  for (int i = 0; i < fileList.length; i++) {
    files.add(MultipartFile.fromBytes(
      fileList[i],
      // 文件名
      filename: 'gallery_i.jpg',
      // 文件类型
      contentType: MediaType("image", "jpg"),
    ));
  }
  FormData formData = FormData.fromMap({
    "color": "#e3e3e3",
    "size": 16,
    "iswatermark": 0,
    "file": files
  });
  // return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "application/octet-stream", method: Method.post,);
  return request(Api.API_UPLOAD_IMAGE,data: formData,contentType: "multipart/form-data", method: Method.post,);
}

// // 下载图片
// void downLoadImageSave2Gallery(String url,{CancelToken? cancelToken}) async {
//   EasyLoading.show(status: "下载中...");
//   try {
//     // https://sv.huaji.com/huaji_saas_220107134805_2729.jpeg
//     var response = await Dio().get(url,
//         options: Options(responseType: ResponseType.bytes));
//     final result = await ImageGallerySaver.saveImage(
//         Uint8List.fromList(response.data),
//         quality: 60,
//         name: url.substring(url.lastIndexOf("/")));
//     EasyLoading.showSuccess("已保存到系统相册!");
//     print(result);
//   } on DioError catch (e) {
//     EasyLoading.showError("保存失败:e");
//     print('downloadFile error---------e');
//   }
// }

// 下载资源
// void downLoadResource2System(String url,{String? savePath}) async {
//   try {
//     String? path;
//     if(savePath!=null){
//       path = savePath + url.substring(url.lastIndexOf("/"));
//     }else{
//       var appDir = await getTemporaryDirectory();
//       path = appDir.path + url.substring(url.lastIndexOf("/"));
//     }
//     await Dio().download(url, savePath,onReceiveProgress: (int count, int total){
//       EasyLoading.showProgress(count/total,status: "下载中...");
//     });
//     final result = await ImageGallerySaver.saveFile(path);
//     EasyLoading.showSuccess("已保存到手机!");
//     print(result);
//   }catch(e){
//     EasyLoading.showError("下载失败:e");
//     print('downloadFile error---------$e');
//   }
// }
iichen:https://iichen.cn/?p=87
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇