From 63ad60dd2a32f9af8d9d64d87516f5b2f3b436bb Mon Sep 17 00:00:00 2001 From: aiyingying <2454089983@qq.com> Date: Sat, 21 Jun 2025 23:09:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dairy/src/main/AndroidManifest.xml | 28 ++ src/dairy/src/main/ic_launcher-playstore.png | Bin 0 -> 10145 bytes .../src/main/java/com/app/diary/Mapp.java | 61 ++++ .../diary/adapter/DiaryRecyclerAdapter.java | 114 +++++++ .../java/com/app/diary/bean/BaseBean.java | 9 + .../java/com/app/diary/bean/Constant.java | 7 + .../main/java/com/app/diary/bean/Diary.java | 96 ++++++ .../com/app/diary/data/DiaryDataSource.java | 42 +++ .../diary/data/impl/DiaryDataSourceImpl.java | 58 ++++ .../diary/room/converter/DateConverter.java | 19 ++ .../java/com/app/diary/room/dao/DiaryDao.java | 49 +++ .../app/diary/room/database/AppDatabase.java | 17 + .../java/com/app/diary/ui/BaseActivity.java | 33 ++ .../java/com/app/diary/ui/BaseFragment.java | 38 +++ .../java/com/app/diary/ui/BaseViewModel.java | 35 ++ .../com/app/diary/ui/DiaryBrowseFragment.java | 187 +++++++++++ .../app/diary/ui/DiaryBrowseViewModel.java | 86 +++++ .../com/app/diary/ui/DiaryEditFragment.java | 315 ++++++++++++++++++ .../com/app/diary/ui/DiaryEditViewModel.java | 145 ++++++++ .../com/app/diary/ui/DiaryListFragment.java | 125 +++++++ .../com/app/diary/ui/DiaryListViewModel.java | 62 ++++ .../java/com/app/diary/ui/IndexFragment.java | 81 +++++ .../java/com/app/diary/ui/MainActivity.java | 21 ++ .../java/com/app/diary/utils/AppUtils.java | 28 ++ .../java/com/app/diary/utils/SizeUtils.java | 42 +++ .../java/com/app/diary/utils/TimeUtils.java | 50 +++ .../java/com/app/diary/utils/ToastUtils.java | 19 ++ .../rxjava/CompletableObserverUtils.java | 41 +++ .../utils/rxjava/SingleObserverUtils.java | 41 +++ src/dairy/src/main/res/drawable/bg_edit.xml | 28 ++ src/dairy/src/main/res/drawable/btn_diary.xml | 31 ++ src/dairy/src/main/res/drawable/btn_full.xml | 28 ++ .../src/main/res/drawable/btn_hollow.xml | 31 ++ .../src/main/res/layout/activity_main.xml | 15 + .../main/res/layout/fragment_diary_browse.xml | 111 ++++++ .../main/res/layout/fragment_diary_edit.xml | 95 ++++++ .../main/res/layout/fragment_diary_list.xml | 32 ++ .../src/main/res/layout/fragment_index.xml | 76 +++++ .../main/res/layout/item_recycler_diary.xml | 71 ++++ .../src/main/res/menu/menu_diary_browse.xml | 16 + .../src/main/res/menu/menu_diary_create.xml | 10 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1215 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 1147 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3164 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 970 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 743 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2096 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 1871 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 1583 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 4718 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 2814 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 2599 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 7267 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 4020 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 3736 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 10848 bytes .../src/main/res/navigation/nav_main.xml | 64 ++++ src/dairy/src/main/res/values/colors.xml | 17 + src/dairy/src/main/res/values/strings.xml | 3 + src/dairy/src/main/res/values/themes.xml | 32 ++ src/dairy/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 ++ 64 files changed, 2551 insertions(+) create mode 100644 src/dairy/src/main/AndroidManifest.xml create mode 100644 src/dairy/src/main/ic_launcher-playstore.png create mode 100644 src/dairy/src/main/java/com/app/diary/Mapp.java create mode 100644 src/dairy/src/main/java/com/app/diary/adapter/DiaryRecyclerAdapter.java create mode 100644 src/dairy/src/main/java/com/app/diary/bean/BaseBean.java create mode 100644 src/dairy/src/main/java/com/app/diary/bean/Constant.java create mode 100644 src/dairy/src/main/java/com/app/diary/bean/Diary.java create mode 100644 src/dairy/src/main/java/com/app/diary/data/DiaryDataSource.java create mode 100644 src/dairy/src/main/java/com/app/diary/data/impl/DiaryDataSourceImpl.java create mode 100644 src/dairy/src/main/java/com/app/diary/room/converter/DateConverter.java create mode 100644 src/dairy/src/main/java/com/app/diary/room/dao/DiaryDao.java create mode 100644 src/dairy/src/main/java/com/app/diary/room/database/AppDatabase.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/BaseActivity.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/BaseFragment.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/BaseViewModel.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseFragment.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseViewModel.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/DiaryEditFragment.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/DiaryEditViewModel.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/DiaryListFragment.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/DiaryListViewModel.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/IndexFragment.java create mode 100644 src/dairy/src/main/java/com/app/diary/ui/MainActivity.java create mode 100644 src/dairy/src/main/java/com/app/diary/utils/AppUtils.java create mode 100644 src/dairy/src/main/java/com/app/diary/utils/SizeUtils.java create mode 100644 src/dairy/src/main/java/com/app/diary/utils/TimeUtils.java create mode 100644 src/dairy/src/main/java/com/app/diary/utils/ToastUtils.java create mode 100644 src/dairy/src/main/java/com/app/diary/utils/rxjava/CompletableObserverUtils.java create mode 100644 src/dairy/src/main/java/com/app/diary/utils/rxjava/SingleObserverUtils.java create mode 100644 src/dairy/src/main/res/drawable/bg_edit.xml create mode 100644 src/dairy/src/main/res/drawable/btn_diary.xml create mode 100644 src/dairy/src/main/res/drawable/btn_full.xml create mode 100644 src/dairy/src/main/res/drawable/btn_hollow.xml create mode 100644 src/dairy/src/main/res/layout/activity_main.xml create mode 100644 src/dairy/src/main/res/layout/fragment_diary_browse.xml create mode 100644 src/dairy/src/main/res/layout/fragment_diary_edit.xml create mode 100644 src/dairy/src/main/res/layout/fragment_diary_list.xml create mode 100644 src/dairy/src/main/res/layout/fragment_index.xml create mode 100644 src/dairy/src/main/res/layout/item_recycler_diary.xml create mode 100644 src/dairy/src/main/res/menu/menu_diary_browse.xml create mode 100644 src/dairy/src/main/res/menu/menu_diary_create.xml create mode 100644 src/dairy/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 src/dairy/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 src/dairy/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/dairy/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 src/dairy/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 src/dairy/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/dairy/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 src/dairy/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 src/dairy/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/dairy/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 src/dairy/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 src/dairy/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/dairy/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 src/dairy/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 src/dairy/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/dairy/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 src/dairy/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 src/dairy/src/main/res/navigation/nav_main.xml create mode 100644 src/dairy/src/main/res/values/colors.xml create mode 100644 src/dairy/src/main/res/values/strings.xml create mode 100644 src/dairy/src/main/res/values/themes.xml create mode 100644 src/dairy/src/main/res/xml/backup_rules.xml create mode 100644 src/dairy/src/main/res/xml/data_extraction_rules.xml diff --git a/src/dairy/src/main/AndroidManifest.xml b/src/dairy/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d2bbc47 --- /dev/null +++ b/src/dairy/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/ic_launcher-playstore.png b/src/dairy/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..83d1d54e7dff571e461937e54973a81b5adce0c2 GIT binary patch literal 10145 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 { + + private List list;//日记本列表数据 + private LayoutInflater inflater;//布局填充器 + + private OnItemClickListener onItemClickListener;//点击事件 + + @Override + public int getItemCount() { + return list == null ? 0 : list.size(); + } + + @NonNull + @Override + public DiaryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + //布局填充器可以重复使用,所以这里使用懒加载的方式创建 + if (inflater == null) { + inflater = LayoutInflater.from(parent.getContext()); + } + View view = inflater.inflate(R.layout.item_recycler_diary, parent, false); + return new DiaryViewHolder(view); + } + + @SuppressLint("RecyclerView") + @Override + public void onBindViewHolder(@NonNull DiaryViewHolder holder, int position) { + Diary diary = list.get(position); + + holder.rowLinearLayout.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(diary, position); + } + } + + }); + holder.dateTextView.setText(new SimpleDateFormat("yyyy年MM月dd日").format(diary.getDate())); + holder.weatherTextView.setText(diary.getWeather()); + holder.titleTextView.setText(diary.getTitle()); + holder.wordCountTextView.setText("字数" + diary.getContent().length() + "个"); + holder.updateTimeTextView.setText("编辑于" + TimeUtils.getSimpleTime(diary.getUpdateTime().getTime())); + } + + /** + * 设置新数据 + */ + public void setNewData(@Nullable List list) { + this.list = list; + notifyDataSetChanged(); + } + + /** + * 设置单个日记的点击事件 + */ + public void setOnItemClickListener(@Nullable OnItemClickListener listener) { + this.onItemClickListener = listener; + } + + /** + * 视图固定器 + */ + public class DiaryViewHolder extends RecyclerView.ViewHolder { + + LinearLayout rowLinearLayout;//行布局控件 + TextView dateTextView;//日记时间文本控件 + TextView weatherTextView;//天气文本控件 + TextView titleTextView;//标题文本控件 + TextView wordCountTextView;//字数文本控件 + TextView updateTimeTextView;//修改时间文本控件 + + public DiaryViewHolder(@NonNull View itemView) { + super(itemView); + rowLinearLayout = itemView.findViewById(R.id.row_linearLayout); + dateTextView = itemView.findViewById(R.id.date_textView); + weatherTextView = itemView.findViewById(R.id.weather_textView); + titleTextView = itemView.findViewById(R.id.title_textView); + wordCountTextView = itemView.findViewById(R.id.wordCount_textView); + updateTimeTextView = itemView.findViewById(R.id.updateTime_textView); + } + + } + + /** + * 日记的点击事件 + */ + public interface OnItemClickListener { + + void onItemClick(Diary diary, int position); + + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/bean/BaseBean.java b/src/dairy/src/main/java/com/app/diary/bean/BaseBean.java new file mode 100644 index 0000000..d9ed6dd --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/bean/BaseBean.java @@ -0,0 +1,9 @@ +package com.app.diary.bean; + +import java.io.Serializable; + +/** + * 数据的基础类 + */ +public class BaseBean implements Serializable { +} diff --git a/src/dairy/src/main/java/com/app/diary/bean/Constant.java b/src/dairy/src/main/java/com/app/diary/bean/Constant.java new file mode 100644 index 0000000..80656bc --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/bean/Constant.java @@ -0,0 +1,7 @@ +package com.app.diary.bean; + +public class Constant { + + public static String DATA_CHANGE = "data_change";//数据是否改变 + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/bean/Diary.java b/src/dairy/src/main/java/com/app/diary/bean/Diary.java new file mode 100644 index 0000000..8137d79 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/bean/Diary.java @@ -0,0 +1,96 @@ +package com.app.diary.bean; + +import androidx.annotation.NonNull; +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import java.util.Date; + +/** + * 日记 + */ +@Entity(tableName = "diary") +public class Diary extends BaseBean { + + @NonNull + @PrimaryKey(autoGenerate = true) + private Long id;//主键 + + @NonNull + private Date date;//日期 + + @NonNull + private String weather;//天气 + + @NonNull + private String title;//标题 + + @NonNull + private String content;//内容 + + @NonNull + @ColumnInfo(name = "create_time") + private Date createTime;//创建时间 + + @NonNull + @ColumnInfo(name = "update_time") + private Date updateTime;//修改时间 + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getWeather() { + return weather; + } + + public void setWeather(String weather) { + this.weather = weather; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/data/DiaryDataSource.java b/src/dairy/src/main/java/com/app/diary/data/DiaryDataSource.java new file mode 100644 index 0000000..b780a23 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/data/DiaryDataSource.java @@ -0,0 +1,42 @@ +package com.app.diary.data; + +import androidx.annotation.NonNull; + +import com.app.diary.bean.Diary; + +import java.util.List; + +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +/** + * 日记本数据源 + */ +public interface DiaryDataSource { + + /** + * 新增一条日记本 + */ + Completable insertDiary(@NonNull Diary diary); + + /** + * 删除一条日记本 + */ + Completable deleteDiary(long diaryId); + + /** + * 修改一条日记本 + */ + Completable updateDiary(@NonNull Diary diary); + + /** + * 根据主键查询日记本 + */ + Single selectOne(long diaryId); + + /** + * 查找日记本列表 + */ + Single> selectList(); + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/data/impl/DiaryDataSourceImpl.java b/src/dairy/src/main/java/com/app/diary/data/impl/DiaryDataSourceImpl.java new file mode 100644 index 0000000..71d19b1 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/data/impl/DiaryDataSourceImpl.java @@ -0,0 +1,58 @@ +package com.app.diary.data.impl; + +import androidx.annotation.NonNull; + +import com.app.diary.bean.Diary; +import com.app.diary.data.DiaryDataSource; +import com.app.diary.room.database.AppDatabase; + +import java.util.Date; +import java.util.List; + +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +/** + * 日志数据源的实现类 + */ +public class DiaryDataSourceImpl implements DiaryDataSource { + + @NonNull + private AppDatabase appDatabase; + + public DiaryDataSourceImpl(@NonNull AppDatabase appDatabase) { + this.appDatabase = appDatabase; + } + + @Override + public Completable insertDiary(@NonNull Diary diary) { + Date now = new Date(); + diary.setCreateTime(now); + diary.setUpdateTime(now); + return appDatabase.diaryDao().insert(diary); + } + + @Override + public Completable deleteDiary(long diaryId) { + Diary diary = new Diary(); + diary.setId(diaryId); + return appDatabase.diaryDao().delete(diary); + } + + @Override + public Completable updateDiary(@NonNull Diary diary) { + diary.setUpdateTime(new Date()); + return appDatabase.diaryDao().update(diary); + } + + @Override + public Single selectOne(long diaryId) { + return appDatabase.diaryDao().getOne(diaryId); + } + + @Override + public Single> selectList() { + return appDatabase.diaryDao().getList(); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/room/converter/DateConverter.java b/src/dairy/src/main/java/com/app/diary/room/converter/DateConverter.java new file mode 100644 index 0000000..16a42ad --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/room/converter/DateConverter.java @@ -0,0 +1,19 @@ +package com.app.diary.room.converter; + +import androidx.room.TypeConverter; + +import java.util.Date; + +public class DateConverter { + + @TypeConverter + public static Date timestampToDate(Long timestamp) { + return timestamp == null ? null : new Date(timestamp); + } + + @TypeConverter + public static Long dateToTimestamp(Date date) { + return date == null ? null : date.getTime(); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/room/dao/DiaryDao.java b/src/dairy/src/main/java/com/app/diary/room/dao/DiaryDao.java new file mode 100644 index 0000000..b9d8559 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/room/dao/DiaryDao.java @@ -0,0 +1,49 @@ +package com.app.diary.room.dao; + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.Query; +import androidx.room.Update; + +import com.app.diary.bean.Diary; + +import java.util.List; + +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +@Dao +public interface DiaryDao { + + /** + * 新增日记 + */ + @Insert + Completable insert(Diary diary); + + /** + * 删除日记 + */ + @Delete + Completable delete(Diary diary); + + /** + * 修改日记 + */ + @Update + Completable update(Diary diary); + + /** + * 获取日记列表 + */ + @Query("SELECT * FROM diary ORDER BY date DESC") + Single> getList(); + + /** + * 获取日记详情 + */ + @Query("SELECT * FROM diary WHERE id = (:diaryId)") + Single getOne(long diaryId); + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/room/database/AppDatabase.java b/src/dairy/src/main/java/com/app/diary/room/database/AppDatabase.java new file mode 100644 index 0000000..1553ff3 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/room/database/AppDatabase.java @@ -0,0 +1,17 @@ +package com.app.diary.room.database; + +import androidx.room.Database; +import androidx.room.RoomDatabase; +import androidx.room.TypeConverters; + +import com.app.diary.bean.Diary; +import com.app.diary.room.converter.DateConverter; +import com.app.diary.room.dao.DiaryDao; + +@Database(entities = {Diary.class}, version = 2) +@TypeConverters({DateConverter.class}) +public abstract class AppDatabase extends RoomDatabase { + + public abstract DiaryDao diaryDao(); + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/BaseActivity.java b/src/dairy/src/main/java/com/app/diary/ui/BaseActivity.java new file mode 100644 index 0000000..47bcf19 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/BaseActivity.java @@ -0,0 +1,33 @@ +package com.app.diary.ui; + +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.navigation.ActivityNavigator; + +/** + * 页面的基础类 + */ +public class BaseActivity extends AppCompatActivity { + + /** + * 设置菜单点击 + */ + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + //设置标题栏的返回键点击事件 + if (item.getItemId() == android.R.id.home) { + //页面返回 + getOnBackPressedDispatcher().onBackPressed(); + } + return super.onOptionsItemSelected(item); + } + + @Override + public void finish() { + super.finish(); + ActivityNavigator.applyPopAnimationsToPendingTransition(this); + } + +} diff --git a/src/dairy/src/main/java/com/app/diary/ui/BaseFragment.java b/src/dairy/src/main/java/com/app/diary/ui/BaseFragment.java new file mode 100644 index 0000000..8d462b4 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/BaseFragment.java @@ -0,0 +1,38 @@ +package com.app.diary.ui; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; + +/** + * Fragment的基础类 + */ +public class BaseFragment extends Fragment { + + /** + * 获取导航控制器 + */ + protected NavController getNavController() { + return NavHostFragment.findNavController(this); + } + + /** + * 初始化标题栏 + * + * @param toolbar 标题栏 + * @param showBack 是否显示返回键 + */ + protected void initSupportActionBar(@NonNull Toolbar toolbar, boolean showBack) { + if (getActivity() != null && (getActivity() instanceof AppCompatActivity)) { + if (toolbar.getTitle() == null) { + toolbar.setTitle(""); + } + ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); + ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(showBack); + } + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/BaseViewModel.java b/src/dairy/src/main/java/com/app/diary/ui/BaseViewModel.java new file mode 100644 index 0000000..0a06636 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/BaseViewModel.java @@ -0,0 +1,35 @@ +package com.app.diary.ui; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; + +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import io.reactivex.rxjava3.disposables.Disposable; + +public class BaseViewModel extends AndroidViewModel { + + protected boolean loaded;//是否已加载过数据 + private CompositeDisposable compositeDisposable; + + public BaseViewModel(@NonNull Application application) { + super(application); + } + + public void addDisposable(@NonNull Disposable disposable) { + if (compositeDisposable == null) { + compositeDisposable = new CompositeDisposable(); + } + compositeDisposable.add(disposable); + } + + @Override + protected void onCleared() { + super.onCleared(); + if (compositeDisposable != null) { + compositeDisposable.clear(); + } + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseFragment.java b/src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseFragment.java new file mode 100644 index 0000000..7859676 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseFragment.java @@ -0,0 +1,187 @@ +package com.app.diary.ui; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.view.MenuProvider; +import androidx.core.widget.NestedScrollView; +import androidx.lifecycle.Observer; +import androidx.lifecycle.SavedStateHandle; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavBackStackEntry; + +import com.app.diary.R; +import com.app.diary.bean.Constant; +import com.app.diary.bean.Diary; + +import java.text.SimpleDateFormat; + +/** + * 日志浏览 + */ +public class DiaryBrowseFragment extends BaseFragment { + + private Toolbar toolbar;//标题栏控件 + private NestedScrollView scrollView;//滑动控件 + private TextView dateTextView;//日期文本控件 + private TextView weekTextView;//星期文本控件 + private TextView weatherTextView;//天气文本控件 + private TextView titleTextView;//标题文本控件 + private TextView contentTextView;//内容文本控件 + private TextView errorTextView;//错误文本控件 + + private long diaryId;//日记主键 + private DiaryBrowseViewModel diaryBrowseViewModel; + private SavedStateHandle savedStateHandle; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + NavBackStackEntry navBackStackEntry = getNavController().getCurrentBackStackEntry(); + navBackStackEntry.getSavedStateHandle().getLiveData(Constant.DATA_CHANGE).observe(navBackStackEntry, new Observer() { + + @Override + public void onChanged(Object dataChanged) { + if ((boolean) dataChanged) { + if (savedStateHandle != null) { + savedStateHandle.set(Constant.DATA_CHANGE, true); + } + if (diaryBrowseViewModel != null) { + diaryBrowseViewModel.loadData(diaryId, false); + } + } + } + + }); + + initData(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_diary_browse, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + diaryBrowseViewModel = new ViewModelProvider(this).get(DiaryBrowseViewModel.class); + savedStateHandle = getNavController().getPreviousBackStackEntry().getSavedStateHandle(); + initView(view); + setView(); + diaryBrowseViewModel.loadData(diaryId, true); + } + + /** + * 初始化数据 + */ + private void initData() { + diaryId = DiaryBrowseFragmentArgs.fromBundle(getArguments()).getDiaryId(); + } + + /** + * 初始化控件 + */ + private void initView(@NonNull View view) { + toolbar = view.findViewById(R.id.toolbar); + scrollView = view.findViewById(R.id.scrollView); + dateTextView = view.findViewById(R.id.date_textView); + weekTextView = view.findViewById(R.id.week_textView); + weatherTextView = view.findViewById(R.id.weather_textView); + titleTextView = view.findViewById(R.id.title_textView); + contentTextView = view.findViewById(R.id.content_textView); + errorTextView = view.findViewById(R.id.error_textView); + } + + /** + * 设置控件 + */ + private void setView() { + //将标题栏关联到页面 + initSupportActionBar(toolbar, true); + + //设置菜单 + requireActivity().addMenuProvider(new MenuProvider() { + + @Override + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { + menuInflater.inflate(R.menu.menu_diary_browse, menu); + } + + @Override + public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { + if (menuItem.getItemId() == R.id.action_update) {//跳转到日记编辑页面 + getNavController().navigate(DiaryBrowseFragmentDirections.diaryEditAction(diaryId)); + + } else if (menuItem.getItemId() == R.id.action_delete) {//弹窗提示是否删除日记 + //弹窗提示是否删除日记 + new AlertDialog.Builder(getContext()).setTitle("提示").setMessage("确定要删除日记吗?").setPositiveButton("确定", new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + diaryBrowseViewModel.deleteDiary(diaryId).observe(getViewLifecycleOwner(), new Observer() { + + @Override + public void onChanged(Boolean success) { + if (success) { + savedStateHandle.set(Constant.DATA_CHANGE, true); + getNavController().navigateUp(); + } + } + + }); + } + + }).setNegativeButton("取消", null).create().show(); + } + return false; + } + + }, getViewLifecycleOwner()); + + //观察日记数据并设置日记控件 + diaryBrowseViewModel.getDiaryLiveData().observe(getViewLifecycleOwner(), new Observer() { + + @Override + public void onChanged(Diary diary) { + setDiaryView(diary); + } + + }); + } + + /** + * 设置日记控件 + */ + private void setDiaryView(@Nullable Diary diary) { + if (diary == null) { + errorTextView.setText("未找到该日记"); + errorTextView.setVisibility(View.VISIBLE); + scrollView.setVisibility(View.GONE); + + } else { + errorTextView.setVisibility(View.GONE); + scrollView.setVisibility(View.VISIBLE); + dateTextView.setText(new SimpleDateFormat("yyyy年MM月dd日").format(diary.getDate())); + weekTextView.setText(new SimpleDateFormat("EEEE").format(diary.getDate())); + weatherTextView.setText(diary.getWeather()); + titleTextView.setText(diary.getTitle()); + //处理内容格式,开头空两格,换行空两格 + String content = "\t\t\t\t" + diary.getContent().replace("\n", "\n\t\t\t\t"); + contentTextView.setText(content); + } + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseViewModel.java b/src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseViewModel.java new file mode 100644 index 0000000..bc56588 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/DiaryBrowseViewModel.java @@ -0,0 +1,86 @@ +package com.app.diary.ui; + +import android.app.Application; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.app.diary.Mapp; +import com.app.diary.bean.Diary; +import com.app.diary.data.DiaryDataSource; +import com.app.diary.utils.ToastUtils; +import com.app.diary.utils.rxjava.CompletableObserverUtils; +import com.app.diary.utils.rxjava.SingleObserverUtils; + +import androidx.annotation.NonNull; + +import io.reactivex.rxjava3.observers.DisposableCompletableObserver; +import io.reactivex.rxjava3.observers.DisposableSingleObserver; + +public class DiaryBrowseViewModel extends BaseViewModel { + + private MutableLiveData diaryLiveData = new MutableLiveData<>();//日记的数据容器 + + private DiaryDataSource diaryDataSource;//日记数据来源 + + public DiaryBrowseViewModel(@NonNull Application application) { + super(application); + diaryDataSource = ((Mapp) application).getDiaryDataSource(); + } + + /** + * 删除日记 + */ + public LiveData deleteDiary(long diaryId) { + MutableLiveData liveData = new MutableLiveData<>(); + diaryDataSource.deleteDiary(diaryId).compose(CompletableObserverUtils.applyUIScheduler(this)).subscribe(new DisposableCompletableObserver() { + + @Override + public void onComplete() { + ToastUtils.showShort("删除成功"); + liveData.setValue(true); + } + + @Override + public void onError(@NonNull Throwable e) { + ToastUtils.showShort("删除失败, 原因:" + e.getMessage()); + liveData.setValue(false); + } + + }); + return liveData; + } + + /** + * 获取日记的数据容器 + */ + public LiveData getDiaryLiveData() { + return diaryLiveData; + } + + /** + * 加载数据 + */ + public void loadData(long diaryId, boolean lazy) { + if (lazy && loaded) { + return; + } + loaded = true; + + diaryDataSource.selectOne(diaryId).compose(SingleObserverUtils.applyUIScheduler(this)).subscribe(new DisposableSingleObserver() { + + @Override + public void onSuccess(@NonNull Diary diary) { + diaryLiveData.setValue(diary); + } + + @Override + public void onError(@NonNull Throwable e) { + ToastUtils.showShort("获取失败, 原因:" + e.getMessage()); + diaryLiveData.setValue(null); + } + + }); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/DiaryEditFragment.java b/src/dairy/src/main/java/com/app/diary/ui/DiaryEditFragment.java new file mode 100644 index 0000000..8eff2db --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/DiaryEditFragment.java @@ -0,0 +1,315 @@ +package com.app.diary.ui; + +import android.app.DatePickerDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.view.MenuProvider; +import androidx.lifecycle.Observer; +import androidx.lifecycle.SavedStateHandle; +import androidx.lifecycle.ViewModelProvider; + +import com.app.diary.R; +import com.app.diary.bean.Constant; +import com.app.diary.bean.Diary; +import com.app.diary.utils.ToastUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; + +/** + * 编辑日志 + */ +public class DiaryEditFragment extends BaseFragment { + + private static final String[] WEATHERS = new String[]{"晴天", "雨天", "雪天"}; + + private Toolbar toolbar;//标题栏控件 + private TextView dateTextView;//日期文本控件 + private TextView weatherTextView;//天气文本控件 + private EditText titleEditText;//标题输入框控件 + private EditText contentEditText;//内容输入框控件 + + private DatePickerDialog datePickerDialog;//日期选择对话框 + private AlertDialog weatherPickerDialog;//天气选择对话框 + + private long diaryId;//日记主键 + private DiaryEditViewModel diaryEditViewModel; + private SavedStateHandle savedStateHandle; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initData(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_diary_edit, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + diaryEditViewModel = new ViewModelProvider(this).get(DiaryEditViewModel.class); + savedStateHandle = getNavController().getPreviousBackStackEntry().getSavedStateHandle(); + initView(view); + setView(); + if (diaryId > 0) { + diaryEditViewModel.loadData(diaryId, true); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + //在页面销毁前,关闭日期选择对话框,防止对话框未关闭报错 + if (datePickerDialog != null && datePickerDialog.isShowing()) { + datePickerDialog.dismiss(); + } + if (weatherPickerDialog != null && weatherPickerDialog.isShowing()) { + weatherPickerDialog.dismiss(); + } + } + + /** + * 初始化数据 + */ + private void initData() { + diaryId = DiaryEditFragmentArgs.fromBundle(getArguments()).getDiaryId(); + } + + /** + * 初始化控件 + */ + private void initView(@NonNull View view) { + toolbar = view.findViewById(R.id.toolbar); + dateTextView = view.findViewById(R.id.date_textView); + weatherTextView = view.findViewById(R.id.weather_textView); + titleEditText = view.findViewById(R.id.title_editText); + contentEditText = view.findViewById(R.id.content_editText); + } + + /** + * 设置控件 + */ + private void setView() { + //将标题栏关联到页面 + initSupportActionBar(toolbar, true); + + //添加菜单 + requireActivity().addMenuProvider(new MenuProvider() { + + @Override + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { + menuInflater.inflate(R.menu.menu_diary_create, menu); + } + + @Override + public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { + if (menuItem.getItemId() == R.id.action_save) {//保存日记 + saveDiary(); + } + return false; + } + + }, getViewLifecycleOwner()); + + //设置日期文本的点击事件 + dateTextView.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + showDatePickerDialog(); + } + + }); + //设置天气文本的点击事件 + weatherTextView.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + showWeatherPickerDialog(); + } + + }); + + if (diaryId > 0) {//修改日记 + diaryEditViewModel.getDiaryLiveData().observe(getViewLifecycleOwner(), new Observer() { + + @Override + public void onChanged(Diary diary) { + if (diary == null) { + ToastUtils.showShort("未找到该日记"); + getNavController().navigateUp(); + + } else { + //设置该日记数据 + toolbar.setTitle("修改日记"); + dateTextView.setText(new SimpleDateFormat("yyyy年MM月dd日").format(diary.getDate())); + weatherTextView.setText(diary.getWeather()); + titleEditText.setText(diary.getTitle()); + contentEditText.setText(diary.getContent()); + } + } + + }); + + } else {//创建日记 + //设置默认值 + toolbar.setTitle("创建日记"); + dateTextView.setText(new SimpleDateFormat("yyyy年MM月dd日").format(new Date())); + weatherTextView.setText(WEATHERS[0]); + } + } + + /** + * 显示日期选择对话框 + */ + private void showDatePickerDialog() { + //懒加载创建日期选择对话框,取当前日期为默认日期 + if (datePickerDialog == null) { + Calendar calendar = Calendar.getInstance(); + try { + String dateStr = dateTextView.getText().toString().trim(); + Date date = new SimpleDateFormat("yyyy年MM月dd日").parse(dateStr); + calendar.setTime(date); + } catch (ParseException e) { + } + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH); + int day = calendar.get(Calendar.DAY_OF_MONTH); + + datePickerDialog = new DatePickerDialog(getContext(), new DatePickerDialog.OnDateSetListener() { + + @Override + public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { + //将选择的年月日组合成文字,显示在日期文本上 + Date date = getDate(year, monthOfYear, dayOfMonth); + String dateStr = new SimpleDateFormat("yyyy年MM月dd日").format(date); + dateTextView.setText(dateStr); + } + + }, year, month, day); + } + if (!datePickerDialog.isShowing()) { + datePickerDialog.show(); + } + } + + /** + * 显示天气选择对话框 + */ + private void showWeatherPickerDialog() { + if (weatherPickerDialog == null) { + String weather = weatherTextView.getText().toString().trim(); + int position = Arrays.asList(WEATHERS).indexOf(weather); + if (position < 0) { + position = 0; + } + weatherPickerDialog = new AlertDialog.Builder(getContext()).setTitle("选择天气").setSingleChoiceItems(WEATHERS, position, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + weatherTextView.setText(WEATHERS[which]); + dialog.dismiss(); + } + + }).create(); + } + if (!weatherPickerDialog.isShowing()) { + weatherPickerDialog.show(); + } + } + + /** + * 根据年月日获取日期函数 + */ + private Date getDate(int year, int monthOfYear, int dayOfMonth) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, monthOfYear); + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); + return calendar.getTime(); + } + + /** + * 保存日记 + */ + private void saveDiary() { + //检查输入情况 + String date = dateTextView.getText().toString().trim(); + if (date.isEmpty()) { + ToastUtils.showShort("未选择日期"); + return; + } + String weather = weatherTextView.getText().toString().trim(); + if (weather.isEmpty()) { + ToastUtils.showShort("未选择天气"); + return; + } + String title = titleEditText.getText().toString().trim(); + if (title.isEmpty()) { + ToastUtils.showShort("未输入标题"); + return; + } + String content = contentEditText.getText().toString().trim(); + if (content.isEmpty()) { + ToastUtils.showShort("未输入内容"); + return; + } + + Date diaryDate; + try { + diaryDate = new SimpleDateFormat("yyyy年MM月dd日").parse(date); + } catch (ParseException e) { + ToastUtils.showShort("保存失败,时间转换错误"); + return; + } + + if (diaryId > 0) {//修改日记 + diaryEditViewModel.updateDiary(diaryId, diaryDate, weather, title, content).observe(getViewLifecycleOwner(), new Observer() { + + @Override + public void onChanged(Boolean success) { + if (success) { + savedStateHandle.set(Constant.DATA_CHANGE, true); + getNavController().navigateUp(); + } + } + + }); + + } else {//创建日记 + diaryEditViewModel.insertDiary(diaryDate, weather, title, content).observe(getViewLifecycleOwner(), new Observer() { + + @Override + public void onChanged(Boolean success) { + if (success) { + savedStateHandle.set(Constant.DATA_CHANGE, true); + getNavController().navigateUp(); + } + } + + }); + } + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/DiaryEditViewModel.java b/src/dairy/src/main/java/com/app/diary/ui/DiaryEditViewModel.java new file mode 100644 index 0000000..47439fb --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/DiaryEditViewModel.java @@ -0,0 +1,145 @@ +package com.app.diary.ui; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.app.diary.Mapp; +import com.app.diary.bean.Diary; +import com.app.diary.data.DiaryDataSource; +import com.app.diary.utils.ToastUtils; +import com.app.diary.utils.rxjava.CompletableObserverUtils; +import com.app.diary.utils.rxjava.SingleObserverUtils; + +import java.util.Date; + +import io.reactivex.rxjava3.core.CompletableSource; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.core.SingleEmitter; +import io.reactivex.rxjava3.core.SingleOnSubscribe; +import io.reactivex.rxjava3.functions.Function; +import io.reactivex.rxjava3.observers.DisposableCompletableObserver; +import io.reactivex.rxjava3.observers.DisposableSingleObserver; + +public class DiaryEditViewModel extends BaseViewModel { + + private MutableLiveData diaryLiveData = new MutableLiveData<>();//日记的数据容器 + + private DiaryDataSource diaryDataSource;//日记数据来源 + + public DiaryEditViewModel(@NonNull Application application) { + super(application); + diaryDataSource = ((Mapp) application).getDiaryDataSource(); + } + + /** + * 获取日记的数据容器 + */ + public LiveData getDiaryLiveData() { + return diaryLiveData; + } + + /** + * 加载数据 + */ + public void loadData(long diaryId, boolean lazy) { + if (lazy && loaded) { + return; + } + loaded = true; + + diaryDataSource.selectOne(diaryId).compose(SingleObserverUtils.applyUIScheduler(this)).subscribe(new DisposableSingleObserver() { + + @Override + public void onSuccess(Diary diary) { + diaryLiveData.setValue(diary); + } + + @Override + public void onError(@NonNull Throwable e) { + ToastUtils.showShort("获取失败, 原因:" + e.getMessage()); + diaryLiveData.setValue(null); + } + + }); + } + + /** + * 新增日记 + */ + public LiveData insertDiary(Date date, String weather, String title, String content) { + MutableLiveData liveData = new MutableLiveData<>(); + Single.create(new SingleOnSubscribe() { + + @Override + public void subscribe(@NonNull SingleEmitter emitter) throws Throwable { + Diary diary = new Diary(); + diary.setDate(date); + diary.setWeather(weather); + diary.setTitle(title); + diary.setContent(content); + emitter.onSuccess(diary); + } + + }).flatMapCompletable(new Function() { + + @Override + public CompletableSource apply(Diary diary) throws Throwable { + return diaryDataSource.insertDiary(diary); + } + + }).compose(CompletableObserverUtils.applyUIScheduler(this)).subscribe(new DisposableCompletableObserver() { + + @Override + public void onComplete() { + ToastUtils.showShort("新增成功"); + liveData.setValue(true); + } + + @Override + public void onError(@NonNull Throwable e) { + ToastUtils.showShort("新增失败, 原因:" + e.getMessage()); + liveData.setValue(false); + } + + }); + return liveData; + } + + /** + * 修改日记 + */ + public LiveData updateDiary(long diaryId, Date date, String weather, String title, String content) { + MutableLiveData liveData = new MutableLiveData<>(); + diaryDataSource.selectOne(diaryId).flatMapCompletable(new Function() { + + @Override + public CompletableSource apply(Diary diary) throws Throwable { + diary.setDate(date); + diary.setWeather(weather); + diary.setTitle(title); + diary.setContent(content); + return diaryDataSource.updateDiary(diary); + } + + }).compose(CompletableObserverUtils.applyUIScheduler(this)).subscribe(new DisposableCompletableObserver() { + + @Override + public void onComplete() { + ToastUtils.showShort("修改成功"); + liveData.setValue(true); + } + + @Override + public void onError(@NonNull Throwable e) { + ToastUtils.showShort("修改失败, 原因:" + e.getMessage()); + liveData.setValue(false); + } + + }); + return liveData; + } + +} diff --git a/src/dairy/src/main/java/com/app/diary/ui/DiaryListFragment.java b/src/dairy/src/main/java/com/app/diary/ui/DiaryListFragment.java new file mode 100644 index 0000000..1a0e700 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/DiaryListFragment.java @@ -0,0 +1,125 @@ +package com.app.diary.ui; + +import android.graphics.Rect; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavBackStackEntry; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.app.diary.R; +import com.app.diary.adapter.DiaryRecyclerAdapter; +import com.app.diary.bean.Constant; +import com.app.diary.bean.Diary; +import com.app.diary.utils.SizeUtils; + +import java.util.List; + +/** + * 日志列表 + */ +public class DiaryListFragment extends BaseFragment { + + private Toolbar toolbar;//标题栏控件 + private RecyclerView recyclerView;//列表控件 + + private DiaryRecyclerAdapter diaryRecyclerAdapter; + private DiaryListViewModel diaryListViewModel; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + NavBackStackEntry navBackStackEntry = getNavController().getCurrentBackStackEntry(); + navBackStackEntry.getSavedStateHandle().getLiveData(Constant.DATA_CHANGE).observe(navBackStackEntry, new Observer() { + + @Override + public void onChanged(Object dataChanged) { + if ((boolean) dataChanged) { + if (diaryListViewModel != null) { + diaryListViewModel.loadData(false); + } + } + } + + }); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_diary_list, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + diaryListViewModel = new ViewModelProvider(this).get(DiaryListViewModel.class); + initView(view); + setView(); + diaryListViewModel.loadData(true); + } + + /** + * 初始化控件 + */ + private void initView(@NonNull View view) { + toolbar = view.findViewById(R.id.toolbar); + recyclerView = view.findViewById(R.id.recyclerView); + } + + /** + * 设置控件 + */ + private void setView() { + //将标题栏关联到页面 + initSupportActionBar(toolbar, true); + + //设置列表的布局样式 + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); + recyclerView.setLayoutManager(linearLayoutManager); + //设置列表的间隔距离 + recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { + + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + int count = parent.getAdapter().getItemCount(); + int index = parent.getChildAdapterPosition(view); + if (index < count - 1) { + outRect.set(0, 0, 0, SizeUtils.dp2px(30)); + } + } + + }); + //设置列表的适配器 + diaryRecyclerAdapter = new DiaryRecyclerAdapter(); + diaryRecyclerAdapter.setOnItemClickListener(new DiaryRecyclerAdapter.OnItemClickListener() { + + @Override + public void onItemClick(Diary diary, int position) { + getNavController().navigate(DiaryListFragmentDirections.diaryBrowseAction(diary.getId())); + } + + }); + recyclerView.setAdapter(diaryRecyclerAdapter); + + //观察日记列表数据并将数据加入适配器 + diaryListViewModel.getDiaryListLiveData().observe(getViewLifecycleOwner(), new Observer>() { + + @Override + public void onChanged(List list) { + diaryRecyclerAdapter.setNewData(list); + } + + }); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/DiaryListViewModel.java b/src/dairy/src/main/java/com/app/diary/ui/DiaryListViewModel.java new file mode 100644 index 0000000..3b51a0c --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/DiaryListViewModel.java @@ -0,0 +1,62 @@ +package com.app.diary.ui; + +import android.app.Application; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.app.diary.Mapp; +import com.app.diary.bean.Diary; +import com.app.diary.data.DiaryDataSource; +import com.app.diary.utils.ToastUtils; +import com.app.diary.utils.rxjava.SingleObserverUtils; + +import java.util.List; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.observers.DisposableSingleObserver; + +public class DiaryListViewModel extends BaseViewModel { + + private MutableLiveData> diaryListLiveData = new MutableLiveData<>();//日记列表的数据容器 + + private DiaryDataSource diaryDataSource;//日记数据来源 + + public DiaryListViewModel(@NonNull Application application) { + super(application); + diaryDataSource = ((Mapp) application).getDiaryDataSource(); + } + + /** + * 获取日记列表的数据容器 + */ + public LiveData> getDiaryListLiveData() { + return diaryListLiveData; + } + + /** + * 加载数据 + */ + public void loadData(boolean lazy) { + if (lazy && loaded) { + return; + } + loaded = true; + + diaryDataSource.selectList().compose(SingleObserverUtils.applyUIScheduler(this)).subscribe(new DisposableSingleObserver>() { + + @Override + public void onSuccess(List list) { + diaryListLiveData.setValue(list); + } + + @Override + public void onError(@NonNull Throwable e) { + ToastUtils.showShort("获取失败, 原因:" + e.getMessage()); + diaryListLiveData.setValue(null); + } + + }); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/IndexFragment.java b/src/dairy/src/main/java/com/app/diary/ui/IndexFragment.java new file mode 100644 index 0000000..206b6b6 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/IndexFragment.java @@ -0,0 +1,81 @@ +package com.app.diary.ui; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; + +import com.app.diary.R; +import com.app.diary.utils.AppUtils; + +/** + * 首页 + */ +public class IndexFragment extends BaseFragment { + + private Toolbar toolbar;//标题栏控件 + private Button browseButton;//查看日记按钮控件 + private Button createButton;//创建日记按钮控件 + private TextView versionTextView;//当前版本文本控件 + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_index, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initView(view); + setView(); + } + + /** + * 初始化控件 + */ + private void initView(@NonNull View view) { + toolbar = view.findViewById(R.id.toolbar); + browseButton = view.findViewById(R.id.browse_button); + createButton = view.findViewById(R.id.create_button); + versionTextView = view.findViewById(R.id.version_textView); + } + + /** + * 设置控件 + */ + private void setView() { + //将标题栏关联到页面 + initSupportActionBar(toolbar, false); + + //设置查看日记按钮的点击事件 + browseButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + getNavController().navigate(IndexFragmentDirections.diaryListAction()); + } + + }); + + //设置创建日记按钮的点击事件 + createButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + getNavController().navigate(IndexFragmentDirections.diaryEditAction(0)); + } + + }); + + //将当前版本名称显示在文本上 + versionTextView.setText("当前版本:v" + AppUtils.getVersionName()); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/ui/MainActivity.java b/src/dairy/src/main/java/com/app/diary/ui/MainActivity.java new file mode 100644 index 0000000..acdb36c --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/ui/MainActivity.java @@ -0,0 +1,21 @@ +package com.app.diary.ui; + +import android.content.pm.ActivityInfo; +import android.os.Bundle; + +import com.app.diary.R; + +/** + * 主页面 + */ +public class MainActivity extends BaseActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + //设置竖屏 + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/utils/AppUtils.java b/src/dairy/src/main/java/com/app/diary/utils/AppUtils.java new file mode 100644 index 0000000..af3ba9b --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/utils/AppUtils.java @@ -0,0 +1,28 @@ +package com.app.diary.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import com.app.diary.Mapp; + +/** + * app工具 + */ +public class AppUtils { + + /** + * 获取版本名称 + */ + public static String getVersionName() { + try { + Context context = Mapp.getInstance(); + PackageManager pm = context.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + return pi == null ? "" : pi.versionName; + } catch (PackageManager.NameNotFoundException e) { + return ""; + } + } + +} diff --git a/src/dairy/src/main/java/com/app/diary/utils/SizeUtils.java b/src/dairy/src/main/java/com/app/diary/utils/SizeUtils.java new file mode 100644 index 0000000..cf4bda2 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/utils/SizeUtils.java @@ -0,0 +1,42 @@ +package com.app.diary.utils; + +import android.content.res.Resources; + +/** + * 尺寸工具 + */ +public class SizeUtils { + + /** + * dp单位转成px单位 + */ + public static int dp2px(float dpValue) { + float scale = Resources.getSystem().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + /** + * px单位转成dp单位 + */ + public static int px2dp(float pxValue) { + float scale = Resources.getSystem().getDisplayMetrics().density; + return (int) (pxValue / scale + 0.5f); + } + + /** + * sp单位转成px单位 + */ + public static int sp2px(float spValue) { + float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity; + return (int) (spValue * fontScale + 0.5f); + } + + /** + * px单位转成sp单位 + */ + public static int px2sp(float pxValue) { + float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity; + return (int) (pxValue / fontScale + 0.5f); + } + +} diff --git a/src/dairy/src/main/java/com/app/diary/utils/TimeUtils.java b/src/dairy/src/main/java/com/app/diary/utils/TimeUtils.java new file mode 100644 index 0000000..f7daf9f --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/utils/TimeUtils.java @@ -0,0 +1,50 @@ +package com.app.diary.utils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * 时间工具 + */ +public class TimeUtils { + + /** + * 获取简要时间 + */ + public static String getSimpleTime(long millis) { + long now = System.currentTimeMillis(); + long span = now - millis; + if (span < 0) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(millis)); + } else if (span < 1000) { + return "刚刚"; + } else if (span < 60000) { + return String.format(Locale.getDefault(), "%d秒前", span / 1000); + } else if (span < 3600000) { + return String.format(Locale.getDefault(), "%d分钟前", span / 60000); + } + long wee = getWeeOfToday(); + if (millis >= wee) { + return "今天" + new SimpleDateFormat("HH:mm:ss").format(new Date(millis)); + } else if (millis >= wee - 86400000) { + return "昨天" + new SimpleDateFormat("HH:mm:ss").format(new Date(millis)); + } else { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(millis)); + } + } + + /** + * 获取时分秒归0的当前时间 + */ + private static long getWeeOfToday() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTimeInMillis(); + } + +} diff --git a/src/dairy/src/main/java/com/app/diary/utils/ToastUtils.java b/src/dairy/src/main/java/com/app/diary/utils/ToastUtils.java new file mode 100644 index 0000000..265d1b8 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/utils/ToastUtils.java @@ -0,0 +1,19 @@ +package com.app.diary.utils; + +import android.widget.Toast; + +import com.app.diary.Mapp; + +/** + * 吐司工具 + */ +public class ToastUtils { + + /** + * 短时间吐司 + */ + public static void showShort(CharSequence text) { + Toast.makeText(Mapp.getInstance(), text, Toast.LENGTH_SHORT).show(); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/utils/rxjava/CompletableObserverUtils.java b/src/dairy/src/main/java/com/app/diary/utils/rxjava/CompletableObserverUtils.java new file mode 100644 index 0000000..b240e0f --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/utils/rxjava/CompletableObserverUtils.java @@ -0,0 +1,41 @@ +package com.app.diary.utils.rxjava; + +import androidx.annotation.Nullable; + +import com.app.diary.ui.BaseViewModel; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.CompletableTransformer; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public class CompletableObserverUtils { + + /** + * UI线程调度器 + */ + public static CompletableTransformer applyUIScheduler(@Nullable BaseViewModel viewModel) { + return upstream -> upstream + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe(disposable -> { + if (viewModel != null) { + viewModel.addDisposable(disposable); + } + }); + } + + /** + * IO线程调度器 + */ + public static CompletableTransformer applyIOScheduler(@Nullable BaseViewModel viewModel) { + return upstream -> upstream + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .doOnSubscribe(disposable -> { + if (viewModel != null) { + viewModel.addDisposable(disposable); + } + }); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/java/com/app/diary/utils/rxjava/SingleObserverUtils.java b/src/dairy/src/main/java/com/app/diary/utils/rxjava/SingleObserverUtils.java new file mode 100644 index 0000000..13dba64 --- /dev/null +++ b/src/dairy/src/main/java/com/app/diary/utils/rxjava/SingleObserverUtils.java @@ -0,0 +1,41 @@ +package com.app.diary.utils.rxjava; + +import androidx.annotation.Nullable; + +import com.app.diary.ui.BaseViewModel; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.SingleTransformer; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public class SingleObserverUtils { + + /** + * UI线程调度器 + */ + public static SingleTransformer applyUIScheduler(@Nullable BaseViewModel viewModel) { + return upstream -> upstream + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe(disposable -> { + if (viewModel != null) { + viewModel.addDisposable(disposable); + } + }); + } + + /** + * IO线程调度器 + */ + public static SingleTransformer applyIOScheduler(@Nullable BaseViewModel viewModel) { + return upstream -> upstream + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .doOnSubscribe(disposable -> { + if (viewModel != null) { + viewModel.addDisposable(disposable); + } + }); + } + +} \ No newline at end of file diff --git a/src/dairy/src/main/res/drawable/bg_edit.xml b/src/dairy/src/main/res/drawable/bg_edit.xml new file mode 100644 index 0000000..b175628 --- /dev/null +++ b/src/dairy/src/main/res/drawable/bg_edit.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/drawable/btn_diary.xml b/src/dairy/src/main/res/drawable/btn_diary.xml new file mode 100644 index 0000000..a6ff890 --- /dev/null +++ b/src/dairy/src/main/res/drawable/btn_diary.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/drawable/btn_full.xml b/src/dairy/src/main/res/drawable/btn_full.xml new file mode 100644 index 0000000..804166f --- /dev/null +++ b/src/dairy/src/main/res/drawable/btn_full.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/drawable/btn_hollow.xml b/src/dairy/src/main/res/drawable/btn_hollow.xml new file mode 100644 index 0000000..f295793 --- /dev/null +++ b/src/dairy/src/main/res/drawable/btn_hollow.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/layout/activity_main.xml b/src/dairy/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..a3dc963 --- /dev/null +++ b/src/dairy/src/main/res/layout/activity_main.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/layout/fragment_diary_browse.xml b/src/dairy/src/main/res/layout/fragment_diary_browse.xml new file mode 100644 index 0000000..cc25615 --- /dev/null +++ b/src/dairy/src/main/res/layout/fragment_diary_browse.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/layout/fragment_diary_edit.xml b/src/dairy/src/main/res/layout/fragment_diary_edit.xml new file mode 100644 index 0000000..bc37526 --- /dev/null +++ b/src/dairy/src/main/res/layout/fragment_diary_edit.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/layout/fragment_diary_list.xml b/src/dairy/src/main/res/layout/fragment_diary_list.xml new file mode 100644 index 0000000..705ea29 --- /dev/null +++ b/src/dairy/src/main/res/layout/fragment_diary_list.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dairy/src/main/res/layout/fragment_index.xml b/src/dairy/src/main/res/layout/fragment_index.xml new file mode 100644 index 0000000..e0168c8 --- /dev/null +++ b/src/dairy/src/main/res/layout/fragment_index.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + +