1 Star 1 Fork 1

Kj / todolist安卓版本reminder(提醒事项)

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-2.0

计划管理APP 功能

  1. 列表输入图片说明

  2. 上拉查询(数据保存在本地的sqlite数据库)输入图片说明

  3. 订制输入图片说明

  4. 编辑(开始时间、结束时间、文字描述、图片、附件、重要程度、完成情况)输入图片说明

  5. 左滑删除输入图片说明

  6. 提醒输入图片说明

  7. 显示/隐藏已完成输入图片说明输入图片说明

列表功能和数据库实现 数据库实现

  1. 定义数据类 使用了@Parcelize注解,这意味着它可以被自动地转换为Parcelable,从而可以在Intent中传递 imageurl和fileurl用于保存图片和附件在手机文件系统中的地址 @Parcelize data class detail( var title: String, //标题 val description: String? =null, //描述 var isChecked: Boolean = false, //是否完成 var begindate: Long? = null, var enddate: Long?=null, var begintime:Long?=null, var endtime:Long?=null, var priority:Int?=3, //默认无优先级 var id:Int?=null, var imageUrl:String?=null, var fileUrl:String?=null, ): Parcelable
  2. 创建数据库 数据库要实现查询,更新,添加,删除等功能 首先,它们都需要一个Context对象来创建DatabaseHelper,然后通过DatabaseHelper获取SQLiteDatabase对象。然后,它们都会根据需要对数据库进行查询、插入、更新或删除操作。 通过游标cursor实现查询功能,通过添加/覆盖contentvalues的值实现添加和更新功能

列表功能

  1. 使用recyclerview进行列表展示,首先定义两种type,第一种为正常的列表展示listitem,第二种为底部footview用于添加新事项,现在适配器的getitemviewtype中定义类型,数据的底部定义为footview。 如果视图类型是TYPE_ITEM,那么就从listitem.xml布局文件中创建一个新的视图;如果视图类型是TYPE_FOOTER,那么就从footview.xml布局文件中创建一个新的视图,并将这个视图传递给FooterViewHolder的构造函数,创建一个FooterViewHolder实例。 override fun getItemViewType(position: Int): Int { return if (position == DetailList.size) { TYPE_FOOTER } else { TYPE_ITEM } }

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return if (viewType == TYPE_ITEM) { val view = LayoutInflater.from(parent.context).inflate(R.layout.listitem, parent, false) ViewHolder(view) } else { val view = LayoutInflater.from(parent.context).inflate(R.layout.footview, parent, false) FooterViewHolder(view) } }2. 在onbindviewholder实现具体逻辑 3. 在mainactivity绑定recyclerview a. 首先获取数据库的所有数据,定义列表为线性布局 b. 注册一个用于处理从Activity返回的结果的回调。如果返回的结果码是RESULT_OK,则调用updateRecyclerView()函数更新RecyclerView。 c. 创建一个新的Recycleadapter,将查询到的数据、结果处理器和一个函数引用(用于设置FloatingActionButton的可见性)传递给它。 private fun setupRecyclerView() { val detailList = queryDataFromDatabase(this) val layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this) recyclerView.layoutManager = layoutManager

    // registerForActivityResult()方法的第一个参数是一个ActivityResultContract对象,这个对象负责创建Intent,
    // 当Activity结束时,会将Intent传递给onActivityResult()方法。
    resultLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (it.resultCode == RESULT_OK) {
                updateRecyclerView()
            }
        }
    // Recycleadapter是自己写的一个类,用于将数据和视图绑定,这里的adapter就是Recycleadapter的一个实例
    val adapter =
        Recycleadapter(detailList, resultLauncher, ::setFloatingActionButtonVisibility)
    recyclerView.adapter = adapter

    // ItemTouchHelper是一个辅助类,用于实现RecyclerView的拖拽和滑动删除功能
    val itemTouchHelperCallback =
        object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
            // onMove()方法用于实现拖拽功能,这里不需要,所以返回false
            override fun onMove(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                target: RecyclerView.ViewHolder
            ): Boolean {
                return false
            }

            // onSwiped()方法用于实现滑动删除功能
            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                val position = viewHolder.adapterPosition
                detailList.removeAt(position)
                recyclerView.adapter!!.notifyItemRemoved(position)
                // 删除数据库中的数据
                deleteDataFromDatabase(this@MainActivity, position)
            }
        }
    // 将ItemTouchHelper和RecyclerView绑定
    val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)
    itemTouchHelper.attachToRecyclerView(recyclerView)
}

