西建大iOS Club 社团 AI App 到底是怎么搞出来的

240 阅读4分钟

就在前不久,西建大iOS Club 社团 AI 安卓版App正式上线了。其实也是正好,正好我在学Flutter,就想着拿Flutter搞个社团AI App出来。

然后,就搞出来了。

好多人不会搞PWA,怎么办

PWA,简单来讲就是你可以直接把网站 "安装" 到设备上。这样你就可以快速访问了。像社团有些网站就支持PWA(建大百科、iOS 图书馆、社团AI)。

而非常好的一点就在于,社团AI使用的开源前端项目LobeChat就支持PWA。如果你想 "安装" 社团AI,你只需要在Edge上打开社团AI网站,他自己就会跳出安装的弹窗。

但问题是很多人用的并不是Edge(我也不知道谷歌浏览器和火狐支不支持PWA),而是手机自带的浏览器。这种浏览器是不支持PWA的。

那么就很没办法,不能快速访问到社团AI,这也就导致很多人使用社团AI都是在电脑上用的。

其实在今年1月之前,我就想过要不要搞一个移动端的社团AI,但是我电脑上的Flutter环境死活也配置不好(其实就是因为我用户名是中文),而且自己写一个有点太麻烦了,所以就一直搁置了下来。

直到,有人说了这样一句话:

直接拿WebView包装一下,搞个App出来

把网站 "包装" 进去

他讲到的,其实就是在App上直接访问网站。相当于就是放了个 "浏览器" 上去。

而与此同时,我也把Windows用户名问题给解决了。正好我也不用从头开始写个App出来。所以,直接开搞了。

其实整个的代码逻辑很简单,放个WebView进去就行了。这里我使用的WebView库是 flutter_inappwebview

flutter pub add flutter_inappwebview

然后就直接在 lib/main.dart 上开写就行:

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    var theme = Theme.of(context);
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
      statusBarColor: theme.scaffoldBackgroundColor, // 状态栏背景颜色
      statusBarIconBrightness: theme.brightness == Brightness.dark
          ? Brightness.light // 如果是深色主题,图标为亮色
          : Brightness.dark, // 如果是浅色主题,图标为暗色
    ));
    return MaterialApp(
      title: 'iOS AI',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const WebViewScreen(),
    );
  }
}

class WebViewScreen extends StatefulWidget {
  const WebViewScreen({super.key});

  @override
  State<WebViewScreen> createState() => _WebViewScreenState();
}

class _WebViewScreenState extends State<WebViewScreen> {
  late final InAppWebViewController _controller;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
          top: true,
          child: InAppWebView(
            initialUrlRequest: URLRequest(
              url:
                  WebUri.uri(Uri.parse('https://gpt.xauat.site/')), // 替换为你的网站URL
            ),
          ),
        ),
    );
  }
}

就直接在SafeArea里套个InAppWebView就行了。

但是如果你现在运行,你就会发现:

欸,怎么显示不出来?

设置权限

这里其实是因为,你没有设置相关的权限。

现在我们来到这个文件:

android/app/src/main/AndroidManifest.xml

直接在下面加个权限:

<uses-permission
        android:name="android.permission.INTERNET"
        tools:ignore="ManifestOrder" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />

现在再运行,OK了。

要是还不行,直接实机测试

写一个返回事件

我们测试的时候会发现一点:我们无法使用返回手势进行页面返回操作,直接退出App了。

这里因为WebView的回退跟应用的返回事件并没有任何关联。怎么办?我们给他加上这层关联不就行了?

所以我们这里得使用WillPopScope控件了。而且我们得把WebView的控件给保留下来。我们的代码得这么写:

class _WebViewScreenState extends State<WebViewScreen> {
  late final InAppWebViewController _controller;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        final canGoBack = await _controller.canGoBack();
        if (canGoBack) {
          _controller.goBack();
        }

        // 允许关闭页面
        return !canGoBack;
      },
      child: Scaffold(
        body: SafeArea(
          top: true,
          child: InAppWebView(
            initialUrlRequest: URLRequest(
              url:
                  WebUri.uri(Uri.parse('https://gpt.xauat.site/')), // 替换为你的网站URL
            ),
            initialSettings: InAppWebViewSettings(
              javaScriptEnabled: true,
              mediaPlaybackRequiresUserGesture: false,
              databaseEnabled: true,

              // Android 专属
              useHybridComposition: true,
              allowFileAccess: true,
              allowContentAccess: true,
              domStorageEnabled: true,
              // 启用本地存储

              allowsInlineMediaPlayback: true,
              allowsBackForwardNavigationGestures: true,
            ),
            onWebViewCreated: (controller) {
              _controller = controller;
            },
            onPermissionRequest: (controller, request) async {
              return PermissionResponse(
                resources: request.resources,
                action: PermissionResponseAction.GRANT,
              );
            },
          ),
        ),
      ),
    );
  }
}

当我们进行返回操作时,WillPopScope控件会触发onWillPop事件。而onWillPop事件中,我们规定,当WebView不能进行历史回退时,就直接退出,如果可以,进行历史回退而不退出应用。

同时我们也开启一下WebView的权限,这样就能正常运行网站了。

现在,我们就可以使用返回手势进行历史回退操作了。

想访问手机图片或者文件夹,怎么办

目前最新的LobeChat支持图片解析,同时服务端数据库版的LaobeChat支持文件解析和知识库。但是当我们上传图片时,会无法调用。这是为什么呢?

因为没有权限嘛。

所以我们得去趟刚才设置权限的地方:

android/app/src/main/AndroidManifest.xml

设置一下:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.VIDEO_CAPTURE" />
    <uses-permission android:name="android.permission.AUDIO_CAPTURE" />

当然我们还可以让用户帮我们设置权限。我们先安装这个库:

flutter pub add permission_handler

然后在初始化的时候让用户自己选择要不要打开该权限:

import 'package:permission_handler/permission_handler.dart';

...

class _WebViewScreenState extends State<WebViewScreen> {
  late final InAppWebViewController _controller;

  @override
  void initState() {
    super.initState();
    // 初始化逻辑转移到 WebView 属性中
    requestPermissions();
  }

  Future<void> requestPermissions() async {
    await [
      Permission.camera,
      Permission.microphone,
      Permission.storage,
      Permission.speech
    ].request();
  }
 ...(中间代码省略) 
}

这样就可以了!

发布

现在,我们就可以发布了!

但是在发布之前还得设置一下你的App图标:

选中这几个里头的都得改

然后在控制台输入这个即可:

flutter build apk

这样就可以了!

当然,因为我没有苹果电脑,所以我生成不了iOS平台的安装包。所以这期并不涉及iOS平台。

End

到这里就结束了,具体的项目地址在这里