「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了

郁保四
• 阅读 3338

「daza.io」是一款基于技能树(正在实现)的技术内容聚合应用,根据你的技能对内容进行筛选,让你在这个信息过载的时代里更高效地获取你所需的内容。

自上次发文章之后已经过了2个月了,我也在11月19号结束了一个人的旅行(历时59天)回到了深圳,专心于完成这个全端项目的客户端开发,终于在12月2号 iOS 版上线 AppStore ,12月7号 Android 上线到 GooglePlay。

最初我将 iOS 版定价为1元,但是后来和一朋友聊天时聊到这个项目能为用户提供什么价值的问题,后来想想目前这个项目能给用户提供的价值是有限的,所以就调为免费的了。

访问网站

获取源码

Star ! Star ! Star !

应用截图

「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了

Android 的界面布局与 iOS 版基本保持一致,但均采用了原生的控件实现,这里就不放截图了。

获取

快速获取(自动识别系统):
http://a.app.qq.com/o/simple....

「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了

iOS 版

「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了

Android 版

已上架多个国内主流应用市场

「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了

一些小技巧

以下是我觉得比较值得分享的小技巧。

设计

对于我来说 UI 才是最头痛的,在没有设计师帮忙的情况下一切都得自己来了,下面是我在做 UI 时的一些经验。

  1. 选用成熟的配色方案

  2. 使用相同风格的图标(尽量使用比较全的图标库)

  3. 尽量保持简洁的界面设计(实用至上)

  4. 与系统风格保持一致

  5. 合适的字体尺寸以及边距等

  6. 设计要符合使用场景(很多使用侧边栏导航的应用就是反面教材)

我在项目里使用了 Material Design 提供的配色(Blue Grey)和图标,在两个系统上看起来都非常的和谐。

参考资源

第三方服务

使用第三方服务就是为了减少研发成本,但一定要慎重选用。下面介绍这个项目使用的一些第三方服务。

  • DaoCloud

    > 使用了 Docker 镜像构建,自有主机功能。
    > 当前项目已经完全实现自动部署。
  • 阿里云

    > 使用了 ECS 云主机
  • 七牛云

    > 使用了 云存储,免费 SSL 证书
  • 云巴

    > 使用了推送服务
  • GrowingIO

    > 用于统计
  • BugHD

    > 用于 Crash 收集
  • AdMob

    > 广告
    

API

使用了 REST 风格进行设计,每个接口所返回的数据结构均保持一致。

数据结构示例:

{
    "code": 0,
    "message": "...",
    "errors": [
        {
            "code": 10000,
            "field": "user",
            "message": "用户 不存在。"
        }
    ],
    "pagination": {
        "total": 10,
        "per_page": 10,
        "current_page": 1,
        "last_page": 1,
        "from": 1,
        "to": 10
    },
    "data": {
        ...
    }
}
  • code: 错误码

    > 当错误码不为 0 时代表发生错误。
  • message: 错误消息

  • errors: 错误列表

    > 当发生多个错误时返回错误列表,客户端根据列表返回的进行相应的处理。
  • pagination: 分页对象

    > 仅当 data 字段为数组时才返回。
"total": 总数
"per_page": 每页显示数量
"current_page": 当前页码
"last_page": 最后一页面页码
"from": 开始Id
"to": 结束Id
  • data: 数据(对象 / 数组)

    > 在实现时使用泛型对 data 进行处理。
    

泛型数据处理示例(Java):

public class Result<T> {

    private int code;
    private String message;
    private List<Error> errors;
    private Pagination pagination;
    private T data;

    public Result() {
    }

    public boolean isSuccessful() {
        return this.code == 0;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public List<Error> getErrors() {
        return errors;
    }

    public void setErrors(List<Error> errors) {
        this.errors = errors;
    }

    public Pagination getPagination() {
        return pagination;
    }

    public void setPagination(Pagination pagination) {
        this.pagination = pagination;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

// 当 data 为 User 时的示例
new Result<User>();
// 当 data 为 User 列表时的示例
new Result<ArrayList<User>>();

客户端

文章详情(WebView交互)

文章详情页面因为排版相关原因,并没有采用原生的开发方式,而是直接加载一个外部链接

外部链接:

https://daza.io/in-app/articles/{id}

因为 WebView 此时是没有保存用户状态的,所以需要将客户端的用户登录Token相关信息传递给 WebView,即在加载完毕后执行 JavaScript 代码写入。

Java:

// 开启 JavaScript 及 localStorage支持
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);

// 页面加载完毕后将相关数据保存到localStorage里。
public void onPageFinished(WebView view, String url) {
    String script = "javascript:";
    if (Auth.check()) {
        script += "localStorage.setItem('auth.id', '" + Auth.id() + "');\n";
        script += "localStorage.setItem('auth.user', '" + Auth.user().toJSONString() + "');\n";
        script += "localStorage.setItem('auth.jwt_token', '" + Auth.jwtToken().toJSONString() + "');\n";
    } else {
        script += "localStorage.clear();\n";
    }
    mWebView.loadUrl(script);
}

完整代码:
https://github.com/lijy91/daz...

Swift:

func webViewDidFinishLoad(webView: UIWebView) {
    if (!Auth.check()) {
        return
    }
    let standardUserDefaults = NSUserDefaults.standardUserDefaults()
    
    let authId = Auth.id();
    let authUser = standardUserDefaults.stringForKey("auth.user")
    let authJwtToken = standardUserDefaults.stringForKey("auth.jwt_token")
    var script = ""
    script += "localStorage.setItem('auth.id', '\(authId)');\n"
    script += "localStorage.setItem('auth.user', '\(authUser!)');\n"
    script += "localStorage.setItem('auth.jwt_token', '\(authJwtToken!)');\n"
    webView.stringByEvaluatingJavaScriptFromString(script)
}

完整代码:
https://github.com/lijy91/daz...

DeepLink支持

支持 DeepLink 后在WebView里直接可以通过自定义的 URL 来打开相应的页面,避免与 WebView 更麻烦的操作。

目前支持的链接:

daza://users/{user_id}
daza://topics/{topic_id}
daza://articles/{article_id}
daza://articles/{article_id}/comments

由于安卓的WebView不支持这个DeepLink,所以需要做一些处理:

public WebViewClient mWebViewClient = new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
       if (url.startsWith("daza://")) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse(url));
            startActivity(intent);
            return true;
        }
        return super.shouldOverrideUrlLoading(view, url);
    }
}

关于作者

目前正处于自由职业的状态,如果有API或者客户端的需求欢迎加我微信

「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了

如果你有什么好想法想告诉我,或者想加入讨论组(注明加入讨论组),请加我微信。

捐赠

「daza.io」个人全端开源项目的 Android & iOS 客户端都上线了

如果你觉得我的工作对你有帮助,那你可以为项目捐赠运营费用。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这