查询功能

  1. 使用NestedScrollView将主界面的layout包裹起来,实现上滑搜索

  2. 设置NestedScrollView的滚动监听。当滚动位置(scrollY)小于等于0时,searchView可见,checkmore按钮不可见。当滚动位置大于100时,searchView不可见,checkmore按钮可见。这样可以在滚动时改变这两个控件的可见性。

  3. 设置searchView的查询文本监听。当查询文本改变时,调用updateRecyclerView(newText)函数来更新RecyclerView。这样可以实现搜索功能,即根据用户在searchView中输入的文本来过滤RecyclerView中的数据。

  4. updateRecyclerView调用setData(filteredList)方法将过滤后的数据设置到适配器中,设置showFooter属性的值为filter是否为空或空字符串,然后调用notifyDataSetChanged()方法通知RecyclerView数据已更改 private fun setupSearchView() { val nestedScrollView = findViewById(R.id.NestedScrollView) nestedScrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, _ -> if (scrollY <= 0) { searchView.visibility = View.VISIBLE checkmore.visibility = View.GONE } else if (scrollY > 100) { searchView.visibility = View.GONE checkmore.visibility = View.VISIBLE } })

     searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
         override fun onQueryTextSubmit(query: String?): Boolean {
             return false
         }
    
         override fun onQueryTextChange(newText: String?): Boolean {
             updateRecyclerView(newText)
             return false
         }
     })

    }

private fun updateRecyclerView(filter: String? = null) { val detailList = queryDataFromDatabase(this) // 如果filter不为空,就将detailList中的数据过滤,只保留包含filter的数据 // 用于查询功能,不为空就只保存输入框里的内容进行查询 val filteredList = if (filter != null) { detailList.filter { it.title.contains(filter) } } else { detailList } (recyclerView.adapter as Recycleadapter).apply { setData(filteredList) showFooter = filter.isNullOrEmpty() notifyDataSetChanged() } }订制功能/新建提醒事项与编辑功能

  1. 方法一:在列表最下方有footview,输入标题后点击最右边的图标,会进入到编辑页面;方法二:通过主页面的底部的悬浮按钮添加新事项

  2. 当点击addbutton时,执行以下操作: 获取addtext中的文本,作为标题。如果标题为空,显示一个Toast消息"标题不能为空"。如果DetailList中存在一个未完成的任务,其标题与输入的标题相同,显示一个Toast消息"标题已存在",并清空addtext。否则,创建一个新的detail对象,将其添加到数据库和DetailList中,通知RecyclerView插入了一个新的item,然后清空addtext。

  3. 点击事项右侧的图标后(或通过悬浮按钮),跳转到detail activity中,在detail activity中获取到detail实例后进行初始化 a. 如果有标题、开始日期、结束日期、开始时间、结束时间、图片、附件的,则初始化显示 b. 通过DatePickerDialog设置开始日期和结束日期的日期选择功能 c. 通过TimePickerDialog设置开始时间和结束时间的选择功能 d. 通过Spinner设置优先级的选择,首先创建一个array,存放要展示的列表,创建一个ArrayAdapter,用于将数组和spinner绑定,获取用户选择的优先级后更新数据库 // 设置优先级 val priority = findViewById(R.id.priority_spinner) // 创建一个ArrayAdapter,用于将数组和spinner绑定 val adapter_spinner = ArrayAdapter.createFromResource( this, R.array.planets_array, android.R.layout.simple_spinner_item ) // 设置下拉列表的风格 adapter_spinner.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) priority.adapter = adapter_spinner // 设置优先级的默认值 priority.setSelection(detail?.priority ?: 3)

     priority.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
         override fun onItemSelected(
             parent: AdapterView<*>,
             view: View?, position: Int, id: Long
         ) {
             parent as Spinner
             detail?.priority = position // 保存优先级
         }
    
         override fun onNothingSelected(parent: AdapterView<*>?) {
    
         }
     }    e. 添加图像与图片,添加附件。
     i. 用户选择拍照/选择图片,如果用户选择"拍照",会检查应用是否有相机权限。如果没有,会请求相机权限。如果有,会创建一个新的文件用于存储拍照后的图片,然后启动相机应用。拍照后的图片会保存到刚刚创建的文件中。  如果用户选择"选择图片",代码会启动一个用于选择图片的应用。用户可以从这个应用中选择一张图片,然后这个图片的URI会作为结果返回。 当这两个应用结束后,会调用当前Activity的onActivityResult方法,可以在这个方法中获取拍照后的图片或用户选择的图片。当用户点击添加附件按钮时,会创建一个新的Intent,并设置其动作为Intent.ACTION_GET_CONTENT。这意味着这个Intent将用于获取用户选择的文件。然后,设置Intent的类型为"/",这意味着这个Intent可以选择任何类型的文件。最后,使用startActivityForResult方法启动这个Intent,并等待用户选择文件。当用户选择了文件后,会调用当前Activity的onActivityResult方法,可以在这个方法中获取用户选择的文件。
     ii. 在用户选择完照片/拍照/上传完附件后,打开一个输入流,用于读取用户拍摄的照片。 创建一个新的文件,用于保存用户拍摄的照片。打开一个输出流,用于写入用户拍摄的照片。 将输入流的内容复制到输出流,即将用户拍摄的照片写入到文件中。关闭输入流和输出流。

if (requestCode == 1 && resultCode == RESULT_OK) { val userimage = findViewById(R.id.userimage) // 从文件中读取图片 userimage.setImageURI(imageUri) userimage.visibility = View.VISIBLE

        // 随机生成文件名
        val filename = "userimage${System.currentTimeMillis()}.jpg"
        val inputStream = contentResolver.openInputStream(imageUri) //获取图片的输入流
        val file = File(filesDir, filename) //创建文件
        val outputStream = FileOutputStream(file) //创建输出流
        inputStream?.copyTo(outputStream) //将输入流复制到输出流
        inputStream?.close() //关闭输入流
        outputStream.close() //关闭输出流
        // 将文件名保存
        val detail = intent.getParcelableExtra<detail>("detail")
        if (detail != null) {
            detail.imageUrl = filename
            // 更新数据库
            val id = queryIdFromDatabase(this, detail)
            updateDatabase(this, detail, id)
        }
    }        

提醒功能

  1. 使用worker进行后台处理,提醒事项的即将开始,和结束。首先从数据库中查询所有的计划,然后过滤出即将开始和已经结束的计划。对于每个即将开始的计划,发送一个"你的计划即将开始"的通知;对于每个已经结束的计划,发送一个"你的计划已经结束,请输入完成情况"的通知。

  2. 在mainactivity启动后台任务 override fun doWork(): Result { val allPlans = queryDataFromDatabase(applicationContext) val currenttime = System.currentTimeMillis()

     val upcomingPlans = allPlans.filter { plan ->
         plan.begindate?.let { it != 0.toLong() && it - currenttime < 86400000 && it > currenttime && !plan.isChecked }
             ?: false
     }
    
     val overPlans = allPlans.filter { plan ->
         plan.enddate?.let { it != 0.toLong() && it < currenttime && !plan.isChecked } ?: false
     }
    
     upcomingPlans.forEach {
         showNotification(
             applicationContext,
             it,
             "你的${it.title}计划即将开始"
         )
     }
     overPlans.forEach {
         showNotification(
             applicationContext,
             it,
             "你的${it.title}计划已经结束,请输入完成情况"
         )
     }
    
     return Result.success()

    }左滑删除 创建一个ItemTouchHelper.SimpleCallback对象,这个对象定义了当用户在RecyclerView的item上执行拖拽或滑动操作时应该执行的操作。当一个item被滑动时,会获取这个item的位置,然后从detailList中移除这个item,通知适配器这个item已经被移除,并从数据库中删除这个item private fun setupRecyclerView() { val detailList = queryDataFromDatabase(this) val layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this) recyclerView.layoutManager = layoutManager

     // ItemTouchHelper是一个辅助类,用于实现RecyclerView的拖拽和滑动删除功能
     val itemTouchHelperCallback =
         object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
             // onMove()方法用于实现拖拽功能,这里不需要,所以返回false
             override fun onMove(
                 recyclerView: RecyclerView,
                 viewHolder: RecyclerView.ViewHolder,
                 target: RecyclerView.ViewHolder
             ): Boolean {
                 return false
             }
    
             // onSwiped()方法用于实现滑动删除功能
             override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                 val position = viewHolder.adapterPosition
                 detailList.removeAt(position)
                 recyclerView.adapter!!.notifyItemRemoved(position)
                 // 删除数据库中的数据
                 deleteDataFromDatabase(this@MainActivity, position)
             }
         }
     // 将ItemTouchHelper和RecyclerView绑定
     val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback)
     itemTouchHelper.attachToRecyclerView(recyclerView)

    }展示/隐藏已完成 当用户点击"showfinished"菜单项时,会切换clickshowfinished的值,然后获取RecyclerView的适配器,并将适配器的setShowFinished属性设置为clickshowfinished的值。然后,根据clickshowfinished的值来更新"showfinished"菜单项的标题。最后,通知适配器数据已更改 // mainactivity private fun setupCheckMoreButton() { var clickshowfinished = false var showFinishedTitle = "显示已完成的任务"

     checkmore.setOnClickListener {
         val popupMenu = PopupMenu(this, it)
         popupMenu.menuInflater.inflate(R.menu.popup_menu, popupMenu.menu)
         popupMenu.menu.findItem(R.id.showfinished).title = showFinishedTitle
         popupMenu.setOnMenuItemClickListener { menuItem ->
             when (menuItem.itemId) {
                 R.id.showfinished -> {
                     clickshowfinished = !clickshowfinished
                     val adapter = recyclerView.adapter as Recycleadapter
                     adapter.setShowFinished = clickshowfinished
                     showFinishedTitle =
                         if (clickshowfinished) "隐藏已完成的任务" else "显示已完成的任务"
                     adapter.notifyDataSetChanged()
                     true
                 }
    
                 else -> false
             }
         }
         popupMenu.show()
     }

    }

//adapter override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is ViewHolder) { val detail = DetailList[position] // 默认隐藏已经完成的item,只有点击查看已完成的任务才会显示 if (setShowFinished) { holder.itemView.visibility = View.VISIBLE holder.itemView.layoutParams = RecyclerView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) //设置布局参数,宽度为match_parent,高度为wrap_content }

木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

以苹果提醒事项为原型开发的安卓版提醒事项,实现了上拉查询,左滑删除,即将开始事项与已经结束事项提醒,展示/隐藏已完成功能等。请开发一个Android的计划管理APP。要求对计划进行全面的管理,包括列表、查询、订制、编辑和删除。一个计划包括开始时间、结束时间、文字描述、图片、附件、重要程度、完成情况等。APP会提示用户即将开始的计划,超过计划结束时间后要求用户输入完成情况。计划可以提前完成,也可以延 展开 收起
Kotlin
MulanPSL-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/kjiskyz/reminder.git
git@gitee.com:kjiskyz/reminder.git
kjiskyz
reminder
todolist安卓版本reminder(提醒事项)
master

搜索帮助