diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7167f0611c2a8104d8931f53f346dcb5b7190025 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1 @@ +/misc.xml diff --git a/.idea/SlackerKiller.iml b/.idea/SlackerKiller.iml new file mode 100644 index 0000000000000000000000000000000000000000..d6ebd4805981b8400db3e3291c74a743fef9a824 --- /dev/null +++ b/.idea/SlackerKiller.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..35473e9c819a5c4fa300e221f60e1c9508fe25e5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4da205c13803fda5f23ca9a82ef36b2dc902dd1 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + 1639983764287 + + + + \ No newline at end of file diff --git a/Docs/presentation/SlackerKiller.pptx b/Docs/presentation/SlackerKiller.pptx new file mode 100644 index 0000000000000000000000000000000000000000..9d24bd6e07a880010c4e77c6753da224d6932469 Binary files /dev/null and b/Docs/presentation/SlackerKiller.pptx differ diff --git a/Docs/presentation/SlackerKiller2.html b/Docs/presentation/SlackerKiller2.html new file mode 100644 index 0000000000000000000000000000000000000000..68006f6f76d8d47e671ed247b480dc3eec44fe6d --- /dev/null +++ b/Docs/presentation/SlackerKiller2.html @@ -0,0 +1,1385 @@ + + + + + +SlackerKiller2 + +
+

SlackerKiller

界面导航

产品初衷

产品功能

使用介绍

下载链接

未来改进

做这款slackerKiller应用的初衷

具体我们的应用能为你实现什么

你可以为你完成的任务进行紧急程度的设置,如果你希望某一个任务今早完成,那么请为任务设置高紧急度!

emergency3

你还可以为你的一天生活选择一个偏好时间,比如,小卢同学喜欢早起,那么他就想在早上完成一些重要的任务,那么"懒蛋杀手“就提供了这么一个偏好时间段的选择

至于什么任务对于你来说是重要的,对于一个小同学Free来说,他觉得健身是重要的

image-20211222103526661

而对他来说模电的就是非常不重要的,那么他指定了两个任务:健身(重要),模电作业(不重要),如上面所说的假设他也选择了早上作为自己的偏好时间。那么“懒蛋杀手”就会为Free同学优先在早上的空闲时间分配健身的任务,而可能在晚上为他分配模电任务。

如此他就可以在自己效率最高的时间段完成他认为重要的任务(健身),而在他已经昏昏欲睡效率低下的时候就开心地(摆烂)完成他的模电作业咯!

importance1

 

(暂时因为一些bug没有上线)不要担心你的任务可能在自己预设的时间里无法完成,"懒蛋杀手"还有一个特别的功能就是对你任务的回顾和总结

如果你在某个任务完成时,“懒蛋杀手”会提供你一个打分机制,如果你觉得这个任务我完成的非常好,那么选择满星!但是如果一个任务完成的很烂,比如,Free同学在写模电作业的时候睡着了,等他睡醒发现,他的预设时间已经到了,不要担心! 他只需要选择低星级并让"懒蛋杀手"为你分配另外的时间继续完成就好

除此之外,"懒蛋杀手"在每周末的时候会为你做一个总结,你完成某一些任务的平均评价它都会展示给你,让你能在下次分配任务时间时做出更好的决断。

 

当然,你可能会问:"QAQ,那如果我想摆烂怎么办,懒蛋杀手把我的时间都分配出去了,好难受!"。

不用担心,你可以给自己分配一个课程任务叫休息啊,当然如果你按照正常操作进行分配,"懒蛋杀手"可能不知道把这个事件分到了哪里去了,所以你需要做一些额外的操作:为这个休息任务选择为课程标签,这样你就可以规定它精确的开始时间和结束时间了。

当然我强烈不建议你那么做,因为如果你做这样的操作,"懒蛋杀手"会把你吃掉的!

 

产品简介

下面来认识我们的懒蛋终结者吧

 

img

c

 

下载来玩吧!

 

to be continue

 

 

+ + \ No newline at end of file diff --git a/Docs/presentation/SlackerKiller2.md b/Docs/presentation/SlackerKiller2.md new file mode 100644 index 0000000000000000000000000000000000000000..0fd2cf59c81e5ef25e1348f16472ba75e18412c2 --- /dev/null +++ b/Docs/presentation/SlackerKiller2.md @@ -0,0 +1,108 @@ +# SlackerKiller + +## 界面导航 + +产品初衷 + +产品功能 + +使用介绍 + +下载链接 + +未来改进 + +## 做这款slackerKiller应用的初衷 + +- 自己作为一名计算机大三的学生深感时间的不够用,以及因为课程导致的时间碎片化问题,加上轻微的拖延症导致很多时间临到ddl才开始做或者根本没时间做。我极度需要一个任务分配的工具来帮助用户将锁片化的时间充分利用起来。 +- 这个应用需要满足我的需求: + - 根据用户的课程计划(这是固定的,由课表或者教务后台读取)和添加的学习生活任务,为用户制定合理的任务分配表 + - 可以在适当时候提醒用户开始任务 + - 对工作学习任务的工作情况做总结 + +- 本质上这是一款智能的任务分配及提醒的应用 + +## 具体我们的应用能为你实现什么 + +你可以为你完成的任务进行==紧急程度的设置==,如果你希望某一个任务今早完成,那么请为任务设置高紧急度! + +emergency3 + +你还可以为你的一天生活选择一个==偏好时间==,比如,小卢同学喜欢早起,那么他就想在早上完成一些重要的任务,那么"懒蛋杀手“就提供了这么一个偏好时间段的选择 + +> 至于什么任务对于你来说是重要的,对于一个小同学Free来说,他觉得健身是重要的 +> +> ![image-20211222103526661](C:\Users\24160\AppData\Roaming\Typora\typora-user-images\image-20211222103526661.png) +> +> 而对他来说模电的就是非常不重要的,那么他指定了两个任务:健身(重要),模电作业(不重要),如上面所说的假设他也选择了早上作为自己的偏好时间。那么“懒蛋杀手”就会为Free同学优先在早上的空闲时间分配健身的任务,而可能在晚上为他分配模电任务。 +> +> 如此他就可以在自己效率最高的时间段完成他认为重要的任务(健身),而在他已经昏昏欲睡效率低下的时候就开心地(摆烂)完成他的模电作业咯! +> +> importance1 +> +> + +(暂时因为一些bug没有上线)不要担心你的任务可能在自己预设的时间里无法完成,"懒蛋杀手"还有一个特别的功能就是==对你任务的回顾和总结== + +如果你在某个任务完成时,“懒蛋杀手”会提供你一个打分机制,如果你觉得这个任务我完成的非常好,那么选择满星!但是如果一个任务完成的很烂,比如,Free同学在写模电作业的时候睡着了,等他睡醒发现,他的预设时间已经到了,不要担心! 他只需要选择低星级并让"懒蛋杀手"为你分配另外的时间继续完成就好 + +除此之外,"懒蛋杀手"在每周末的时候会为你做一个总结,你完成某一些任务的平均评价它都会展示给你,让你能在下次分配任务时间时做出更好的决断。 + + + +当然,你可能会问:"QAQ,那如果我想摆烂怎么办,懒蛋杀手把我的时间都分配出去了,好难受!"。 + +不用担心,你可以给自己==分配一个课程任务叫休息==啊,当然如果你按照正常操作进行分配,"懒蛋杀手"可能不知道把这个事件分到了哪里去了,所以你需要做一些额外的操作:为这个休息任务选择为课程标签,这样你就可以规定它精确的开始时间和结束时间了。 + +当然我强烈不建议你那么做,因为如果你做这样的操作,"懒蛋杀手"会把你吃掉的! + + + +## 产品简介 + +下面来认识我们的懒蛋终结者吧 + +- 初始化课表 + + + +img + +- 添加任务 + + | 健身:100分钟、重要、紧急 | 模电:65分钟、不紧急、不重要 | 算法复习:40分钟、一般紧急、一般重要 | + | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | + | 2 | 3 | 4 | + + + + - todolist展示 + + B8Z5YVXZHD7_T0ZU40H1S1Q + +- 分配任务 + + 6 + + 7 + +- 分配结果 + + +c + + + +## 下载来玩吧! + + + +## to be continue + +- 导入教务系统 +- 总结功能的完善 +- 给自己设置课程 +- 任务的地点聚类 + + + diff --git "a/Docs/presentation/\344\273\273\345\212\241\347\224\230\347\211\271\345\233\276.xlsx" "b/Docs/presentation/\344\273\273\345\212\241\347\224\230\347\211\271\345\233\276.xlsx" new file mode 100644 index 0000000000000000000000000000000000000000..464d7874ff45b914f519da47a410286dad6b7cc5 Binary files /dev/null and "b/Docs/presentation/\344\273\273\345\212\241\347\224\230\347\211\271\345\233\276.xlsx" differ diff --git a/Src/SlackerKiller/.gitignore b/Src/SlackerKiller/.gitignore index aa724b77071afcbd9bb398053e05adaf7ac9405a..e83a637465203a46926171ac2a4d1127699a89bc 100644 --- a/Src/SlackerKiller/.gitignore +++ b/Src/SlackerKiller/.gitignore @@ -7,6 +7,7 @@ /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml +/.idea/misc.xml .DS_Store /build /captures diff --git a/Src/SlackerKiller/.idea/.gitignore b/Src/SlackerKiller/.idea/.gitignore index 26d33521af10bcc7fd8cea344038eaaeb78d0ef5..5a227f6f3655788fd4dde32b110c46937d636d2f 100644 --- a/Src/SlackerKiller/.idea/.gitignore +++ b/Src/SlackerKiller/.idea/.gitignore @@ -1,3 +1,4 @@ # Default ignored files /shelf/ /workspace.xml +/*.xml diff --git a/Src/SlackerKiller/.idea/codeStyles/Project.xml b/Src/SlackerKiller/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000000000000000000000000000000000..7643783a82f60b3b876fe58a9314fb50520df486 --- /dev/null +++ b/Src/SlackerKiller/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Src/SlackerKiller/.idea/codeStyles/codeStyleConfig.xml b/Src/SlackerKiller/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000000000000000000000000000000000000..79ee123c2b23e069e35ed634d687e17f731cc702 --- /dev/null +++ b/Src/SlackerKiller/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/Src/SlackerKiller/.idea/misc.xml b/Src/SlackerKiller/.idea/misc.xml index 860da66a5ea990f8e3c36fcdb0e2e05dacadf880..84450ac75b73192ccd329b7ddc21da4ba18f689d 100644 --- a/Src/SlackerKiller/.idea/misc.xml +++ b/Src/SlackerKiller/.idea/misc.xml @@ -1,6 +1,14 @@ - + + + + diff --git a/Src/SlackerKiller/app/build.gradle b/Src/SlackerKiller/app/build.gradle index 78fabdcf7f08dc87f8ccf2599c61d4a6e61eb4ee..369db5c3c1a82dd7e0d9c94cfee99edca2e62eef 100644 --- a/Src/SlackerKiller/app/build.gradle +++ b/Src/SlackerKiller/app/build.gradle @@ -26,6 +26,7 @@ android { } } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -38,7 +39,7 @@ android { } composeOptions { kotlinCompilerExtensionVersion compose_version - kotlinCompilerVersion '1.5.21' + kotlinCompilerVersion '1.5.31' } packagingOptions { resources { @@ -61,7 +62,7 @@ dependencies { implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' implementation 'androidx.activity:activity-compose:1.4.0' - testImplementation 'junit:junit:' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" @@ -97,11 +98,27 @@ dependencies { def pager_version = "0.21.4-beta" - implementation "com.google.accompanist:accompanist-pager:$pager_version" + implementation "com.google.accompanist:accompanist-pager:0.21.5-rc" // If using indicators, also depend on implementation "com.google.accompanist:accompanist-pager-indicators:$pager_version" //navigation implementation "androidx.navigation:navigation-compose:2.4.0-rc01" + + + implementation "org.burnoutcrew.composereorderable:reorderable:0.7.4" + + + implementation 'com.google.accompanist:accompanist-insets:0.21.5-rc' + // If using insets-ui + implementation 'com.google.accompanist:accompanist-insets-ui:0.21.5-rc' + + implementation "com.google.accompanist:accompanist-systemuicontroller:0.10.0" + + implementation "androidx.compose.material:material-icons-extended:$compose_version" + + implementation 'com.chargemap.compose:numberpicker:0.0.10' + + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' } \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/AndroidManifest.xml b/Src/SlackerKiller/app/src/main/AndroidManifest.xml index 1d25c6fee7fd58aea67718930a447346bed8a2a3..8c96241b3783a1d525ac1291f253497bc35c4d24 100644 --- a/Src/SlackerKiller/app/src/main/AndroidManifest.xml +++ b/Src/SlackerKiller/app/src/main/AndroidManifest.xml @@ -13,7 +13,8 @@ android:name=".MainActivity" android:exported="true" android:label="@string/app_name" - android:theme="@style/Theme.SlackerKiller.NoActionBar"> + android:theme="@style/Theme.SlackerKiller.NoActionBar" + android:windowSoftInputMode="adjustResize"> diff --git a/Src/SlackerKiller/app/src/main/ic_launcher-playstore.png b/Src/SlackerKiller/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..4dda563a58903cf3d4173f326a33b8326a86d0dd Binary files /dev/null and b/Src/SlackerKiller/app/src/main/ic_launcher-playstore.png differ diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/MainActivity.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/MainActivity.kt index 811ac9b005ef457b676bfcaf0cbecdd732451878..919ec938a06e79a8fbb32ac0906465ed965f6d34 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/MainActivity.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/MainActivity.kt @@ -8,14 +8,19 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview +import androidx.core.view.WindowCompat import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.rememberPagerState +import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import org.kannaj.slackerkiller.data.events.EventRepository @@ -24,6 +29,7 @@ import org.kannaj.slackerkiller.model.eventmodel.EventDatabase import org.kannaj.slackerkiller.model.taskmodel.TaskDatabase import org.kannaj.slackerkiller.ui.theme.SlackerKillerTheme import org.kannaj.slackerkiller.ui.todolist.TodoActivityScreen +import org.kannaj.slackerkiller.ui.todolist.TodoDetail import org.kannaj.slackerkiller.ui.todolist.TodoListViewModel import org.kannaj.slackerkiller.ui.todolist.TodoListViewModelFactory import org.kannaj.slackerkiller.ui.weekpage.ScrollableWeekPage @@ -36,50 +42,76 @@ class MainActivity : ComponentActivity() { // is used for init the database(not used for now) val scope = CoroutineScope(SupervisorJob()) - private val eventDatabase by lazy { EventDatabase.getDatabase(this, scope) } + private val eventDatabase by lazy { EventDatabase.getDatabase(this, scope)} private val eventRepository by lazy { EventRepository(eventDatabase.eventDao()) } private val taskDatabase by lazy { TaskDatabase.getDatabase(this) } private val taskRepository by lazy { TaskRepository(taskDatabase.taskDao()) } private val todoListViewModel by viewModels { - TodoListViewModelFactory(taskRepository) + TodoListViewModelFactory(taskRepository, eventRepository) } private val weekPageViewModel by viewModels { WeekPageViewModelFactory(eventRepository, taskRepository) } + private val a = 0L @ExperimentalPagerApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + WindowCompat.setDecorFitsSystemWindows(window, false) + setContent { + + val systemUiController = rememberSystemUiController() + val useDarkIcons = MaterialTheme.colors.isLight + SideEffect { + systemUiController.setSystemBarsColor(Color.Transparent, darkIcons = useDarkIcons) + } + SlackerKillerTheme { // A surface container using the 'background' color from the theme Surface(color = MaterialTheme.colors.background) { val navController = rememberNavController() val pagerState = rememberPagerState() - NavHost(navController = navController, startDestination = "weekPage") { - composable("weekPage") { - ScrollableWeekPage( - navController = navController, - weekPageViewModel = weekPageViewModel, - pagerState = pagerState - ) - } - composable( - "todoList/{weekId}", - arguments = listOf(navArgument("weekId") { - type = NavType.LongType - }) - ) { backStackEntry -> - TodoActivityScreen( - navController = navController, - todoListViewModel = todoListViewModel, - weekId = backStackEntry.arguments?.getLong("weekId") - ) + + ProvideWindowInsets() { + NavHost(navController = navController, startDestination = "weekPage") { + composable("weekPage") { + ScrollableWeekPage( + navController = navController, + weekPageViewModel = weekPageViewModel, + pagerState = pagerState + ) + } + composable( + "todoList/{weekId}", + arguments = listOf(navArgument("weekId") { + type = NavType.LongType + }) + ) { backStackEntry -> + TodoActivityScreen( + navController = navController, + todoListViewModel = todoListViewModel, + weekId = backStackEntry.arguments?.getLong("weekId") + ) + } + composable("todoDetail/{taskId}", + arguments = listOf(navArgument("taskId") { + type = NavType.IntType + }) + ) { backStackEntry -> + TodoDetail( + taskId = backStackEntry.arguments!!.getInt("taskId"), + navController = navController, + todoListViewModel = todoListViewModel + ) + } } } + } } } diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/AlgorithmStructure/Algorithm.java b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/AlgorithmStructure/Algorithm.java index 019e1b8dd622e947409ad85c1f4fcde909e7b953..b99ba5f779d90ec0815abaff73114b73bb840183 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/AlgorithmStructure/Algorithm.java +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/AlgorithmStructure/Algorithm.java @@ -20,13 +20,14 @@ import java.util.List; public class Algorithm { // private AlgorithmModel.TimeTable timeTable; public List FullCalculate(List taskList, int[][][] t, TimeTable timeTable) { + taskList = sortTasks(taskList); // 在这个方法中只分配完整的时间 //t为7*24* 60/TimeTableConfig.minTimeBar // timeTable = new AlgorithmModel.TimeTable(); List UnallocatedTask = new ArrayList<>(MaxUnallocatedTask); for (AlgoTask currTask : taskList) { boolean Yes = false; //Yes为true表示已经分配了 - for (int i = 0; i < 7; i++) { + for (int i = timeTable.getTaskBeginDay()-1; i < 7; i++) { long sumTime = 0; int[] index = {0, 0, 0}; //index 中保存的是可以分配的开始时间 @@ -94,8 +95,12 @@ public class Algorithm { return taskList; } - - public List customizedCalulate(List taskList, int[][][] t, TimeTable timeTable, int preferTimeBegin, int preferTimeEnd) { + // overloaded function,which has two parameters to signify users' loved working time to locate the important tasks in this period + public List FullCalculate(List taskList, int[][][] t, TimeTable timeTable, int preferTimeBegin, int preferTimeEnd) { + taskList = sortTasks(taskList); + // 偏好时间与预设的工作时间不符 + if (preferTimeBegindayEnd) + return taskList; List vitalTasks = new ArrayList<>(); List ordinaryTasks = new ArrayList<>(); List otherTasks = new ArrayList<>(); @@ -117,7 +122,7 @@ public class Algorithm { } } } - for (int d = 0; d < 7; d++) { + for (int d = timeTable.getTaskBeginDay()-1; d < 7; d++) { for (int i = preferTimeBegin - 1; i <= preferTimeEnd - 2; i++) { for (int j = 0; j < 60 / TimeTableConfig.minTimeBar; j++) { if (t[d][i][j] == available) { @@ -131,7 +136,7 @@ public class Algorithm { // 使用偏好时间进行调用 UnallocatedTasks = FullCalculate(vitalTasks, pt, timeTable); // 分配完成后需要根据,pt时间的修改成功更新t表 - for (int d = 0; d < 7; d++) { + for (int d = timeTable.getTaskBeginDay()-1; d < 7; d++) { for (int i = preferTimeBegin - 1; i <= preferTimeEnd - 2; i++) { for (int j = 0; j < 60 / TimeTableConfig.minTimeBar; j++) { if (pt[d][i][j] == available) { diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/DistributerStructure/Distributer.java b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/DistributerStructure/Distributer.java index d67d9ca65222b05ccfb4409ac76d9580313717c5..3a3f788b1b1fb5b9bc1192e8aaafdd57d96c43a7 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/DistributerStructure/Distributer.java +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/DistributerStructure/Distributer.java @@ -17,14 +17,15 @@ public class Distributer { private List tasks; public static void main(String[] args) { - long time1 = 12 * TimeTableConfig.minTimeBar; - long time2 = 24 * TimeTableConfig.minTimeBar; - AlgoTask task1 = new AlgoTask("task1", 0, "test", "data", 1, time1, 0, 0, "here", 0, 0); - AlgoTask task2 = new AlgoTask("task2", 1, "test", "data", 2, time2, 0, 0, "here", 0, 0); - AlgoTask task3 = new AlgoTask("task3", 2, "test", "data", 1, time2, 0, 0, "here", 1, 0); - AlgoTask task4 = new AlgoTask("task4", 3, "test", "data", 1, time2, 0, 0, "here", 1, 0); - - task3.setTimeBegin(System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 7); + long time1 = 48 * TimeTableConfig.minTimeBar; + long time2 = 48 * TimeTableConfig.minTimeBar; + AlgoTask task1 = new AlgoTask("task1", 0, "test", "data", 3, time1, 1, 0, "here", 0, 0); + AlgoTask task2 = new AlgoTask("task2", 1, "test", "data", 1, time2, 3, 0, "here", 0, 0); + AlgoTask task3 = new AlgoTask("task3", 2, "test", "data", 1, time2, 1, 0, "here", 1, 0); + AlgoTask task4 = new AlgoTask("task4", 3, "test", "data", 1, time2, 1, 0, "here", 1, 0); + AlgoTask task5 = new AlgoTask("task4", 4, "test", "data", 1, time2, 3, 0, "here", 0, 0); + + task3.setTimeBegin(System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 7 ); task3.setTimeEnd(System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 7 + 1000 * 60 * 60); Calendar c = Calendar.getInstance(); Calendar test = Calendar.getInstance(); @@ -37,6 +38,7 @@ public class Distributer { tasklist.add(task2); tasklist.add(task3); tasklist.add(task4); + tasklist.add(task5); // Distributer d = new Distributer(); d.run(tasklist, 2); @@ -55,6 +57,21 @@ public class Distributer { * @param tasklist task的list * @param courseList course的list */ + public void run(List tasklist, List courseList, int taskBegin,int preferBegin,int preferEnd) { + tasks = tasklist; + courses = courseList; + setTaskList(courses, taskBegin); + int[][][] freetime = timetable.getFreeTimeArray(); + List result = algo.FullCalculate(algo.sortTasks(tasks), freetime, timetable,preferBegin,preferEnd); + if (result.isEmpty()) { + System.out.println(true); + printTime(tasklist); + printTime(courseList); + } else + System.out.println(false); + timetable.createNewTable(); + } + public void run(List tasklist, List courseList, int taskBegin) { tasks = tasklist; courses = courseList; @@ -67,7 +84,7 @@ public class Distributer { printTime(courseList); } else System.out.println(false); - + timetable.createNewTable(); } /** @@ -88,6 +105,7 @@ public class Distributer { System.out.println(true); else System.out.println(false); + timetable.createNewTable(); } @@ -95,16 +113,19 @@ public class Distributer { timetable.fitTasklist(tasklist, taskBegin); } + + + public void printTime(List tasklist) { Calendar c = Calendar.getInstance(); for (AlgoTask task : tasklist) { c.clear(); c.setTimeInMillis(task.getTimeBegin()); System.out.println("task " + task.getID() + ":"); - System.out.println("\t\tbegin time:" + c.get(Calendar.YEAR) + '.' + (c.get(Calendar.MONTH) + 1) + '.' + c.get(Calendar.DAY_OF_MONTH) + ' ' + c.get(Calendar.HOUR) + ':' + c.get(Calendar.MINUTE)); + System.out.println("\t\tbegin time:" + c.get(Calendar.YEAR) + '.' + (c.get(Calendar.MONTH) + 1) + '.' + c.get(Calendar.DAY_OF_MONTH) + ' ' + c.get(Calendar.HOUR_OF_DAY) + ':' + c.get(Calendar.MINUTE)); c.clear(); c.setTimeInMillis(task.getTimeEnd()); - System.out.println("\t\tend time:" + c.get(Calendar.YEAR) + '.' + (c.get(Calendar.MONTH) + 1) + '.' + c.get(Calendar.DAY_OF_MONTH) + ' ' + c.get(Calendar.HOUR) + ':' + c.get(Calendar.MINUTE)); + System.out.println("\t\tend time:" + c.get(Calendar.YEAR) + '.' + (c.get(Calendar.MONTH) + 1) + '.' + c.get(Calendar.DAY_OF_MONTH) + ' ' + c.get(Calendar.HOUR_OF_DAY) + ':' + c.get(Calendar.MINUTE)); } } diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeBar.java b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeBar.java index a2398e5bbe65a423ea98127cab134ba1eb86a9ea..7cb39b9c7d097c0c33a9d94117224ec7f7db6f67 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeBar.java +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeBar.java @@ -43,10 +43,10 @@ public class TimeBar { protected boolean takeUp(long start, long end) { int startIndex = getBarIndex(start); int endIndex = getBarIndex(end); - if (isTaken(startIndex, endIndex)) { - //已经被占用,不可重复占用 - return false; - } +// if (isTaken(startIndex, endIndex)) { +// //已经被占用,不可重复占用 +// return false; +// } for (int i = startIndex; i < endIndex; i++) { barArray[i] = TimeTableConfig.notAvailable; } diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeTable.java b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeTable.java index 5cff988cfbd9e116de5053ccf6a38aae9b72f553..06610619713c29ea734fabb71643afeb97ece5be 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeTable.java +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/TimeTable/TimeTable.java @@ -4,6 +4,7 @@ package org.kannaj.slackerkiller.backend.AlgorithmModel.TimeTable; import org.kannaj.slackerkiller.backend.AlgorithmModel.TaskStructure.AlgoTask; import org.kannaj.slackerkiller.backend.AlgorithmModel.Utils.TimeTableConfig; +import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -22,12 +23,10 @@ public class TimeTable { private TimeBar[] timeTable; private Calendar weekBegin; private Calendar taskBeginTime; + private int taskBeginDay; public TimeTable() { - timeTable = new TimeBar[7]; - for (int i = 0; i < 7; i++) { - timeTable[i] = new TimeBar(); - } + createNewTable(); //setWeekTime(System.currentTimeMillis()); } @@ -44,19 +43,31 @@ public class TimeTable { tasklist.add(task1); tasklist.add(task2); test.fitTasklist(tasklist); - */ } + public void createNewTable(){ + timeTable = new TimeBar[7]; + for (int i = 0; i < 7; i++) { + timeTable[i] = new TimeBar(); + } + + } + /** * @param tasklist * @return * @brief 无条件fit timetable */ public boolean fitTasklist(List tasklist, int taskBegin) { + if (tasklist.isEmpty()) return true; setWeekTime(tasklist.get(0).getTimeBegin(), taskBegin); + taskBeginDay = taskBegin; + + List rest = restTime(); + tasklist.addAll(rest); for (AlgoTask task : tasklist) { Calendar day = Calendar.getInstance(); day.setTimeInMillis(task.getTimeBegin()); //获得开始时间 @@ -69,6 +80,29 @@ public class TimeTable { return true; } + private List restTime(){ + List rest = new ArrayList(); + long lunch = 1000 * 60 * 60 * 12; + long dinner = 1000 * 60 * 60 *18; + long day = 1000 * 60 * 60 * 24; + long duration = 1000 * 60 * 85;//休息一个半小时 + for (int i=0 ;i<7;i++){ + AlgoTask lunchTask = new AlgoTask("lunch", 1000, "lunchTime", "data", 1, 0, 1, 0, "here", 1, 0); + lunchTask.setTimeBegin(weekBegin.getTimeInMillis() + day * i + lunch); + lunchTask.setTimeEnd(weekBegin.getTimeInMillis() + day * i + lunch + duration); + + AlgoTask dinnerTask = new AlgoTask("dinner", 1001, "dinnerTime", "data", 1, 0, 1, 0, "here", 1, 0); + dinnerTask.setTimeBegin(weekBegin.getTimeInMillis() + day * i + dinner); + dinnerTask.setTimeEnd(weekBegin.getTimeInMillis() + day * i + dinner + duration); + + rest.add(lunchTask); + rest.add(dinnerTask); + } + return rest; + } + + public int getTaskBeginDay(){return taskBeginDay;} + /*根据已经fit了course的timebar,转换成三维数组*/ public int[][][] getFreeTimeArray() { //目前minTimeBar是5min @@ -94,8 +128,10 @@ public class TimeTable { int hour = j; int minutes = k * TimeTableConfig.minTimeBar; long secinmills = minutes * 60 * 1000; + + // long time = taskBeginTime.getTimeInMillis() + day * 24 * 60 * 60 * 1000 + hour * 60 * 60 * 1000 + (minutes) * 60 * 1000; - long time = taskBeginTime.getTimeInMillis() + day * 24 * 60 * 60 * 1000 + hour * 60 * 60 * 1000 + (minutes) * 60 * 1000; + long time = weekBegin.getTimeInMillis() + day * 24 * 60 * 60 * 1000 + hour * 60 * 60 * 1000 + (minutes) * 60 * 1000; // Calendar c = Calendar.getInstance(); // c.setTimeInMillis(time);//获得某一周的具体时间 diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Trans/Translation.java b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Trans/Translation.java index d62867678553ca0bef41c159e5121a0b022131fe..e869f4f1ff7039dad710be6b8add20c80b1769cc 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Trans/Translation.java +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Trans/Translation.java @@ -12,9 +12,9 @@ import java.util.List; public class Translation { public static void main(String[] args) { - Event event1 = new Event(1, "event1", 0, 0, 1000 * 60 * 60, true); - Event event2 = new Event(2, "event2", 0, 0, 1000 * 60 * 60 * 1, true); - Event event3 = new Event(3, "event3", 0, 0, 1000 * 60 * 60 * 3, true); + Event event1 = new Event(1, "event1", 0, 0, 1000 * 60 * 60, true,0,0); + Event event2 = new Event(2, "event2", 0, 0, 1000 * 60 * 60 * 1, true,0,0); + Event event3 = new Event(3, "event3", 0, 0, 1000 * 60 * 60 * 3, true,0,0); List events = new ArrayList(); events.add(event1); @@ -22,12 +22,12 @@ public class Translation { events.add(event3); - Event e1 = new Event(11, "course1", System.currentTimeMillis(), System.currentTimeMillis() + 1000 * 60 * 60, 0, false); + Event e1 = new Event(11, "course1", System.currentTimeMillis(), System.currentTimeMillis() + 1000 * 60 * 60, 0, false,0,0); List es = new ArrayList(); es.add(e1); Translation translation = new Translation(); - List result = translation.Run(es, events, 2); + List result = translation.Run(es, events, 2,7,17); Calendar c = Calendar.getInstance(); for (Event event : result) { c.clear(); @@ -51,6 +51,8 @@ public class Translation { long time = d / (1000 * 60); task.setTime(time); task.setTaskFeature(0); + task.setEmergencyDegree(event.getPriority()); + task.setMatterDegree(event.getImportance()); } else { task.setTimeBegin(event.getBeginTime()); task.setTimeEnd(event.getEndTime()); @@ -73,7 +75,9 @@ public class Translation { task.getTimeBegin(), task.getTimeEnd(), task.getTimeEnd() - task.getTimeBegin(), - task.getTaskFeature() == 0 ? true : false + task.getTaskFeature() == 0 ? true : false, + task.getEmergencyDegree(), + task.getMatterDegree() ); events.add(event); } @@ -84,15 +88,30 @@ public class Translation { * @param courses 要求list内至少有一个event,来确定在哪一周 * @param tasks * @param begin 从第几天开始拍任务,1表示从星期一开始 + * @param preferBegin 偏好开始时间,24小时制 + * @param preferEnd 偏好结束时间,24小时制 * @return 只返回一个排好的event(task)的list,不返回course的list */ - public List Run(List courses, List tasks, int begin) { + public List Run(List courses, List tasks, int begin,int preferBegin,int preferEnd) { List taskList_courses = transToTask(courses); List taskList_tasks = transToTask(tasks); Distributer distributer = new Distributer(); + distributer.run(taskList_tasks, taskList_courses, begin,preferBegin,preferEnd); + return transToEvent(taskList_tasks); + } + /** + * @brief 重载函数,没有偏好时间的话就用这个。这个函数是为了兼容之前的代码不出错 + * @param courses + * @param tasks + * @param begin + * @return + */ + public List Run(List courses, List tasks, int begin) { + List taskList_courses = transToTask(courses); + List taskList_tasks = transToTask(tasks); + Distributer distributer = new Distributer(); distributer.run(taskList_tasks, taskList_courses, begin); - return transToEvent(taskList_tasks); } diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Utils/TimeTableConfig.java b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Utils/TimeTableConfig.java index a84f4e322d7673475492faa520b344d3034a3f38..d077747ac72caa2c9b7f93b48d3113dc43e80654 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Utils/TimeTableConfig.java +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/backend/AlgorithmModel/Utils/TimeTableConfig.java @@ -3,7 +3,7 @@ package org.kannaj.slackerkiller.backend.AlgorithmModel.Utils; public class TimeTableConfig { /*TimeBar的参数*/ public static int minTimeBar = 5; //最小可分配的时间是10min。一天有24*60=1440min - public static int BarCount = 1400 / minTimeBar; //数组shape为[barCount,]的vector + public static int BarCount = 1440 / minTimeBar; //数组shape为[barCount,]的vector public static int available = 0; //0表示可用 public static int notAvailable = 1; //1表示不可用 diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/events/EventRepository.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/events/EventRepository.kt index 70e46e5c115af77e29a0781ead4d1e78ed437006..33cb11f819aa3af296a32e67b34b13ee39ac466a 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/events/EventRepository.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/events/EventRepository.kt @@ -4,7 +4,6 @@ import android.util.Log import androidx.lifecycle.LiveData import org.kannaj.slackerkiller.model.eventmodel.Event import org.kannaj.slackerkiller.model.eventmodel.EventDao -import java.time.Year import java.util.* class EventRepository(private val eventDao: EventDao) { @@ -37,16 +36,17 @@ class EventRepository(private val eventDao: EventDao) { return eventDao.getEventByBeginEnd(beginTime, endTime) } - suspend fun getEventByWeekBlocked(year: Int, weekOfYear: Int): List { - val calendar = Calendar.getInstance() - calendar.clear() - calendar.firstDayOfWeek = Calendar.MONDAY - calendar.set(Calendar.YEAR, year) - calendar.set(Calendar.WEEK_OF_YEAR, weekOfYear) + suspend fun getEventByWeekBlocked(weekBeginTime: Long): List { + val calendar = Calendar.getInstance().also { + it.clear() + it.firstDayOfWeek = Calendar.MONDAY + it.timeInMillis = weekBeginTime + } + Log.i(TAG, "getEventByWeekBlocked: begin ${calendar.time}") val beginTime = calendar.timeInMillis calendar.add(Calendar.WEEK_OF_YEAR, 1) val endTime = calendar.timeInMillis - + Log.i(TAG, "getEventByWeekBlocked: end ${calendar.time}") return eventDao.getEventByBeginEndBlocked(beginTime, endTime) } @@ -55,4 +55,6 @@ class EventRepository(private val eventDao: EventDao) { suspend fun insert(event: Event) = eventDao.insert(event) suspend fun deleteAll() = eventDao.deleteAll() + + suspend fun delete(event: Event) = eventDao.delete(event) } \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/tasks/TaskRepository.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/tasks/TaskRepository.kt index 56673382331d2358e406d5c7c39626a786500aec..f7bc8a4cf375305b2e924676c74ea522360361a1 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/tasks/TaskRepository.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/data/tasks/TaskRepository.kt @@ -14,4 +14,6 @@ class TaskRepository(private val taskDao: TaskDao) { suspend fun addTask(task: Task) = taskDao.insert(task) suspend fun deleteTask(task: Task) = taskDao.delete(task) + + suspend fun update(task: Task) = taskDao.update(task) } \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/Event.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/Event.kt index 768f2b14ffbf01e23a6c48d783ad30c70efd6cd8..bfa011fe4f20f60c903b36d9489d5728487dabc3 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/Event.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/Event.kt @@ -6,10 +6,13 @@ import androidx.room.PrimaryKey @Entity(tableName = "event_table") data class Event( - @PrimaryKey(autoGenerate = true) val id: Int, + @PrimaryKey(autoGenerate = true) val id: Int = 0, @ColumnInfo(name = "name") val name: String, - @ColumnInfo(name = "begin_time")val beginTime: Long, - @ColumnInfo(name = "end_time")val endTime: Long, + @ColumnInfo(name = "begin_time")val beginTime: Long = 0, + @ColumnInfo(name = "end_time")val endTime: Long = 0, @ColumnInfo(name = "duration")val duration: Long, - @ColumnInfo(name = "is_task")val isTask: Boolean + @ColumnInfo(name = "is_task")val isTask: Boolean, + + @ColumnInfo(name = "priority")val priority: Int = 0, + @ColumnInfo(name = "importance")val importance: Int = 0, ) \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/EventDatabase.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/EventDatabase.kt index 3252edc04804b12f595735c44c338111da8006d4..797d19d6f7c742f8d3d251dda043de472158cb7f 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/EventDatabase.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/eventmodel/EventDatabase.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.launch import java.util.* -@Database(entities = [Event::class], version = 1, exportSchema = false) +@Database(entities = [Event::class], version = 2, exportSchema = false) abstract class EventDatabase : RoomDatabase() { abstract fun eventDao(): EventDao @@ -41,7 +41,7 @@ abstract class EventDatabase : RoomDatabase() { context.applicationContext, EventDatabase::class.java, "event_table" - ).build() + ).addCallback(EventDatabaseCallback (scope)).build() INSTANCE = instance instance } @@ -55,20 +55,54 @@ suspend fun genEvents(dao: EventDao) { val cal = Calendar.getInstance() cal.clear() - var i = -1 - while (i < 2) { + var i = -2 + while (i < 4) { + //Monday cal.set(2021, 11, 13 + i * 7, 10, 10) genSingleEvent(dao, cal) + + //Tuesday + cal.set(2021, 11, 14 + i * 7, 14, 0) + genSingleEvent(dao, cal) + + cal.set(2021, 11, 14 + i * 7, 16, 0) + genSingleEvent(dao, cal) + + cal.set(2021, 11, 14 + i * 7, 18, 30) + genSingleEvent(dao, cal) + + cal.set(2021, 11, 14 + i * 7, 20, 10) + genSingleEvent(dao, cal) + + + + //WendensDay cal.set(2021, 11, 15 + i * 7, 14, 0) genSingleEvent(dao, cal) + cal.set(2021, 11, 15 + i * 7, 16, 0) + genSingleEvent(dao, cal) + + + //Thursday cal.set(2021, 11, 16 + i * 7, 10, 10) genSingleEvent(dao, cal) + + //Friday + cal.set(2021, 11, 17 + i * 7, 14, 0) + genSingleEvent(dao, cal) + + cal.set(2021, 11, 17 + i * 7, 16, 0) + genSingleEvent(dao, cal) + cal.set(2021, 11, 17 + i * 7, 18, 30) genSingleEvent(dao, cal) + cal.set(2021, 11, 17 + i * 7, 20, 10) + genSingleEvent(dao, cal) + i++ } @@ -79,8 +113,13 @@ suspend fun genSingleEvent(dao: EventDao, cal: Calendar) { "Operating System", "Software Engineering", "Operating System Lab", + "Algorithm Lab", + "BigData-Processing", + "BigData-Processing Lab", + "BigData-Analysis", + "BigData-Analysis Lab" ) - val duration = 1440000L + val duration = 6000000L dao.insert( Event( id = 0, diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/Task.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/Task.kt index af9f48a35123893db32081a3c03311eff30d1004..24f79dfebfe49b60fb2a7090714ad9834c0169a5 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/Task.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/Task.kt @@ -6,10 +6,12 @@ import androidx.room.PrimaryKey @Entity(tableName = "task_table") data class Task( - @PrimaryKey(autoGenerate = true) val id: Int, - @ColumnInfo(name = "week_id") var weekId: Long, - @ColumnInfo(name = "name") val name: String, - @ColumnInfo(name = "is_completed") val isCompleted: Boolean + @PrimaryKey(autoGenerate = true) val id: Int = 0, + @ColumnInfo(name = "week_id") var weekId: Long = -1L, + @ColumnInfo(name = "name") var name: String, + @ColumnInfo(name = "is_completed") var isCompleted: Boolean = false, + @ColumnInfo(name = "order") var order: Int = 0, + @ColumnInfo(name = "duration") var duration: Long, ) // map String -> list diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/TaskDao.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/TaskDao.kt index ceb6bbb1837471202a8e0f5c97fde8aae4d013a0..52f6f23c7aadd798b51d08677fd93f698787a7ee 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/TaskDao.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/model/taskmodel/TaskDao.kt @@ -1,11 +1,8 @@ package org.kannaj.slackerkiller.model.taskmodel import androidx.lifecycle.LiveData -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert +import androidx.room.* import androidx.room.OnConflictStrategy.REPLACE -import androidx.room.Query @Dao interface TaskDao { @@ -15,7 +12,7 @@ interface TaskDao { @Query("SELECT * FROM task_table WHERE week_id == :weekId") fun getWeekTasks(weekId : Long): LiveData> - @Query("SELECT * FROM task_table WHERE week_id == :weekId") + @Query("SELECT * FROM task_table WHERE week_id == :weekId ORDER BY `order` ASC") suspend fun getAWeek(weekId : Long): List @Insert(onConflict = REPLACE) @@ -23,4 +20,7 @@ interface TaskDao { @Delete suspend fun delete(task: Task) + + @Update + suspend fun update(task: Task) } \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoList.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoList.kt index 3b9855b8816bcfe8bdd7b29cc2f9cb874481dc03..e64969f0f97817fefcc30f5d453f36ce9a385bbb 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoList.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoList.kt @@ -6,23 +6,29 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.outlined.CheckCircle -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue +import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog import androidx.navigation.NavController -import kotlinx.coroutines.launch +import com.google.accompanist.insets.* +import org.burnoutcrew.reorderable.detectReorderAfterLongPress +import org.burnoutcrew.reorderable.draggedItem +import org.burnoutcrew.reorderable.rememberReorderState +import org.burnoutcrew.reorderable.reorderable import org.kannaj.slackerkiller.model.taskmodel.Task import org.kannaj.slackerkiller.ui.theme.SlackerKillerTheme - +import com.google.accompanist.insets.ui.Scaffold +import com.google.accompanist.insets.ui.TopAppBar +import org.kannaj.slackerkiller.ui.widgets.DurationDialog +import org.kannaj.slackerkiller.ui.widgets.TodoEditEntry @Composable fun TodoActivityScreen( @@ -32,63 +38,287 @@ fun TodoActivityScreen( ) { LaunchedEffect(weekId) { - todoListViewModel.updateList(weekId!!) + todoListViewModel.refreshList(weekId!!) } val items: List by todoListViewModel.allTasks.observeAsState(listOf()) + + val arrangeCompleted by todoListViewModel.arrangeCompleted.observeAsState(false) + val waitingArrange by todoListViewModel.waitingArrange.observeAsState(false) + + if (arrangeCompleted) { + AlertDialog( + onDismissRequest = { todoListViewModel.arrangeCompleted.value = false }, + text = { + Text(text = "已完成", fontSize = 16.sp) + }, + buttons = { + Row(modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.End) { + Button(onClick = { + todoListViewModel.arrangeCompleted.value = false + }) { + Text("确定") + } + } + } + ) + } + if (waitingArrange) { + Dialog(onDismissRequest = { }) { + Text("等待中...") + } + } + + + TodoScreen( items = items, onAddItem = todoListViewModel::addTask, onRemoveItem = todoListViewModel::deleteTask, - navController = navController + navController = navController, + onReOrdered = todoListViewModel::onReorder, + onItemCheck = todoListViewModel::onItemChecked, + onArrange = { + todoListViewModel.pressToArrange() + } ) } +@Composable +fun TodoDetail( + taskId: Int, + navController: NavController, + todoListViewModel: TodoListViewModel, +) { + + val task = remember { + mutableStateOf(Task(name = "", duration = 0L)) + } + + val (pickerValue, setPickerVal) = remember { mutableStateOf(0) } + val (text, setText) = remember { mutableStateOf(TextFieldValue()) } + val (openDialog, setDialogOpen) = remember { mutableStateOf(false) } + + LaunchedEffect(taskId) { + task.value = todoListViewModel.getTaskById(taskId) + val pickerNum = (task.value.duration / (5 * 60 * 1000)).toInt() // value / 5 min + setPickerVal(pickerNum) + setText(text.copy(text = task.value.name)) + } + + Scaffold( + topBar = { + // We use TopAppBar from accompanist-insets-ui which allows us to provide + // content padding matching the system bars insets. + TopAppBar( + title = { + Text("Details") + }, + backgroundColor = MaterialTheme.colors.surface, + contentPadding = rememberInsetsPaddingValues( + LocalWindowInsets.current.statusBars, + applyBottom = false, + ), + modifier = Modifier.fillMaxWidth(), + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon(Icons.Default.ArrowBack, "back") + } + } + ) + }, + ) { contentPadding -> + Box(modifier = Modifier.padding(contentPadding)) { + + Column(modifier = Modifier.fillMaxWidth()) { + TodoEditEntry( + pickerValue = pickerValue, + setPickerVal = setPickerVal, + text = text, + setText = setText, + editIsShow = true, + setEditIsShow = {}, + setDialogOpen = setDialogOpen, + onCommit = { + task.value.duration = pickerValue * 5 * 60 * 1000L + task.value.name = text.text + todoListViewModel.updateTask(task.value) + navController.popBackStack() + } + ) + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + IconButton( + onClick = { + todoListViewModel.deleteTask(task.value) + navController.popBackStack() + }, + modifier = Modifier.padding(8.dp) + ) { + Icon(Icons.Default.Delete, "delete") + } + } + } + + DurationDialog(openDialog, setDialogOpen, pickerValue, setPickerVal) + + } + } +} @Composable fun TodoScreen( items: List, onAddItem: (Task) -> Unit, onRemoveItem: (Task) -> Unit, - navController: NavController + navController: NavController, + onReOrdered: (Int, Int) -> Unit, + onItemCheck: (Task) -> Unit, + onArrange: () -> Unit ) { + val (editIsShow, setEditIsShow) = remember { mutableStateOf(false) } + val (openDialog, setDialogOpen) = remember { mutableStateOf(false) } + val (pickerValue, setPickerVal) = remember { mutableStateOf(0) } + val (text, setText) = remember { mutableStateOf(TextFieldValue()) } + + val arrangeDialog = remember { mutableStateOf(false) } + + if (arrangeDialog.value) { + AlertDialog( + onDismissRequest = { arrangeDialog.value = false }, + text = { + Text(text = "是否确定安排?", fontSize = 16.sp) + }, + buttons = { + Row(modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.End) { + Button(onClick = { + arrangeDialog.value = false + onArrange() + }) { + Text("确定") + } + } + + } + ) + } + + Scaffold( topBar = { - TopAppBar { - Row() { - IconButton(onClick = { - navController.popBackStack() - }) { + // We use TopAppBar from accompanist-insets-ui which allows us to provide + // content padding matching the system bars insets. + TopAppBar( + title = { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text("Plan for this week") + Spacer(modifier = Modifier.weight(1f)) + IconButton(onClick = { arrangeDialog.value = true }) { + Icon(Icons.Default.Send, "arrange") + } + } + + }, + backgroundColor = MaterialTheme.colors.surface, + contentPadding = rememberInsetsPaddingValues( + LocalWindowInsets.current.statusBars, + applyBottom = false, + ), + modifier = Modifier.fillMaxWidth(), + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { Icon(Icons.Default.ArrowBack, "back") } } + ) + }, + bottomBar = { + Surface(elevation = 1.dp) { + TodoEditEntry( + pickerValue, + setPickerVal, + text, + setText, + editIsShow, + setEditIsShow, + setDialogOpen, + onCommit = { + val task = + Task(name = text.text, duration = pickerValue * 5 * 60 * 1000L) + onAddItem(task) + } + ) } }, - floatingActionButton = { AddButton(onAddItem) } - ) { - Column(modifier = Modifier.fillMaxWidth()) { + floatingActionButton = { + if (!editIsShow) { + FloatingActionButton( + onClick = { + setEditIsShow(true) + }, + ) { + Icon(imageVector = Icons.Default.Add, contentDescription = "Add") + } + } + }, + modifier = Modifier.fillMaxSize() + ) { contentPadding -> + Column( + modifier = Modifier.fillMaxWidth() + ) { + val state = rememberReorderState() val padding = 8.dp Spacer(modifier = Modifier.size(padding)) LazyColumn( - Modifier + contentPadding = contentPadding, + state = state.listState, + modifier = Modifier .padding(horizontal = padding) .fillMaxWidth() + .reorderable(state, { from, to -> + onReOrdered(from.index, to.index) + }) + .weight(1f) ) { - items(items) { task: Task -> - TodoTask(task = task, onRemoveItem = onRemoveItem) + items(items, { it.id }) { task: Task -> + Box( + modifier = Modifier + .fillMaxWidth() + .draggedItem(state.offsetByKey(task.id)) + .detectReorderAfterLongPress(state) + ) { + val (isChecked, setIsChecked) = remember { mutableStateOf(task.isCompleted) } + TodoTask( + task = task, + isChecked = isChecked, + setIsCheck = setIsChecked, + onItemCheck = onItemCheck, + onRemoveItem = onRemoveItem, + onClickAction = { id -> + navController.navigate("todoDetail/$id") + } + ) + } } } } - + //dialog + DurationDialog(openDialog, setDialogOpen, pickerValue, setPickerVal) } } -@Composable -fun AddButton(onAddItem: (Task) -> Unit) { - FloatingActionButton(onClick = { onAddItem(genRandom()) }) { - Icon(imageVector = Icons.Default.Add, contentDescription = "Add") - } -} fun genRandom(): Task { val message = listOf( @@ -102,12 +332,19 @@ fun genRandom(): Task { "Build stateless composables", "Use state from stateless composables" ).random() - return Task(0, 0, message, false) + return Task(0, 0, message, false, 0, 0) } @Composable -fun TodoTask(task: Task, onRemoveItem: (Task) -> Unit) { +fun TodoTask( + task: Task, + isChecked: Boolean, + setIsCheck: (Boolean) -> Unit, + onItemCheck: (Task) -> Unit, + onClickAction: (Int) -> Unit, + onRemoveItem: (Task) -> Unit +) { val cardPadding = 2.dp Card( elevation = 4.dp, @@ -115,7 +352,7 @@ fun TodoTask(task: Task, onRemoveItem: (Task) -> Unit) { .padding(cardPadding) .fillMaxWidth() .clickable { - onRemoveItem(task) + onClickAction(task.id) } ) { val rowPadding = 16.dp @@ -124,11 +361,29 @@ fun TodoTask(task: Task, onRemoveItem: (Task) -> Unit) { modifier = Modifier.padding(rowPadding) ) { val spacerPadding = 4.dp - Icon( - Icons.Outlined.CheckCircle, - contentDescription = "", - modifier = Modifier.size(30.dp), - ) + if (isChecked) { + Icon( + Icons.Outlined.RadioButtonChecked, + contentDescription = "", + modifier = Modifier + .size(30.dp) + .clickable { + onItemCheck(task) + setIsCheck(false) + }, + ) + } else { + Icon( + Icons.Outlined.RadioButtonUnchecked, + contentDescription = "", + modifier = Modifier + .size(30.dp) + .clickable { + onItemCheck(task) + setIsCheck(true) + }, + ) + } Spacer(modifier = Modifier.padding(spacerPadding)) Text(text = task.name) } @@ -136,27 +391,10 @@ fun TodoTask(task: Task, onRemoveItem: (Task) -> Unit) { } } -@Composable -fun TodoPage(taskList: List) { - Scaffold( - topBar = { - TopAppBar { - Row() { - Spacer(modifier = Modifier.size(8.dp)) - Icon(Icons.Default.ArrowBack, contentDescription = "back") - } - } - }, - backgroundColor = MaterialTheme.colors.primary - ) { -// TodoList(list = taskList) - } -} - @Preview(showBackground = true) @Composable fun DefaultPreview() { SlackerKillerTheme { -// TodoPage(taskList = TaskData.tasks) + } } \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoListViewModel.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoListViewModel.kt index 658e15f057d613419982c51c848377433b55d5e0..e971789317faf32a6278d0aaf60bda78aab9e850 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoListViewModel.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/todolist/TodoListViewModel.kt @@ -3,22 +3,29 @@ package org.kannaj.slackerkiller.ui.todolist import android.util.Log import androidx.lifecycle.* import kotlinx.coroutines.launch +import org.burnoutcrew.reorderable.move import org.kannaj.slackerkiller.backend.AlgorithmModel.Trans.Translation +import org.kannaj.slackerkiller.data.events.EventRepository import org.kannaj.slackerkiller.data.tasks.TaskRepository +import org.kannaj.slackerkiller.model.eventmodel.Event import org.kannaj.slackerkiller.model.taskmodel.Task +import java.util.* -class TodoListViewModel(private val repository: TaskRepository) : ViewModel(){ +class TodoListViewModel( + private val taskRepository: TaskRepository, + private val eventRepository: EventRepository +) : ViewModel() { val TAG = "TodoListViewModel" private val _allTasks = MutableLiveData>() - val allTasks : LiveData> = _allTasks + val allTasks: LiveData> = _allTasks private var weekId: Long = -1 - fun updateList(weekId: Long){ + fun refreshList(weekId: Long) { this.weekId = weekId viewModelScope.launch { - _allTasks.value = repository.getAWeek(weekId) + _allTasks.value = taskRepository.getAWeek(weekId) } } @@ -28,27 +35,169 @@ class TodoListViewModel(private val repository: TaskRepository) : ViewModel(){ return } task.weekId = weekId + + task.order = 0 + _allTasks.value?.let { + task.order = it.size + } + viewModelScope.launch { - repository.addTask(task) - _allTasks.value = repository.getAWeek(weekId) - // TODO: 2021/12/18 make a arraylist in RAM + taskRepository.addTask(task) + _allTasks.value = taskRepository.getAWeek(weekId) } } fun deleteTask(task: Task) { + _allTasks.value = _allTasks.value?.toMutableList().also { + it?.remove(task) + } + + viewModelScope.launch { + taskRepository.deleteTask(task) + updateItemOrder() + } + } + + private suspend fun updateItemOrder() { + _allTasks.value?.toMutableList()?.let { + var order = 0 + it.forEach { task -> + task.order = order + taskRepository.update(task) + order++ + } + } + } + + fun onReorder(from: Int, to: Int) { + _allTasks.value = _allTasks.value?.toMutableList().also { + it?.move(from, to) + } + + viewModelScope.launch { + updateItemOrder() + } + } + + fun onItemChecked(task: Task) { + val isChecked = !task.isCompleted + task.isCompleted = isChecked + + viewModelScope.launch { + taskRepository.update(task) + } + + _allTasks.value = _allTasks.value?.toMutableList().also { + it?.let { + it[task.order].isCompleted = isChecked + } + } + + } + + fun getTaskById(taskId: Int): Task { + Log.i(TAG, "getTaskById: enter") + _allTasks.value?.forEach { + if (it.id == taskId) { + Log.i(TAG, "getTaskById: find ${it.id}") + return it + } + } + return Task(name = "", duration = 0L) + } + + fun updateTask(task: Task) { + viewModelScope.launch { + taskRepository.update(task) + } + } + + // core func to arrange task into schedule + private suspend fun getWeekEvent(): List { + val cal = Calendar.getInstance() + cal.clear() + cal.timeInMillis = weekId + cal.firstDayOfWeek = Calendar.MONDAY + + val year = cal.get(Calendar.YEAR) + val weekOfYear = cal.get(Calendar.WEEK_OF_YEAR) + Log.i(TAG, "getWeekEvent: ${cal.time}") + cal.add(Calendar.WEEK_OF_YEAR, 1) + Log.i(TAG, "getWeekEvent: ${cal.time}") + var res = eventRepository.getEventByWeekBlocked(weekBeginTime = weekId) + + // 删去之前排过的情况 + res.forEach { + Log.i(TAG, "getWeekEvent: before:$it") + if(it.isTask) { + eventRepository.delete(it) + } + } + res = res.filter { + !it.isTask + } + res.forEach { + Log.i(TAG, "getWeekEvent: $it") + } + + return if (res.isEmpty()) { + listOf( + Event( + name = "", + beginTime = weekId, + endTime = weekId, + duration = 0L, + isTask = false + ) + ) + } else { + res + } + } + + private fun taskToEvent(): List { + // 前端保证不为null? + return _allTasks.value!!.map { task -> + Event(name = task.name, duration = task.duration, isTask = true) + } + } + + val arrangeCompleted = MutableLiveData(false) + val waitingArrange = MutableLiveData(false) + + fun pressToArrange(){ viewModelScope.launch { - repository.deleteTask(task) - _allTasks.value = repository.getAWeek(weekId) + waitingArrange.value = true + + val taskList = taskToEvent() + val eventList = getWeekEvent() + val func = Translation() + + val resList = func.Run(eventList, taskList, 1) + Log.i(TAG, "pressToArrange: reslen ${resList.size}") + Log.i(TAG, "pressToArrange: $resList") + resList.forEach { + eventRepository.insert(it) + } + arrangeCompleted.value = true + + waitingArrange.value = false } } + + + } -class TodoListViewModelFactory(private val repository: TaskRepository) : ViewModelProvider.Factory { +class TodoListViewModelFactory( + private val taskRepository: TaskRepository, + private val eventRepository: EventRepository +) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { if (modelClass.isAssignableFrom(TodoListViewModel::class.java)) { @Suppress("UNCHECKED_CAST") - return TodoListViewModel(repository) as T + return TodoListViewModel(taskRepository, eventRepository) as T } throw IllegalArgumentException("Unknown ViewModel class") } diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/Schedule.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/Schedule.kt new file mode 100644 index 0000000000000000000000000000000000000000..80c85fa471f41a0de1bf12567ba2b65bf4106679 --- /dev/null +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/Schedule.kt @@ -0,0 +1,621 @@ +package org.kannaj.slackerkiller.ui.weekpage + +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.ParentDataModifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import kotlin.math.roundToInt + +data class Event( + val name: String, + val color: Color, + val start: LocalDateTime, + val end: LocalDateTime, + val description: String? = null, +) + +inline class SplitType private constructor(val value: Int) { + companion object { + val None = SplitType(0) + val Start = SplitType(1) + val End = SplitType(2) + val Both = SplitType(3) + } +} + +data class PositionedEvent( + val event: Event, + val splitType: SplitType, + val date: LocalDate, + val start: LocalTime, + val end: LocalTime, + val col: Int = 0, + val colSpan: Int = 1, + val colTotal: Int = 1, +) + +val EventTimeFormatter = DateTimeFormatter.ofPattern("h:mm a") + +@Composable +fun BasicEvent( + positionedEvent: PositionedEvent, + modifier: Modifier = Modifier, +) { + val event = positionedEvent.event + val topRadius = + if (positionedEvent.splitType == SplitType.Start || positionedEvent.splitType == SplitType.Both) 0.dp else 4.dp + val bottomRadius = + if (positionedEvent.splitType == SplitType.End || positionedEvent.splitType == SplitType.Both) 0.dp else 4.dp + Column( + modifier = modifier + .fillMaxSize() + .padding( + end = 2.dp, + bottom = if (positionedEvent.splitType == SplitType.End) 0.dp else 2.dp + ) + .clipToBounds() + .background( + event.color, + shape = RoundedCornerShape( + topStart = topRadius, + topEnd = topRadius, + bottomEnd = bottomRadius, + bottomStart = bottomRadius, + ) + ) + .padding(4.dp) + ) { +// Text( +// text = "${event.start.format(EventTimeFormatter)} - ${ +// event.end.format( +// EventTimeFormatter +// ) +// }", +// style = MaterialTheme.typography.caption, +// maxLines = 3, +// overflow = TextOverflow.Clip, +// ) + + Text( + text = event.name, + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Bold, + maxLines = 7, + overflow = TextOverflow.Ellipsis, + fontSize = 10.sp, + ) + + if (event.description != null) { + Text( + text = event.description, + style = MaterialTheme.typography.body2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } +} + +private val sampleEvents = listOf( + Event( + name = "Google I/O Keynote", + color = Color(0xFFAFBBF2), + start = LocalDateTime.parse("2021-05-18T09:00:00"), + end = LocalDateTime.parse("2021-05-18T11:00:00"), + description = "Tune in to find out about how we're furthering our mission to organize the world’s information and make it universally accessible and useful.", + ), + Event( + name = "Developer Keynote", + color = Color(0xFFAFBBF2), + start = LocalDateTime.parse("2021-05-18T09:00:00"), + end = LocalDateTime.parse("2021-05-18T10:00:00"), + description = "Learn about the latest updates to our developer products and platforms from Google Developers.", + ), + Event( + name = "What's new in Android", + color = Color(0xFF1B998B), + start = LocalDateTime.parse("2021-05-18T10:00:00"), + end = LocalDateTime.parse("2021-05-18T11:00:00"), + description = "In this Keynote, Chet Haase, Dan Sandler, and Romain Guy discuss the latest Android features and enhancements for developers.", + ), + Event( + name = "What's new in Material Design", + color = Color(0xFF6DD3CE), + start = LocalDateTime.parse("2021-05-18T11:00:00"), + end = LocalDateTime.parse("2021-05-18T11:45:00"), + description = "Learn about the latest design improvements to help you build personal dynamic experiences with Material Design.", + ), + Event( + name = "What's new in Machine Learning", + color = Color(0xFFF4BFDB), + start = LocalDateTime.parse("2021-05-18T10:00:00"), + end = LocalDateTime.parse("2021-05-18T11:00:00"), + description = "Learn about the latest and greatest in ML from Google. We’ll cover what’s available to developers when it comes to creating, understanding, and deploying models for a variety of different applications.", + ), + Event( + name = "What's new in Machine Learning", + color = Color(0xFFF4BFDB), + start = LocalDateTime.parse("2021-05-18T10:30:00"), + end = LocalDateTime.parse("2021-05-18T11:30:00"), + description = "Learn about the latest and greatest in ML from Google. We’ll cover what’s available to developers when it comes to creating, understanding, and deploying models for a variety of different applications.", + ), + Event( + name = "Jetpack Compose Basics", + color = Color(0xFF1B998B), + start = LocalDateTime.parse("2021-05-20T12:00:00"), + end = LocalDateTime.parse("2021-05-20T13:00:00"), + description = "This Workshop will take you through the basics of building your first app with Jetpack Compose, Android's new modern UI toolkit that simplifies and accelerates UI development on Android.", + ), +) + +class EventsProvider : PreviewParameterProvider { + override val values = sampleEvents.asSequence() +} + +@Preview(showBackground = true) +@Composable +fun EventPreview( + @PreviewParameter(EventsProvider::class) event: Event, +) { + + BasicEvent( + PositionedEvent( + event, + SplitType.None, + event.start.toLocalDate(), + event.start.toLocalTime(), + event.end.toLocalTime() + ), + modifier = Modifier.sizeIn(maxHeight = 64.dp) + ) + +} + +private class EventDataModifier( + val positionedEvent: PositionedEvent, +) : ParentDataModifier { + override fun Density.modifyParentData(parentData: Any?) = positionedEvent +} + +private fun Modifier.eventData(positionedEvent: PositionedEvent) = + this.then(EventDataModifier(positionedEvent)) + +private val DayFormatter = DateTimeFormatter.ofPattern("EE, MMM d") + +@Composable +fun BasicDayHeader( + day: LocalDate, + modifier: Modifier = Modifier, +) { + Text( + text = day.format(DayFormatter), + textAlign = TextAlign.Center, + modifier = modifier + .fillMaxWidth() + .padding(4.dp), + fontSize = 10.sp + ) +} + +@Preview(showBackground = true) +@Composable +fun BasicDayHeaderPreview() { + + BasicDayHeader(day = LocalDate.now()) + +} + +@Composable +fun ScheduleHeader( + minDate: LocalDate, + maxDate: LocalDate, + dayWidth: Dp, + modifier: Modifier = Modifier, + dayHeader: @Composable (day: LocalDate) -> Unit = { BasicDayHeader(day = it) }, +) { + Row(modifier = modifier) { +// val numDays = ChronoUnit.DAYS.between(minDate, maxDate).toInt() + 1 + val numDays = 7 + repeat(numDays) { i -> + Box(modifier = Modifier.width(dayWidth)) { + dayHeader(minDate.plusDays(i.toLong())) + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun ScheduleHeaderPreview() { + + ScheduleHeader( + minDate = LocalDate.now(), + maxDate = LocalDate.now().plusDays(5), + dayWidth = 256.dp, + ) + +} + +private val HourFormatter = DateTimeFormatter.ofPattern("h a") + +@Composable +fun BasicSidebarLabel( + time: LocalTime, + modifier: Modifier = Modifier, +) { + Text( + text = time.format(HourFormatter), + modifier = modifier + .fillMaxHeight() + .padding(4.dp), + fontSize = 10.sp + ) +} + +@Preview(showBackground = true) +@Composable +fun BasicSidebarLabelPreview() { + + BasicSidebarLabel(time = LocalTime.NOON, Modifier.sizeIn(maxHeight = 64.dp)) + +} + +@Composable +fun ScheduleSidebar( + hourHeight: Dp, + modifier: Modifier = Modifier, + minTime: LocalTime = LocalTime.MIN, + maxTime: LocalTime = LocalTime.MAX, + label: @Composable (time: LocalTime) -> Unit = { BasicSidebarLabel(time = it) }, +) { + val numMinutes = ChronoUnit.MINUTES.between(minTime, maxTime).toInt() + 1 + val numHours = numMinutes / 60 + val firstHour = minTime.truncatedTo(ChronoUnit.HOURS) + val firstHourOffsetMinutes = + if (firstHour == minTime) 0 else ChronoUnit.MINUTES.between(minTime, firstHour.plusHours(1)) + val firstHourOffset = hourHeight * (firstHourOffsetMinutes / 60f) + val startTime = if (firstHour == minTime) firstHour else firstHour.plusHours(1) + Column(modifier = modifier) { + Spacer(modifier = Modifier.height(firstHourOffset)) + repeat(numHours) { i -> + Box(modifier = Modifier.height(hourHeight)) { + label(startTime.plusHours(i.toLong())) + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun ScheduleSidebarPreview() { + + ScheduleSidebar(hourHeight = 64.dp) + +} + +private fun splitEvents(events: List): List { + return events + .map { event -> + val startDate = event.start.toLocalDate() + val endDate = event.end.toLocalDate() + if (startDate == endDate) { + listOf( + PositionedEvent( + event, + SplitType.None, + event.start.toLocalDate(), + event.start.toLocalTime(), + event.end.toLocalTime() + ) + ) + } else { + val days = ChronoUnit.DAYS.between(startDate, endDate) + val splitEvents = mutableListOf() + for (i in 0..days) { + val date = startDate.plusDays(i) + splitEvents += PositionedEvent( + event, + splitType = if (date == startDate) SplitType.End else if (date == endDate) SplitType.Start else SplitType.Both, + date = date, + start = if (date == startDate) event.start.toLocalTime() else LocalTime.MIN, + end = if (date == endDate) event.end.toLocalTime() else LocalTime.MAX, + ) + } + splitEvents + } + } + .flatten() +} + +private fun PositionedEvent.overlapsWith(other: PositionedEvent): Boolean { + return date == other.date && start < other.end && end > other.start +} + +private fun List.timesOverlapWith(event: PositionedEvent): Boolean { + return any { it.overlapsWith(event) } +} + +private fun arrangeEvents(events: List): List { + val positionedEvents = mutableListOf() + val groupEvents: MutableList> = mutableListOf() + + fun resetGroup() { + groupEvents.forEachIndexed { colIndex, col -> + col.forEach { e -> + positionedEvents.add(e.copy(col = colIndex, colTotal = groupEvents.size)) + } + } + groupEvents.clear() + } + + events.forEach { event -> + var firstFreeCol = -1 + var numFreeCol = 0 + for (i in 0 until groupEvents.size) { + val col = groupEvents[i] + if (col.timesOverlapWith(event)) { + if (firstFreeCol < 0) continue else break + } + if (firstFreeCol < 0) firstFreeCol = i + numFreeCol++ + } + + when { + // Overlaps with all, add a new column + firstFreeCol < 0 -> { + groupEvents += mutableListOf(event) + // Expand anything that spans into the previous column and doesn't overlap with this event + for (ci in 0 until groupEvents.size - 1) { + val col = groupEvents[ci] + col.forEachIndexed { ei, e -> + if (ci + e.colSpan == groupEvents.size - 1 && !e.overlapsWith(event)) { + col[ei] = e.copy(colSpan = e.colSpan + 1) + } + } + } + } + // No overlap with any, start a new group + numFreeCol == groupEvents.size -> { + resetGroup() + groupEvents += mutableListOf(event) + } + // At least one column free, add to first free column and expand to as many as possible + else -> { + groupEvents[firstFreeCol] += event.copy(colSpan = numFreeCol) + } + } + } + resetGroup() + return positionedEvents +} + +sealed class ScheduleSize { + class FixedSize(val size: Dp) : ScheduleSize() + class FixedCount(val count: Float) : ScheduleSize() { + constructor(count: Int) : this(count.toFloat()) + } + + class Adaptive(val minSize: Dp) : ScheduleSize() +} + +@Composable +fun Schedule( + events: List, + modifier: Modifier = Modifier, + eventContent: @Composable (positionedEvent: PositionedEvent) -> Unit = { + BasicEvent( + positionedEvent = it + ) + }, + dayHeader: @Composable (day: LocalDate) -> Unit = { BasicDayHeader(day = it) }, + timeLabel: @Composable (time: LocalTime) -> Unit = { BasicSidebarLabel(time = it) }, + minDate: LocalDate = events.minByOrNull(Event::start)?.start?.toLocalDate() ?: LocalDate.now(), + maxDate: LocalDate = events.maxByOrNull(Event::end)?.end?.toLocalDate() ?: LocalDate.now(), + minTime: LocalTime = LocalTime.MIN, + maxTime: LocalTime = LocalTime.MAX, + daySize: ScheduleSize = ScheduleSize.FixedSize(70.dp), + hourSize: ScheduleSize = ScheduleSize.FixedSize(64.dp), +) { +// val numDays = ChronoUnit.DAYS.between(minDate, maxDate).toInt() + 1 + val numDays = 7 + val numMinutes = ChronoUnit.MINUTES.between(minTime, maxTime).toInt() + 1 + val numHours = numMinutes.toFloat() / 60f + val verticalScrollState = rememberScrollState() + val horizontalScrollState = rememberScrollState() + var sidebarWidth by remember { mutableStateOf(0) } + var headerHeight by remember { mutableStateOf(0) } + BoxWithConstraints(modifier = modifier) { + val dayWidth: Dp = when (daySize) { + is ScheduleSize.FixedSize -> daySize.size + is ScheduleSize.FixedCount -> with(LocalDensity.current) { ((constraints.maxWidth - sidebarWidth) / daySize.count).toDp() } + is ScheduleSize.Adaptive -> with(LocalDensity.current) { + maxOf( + ((constraints.maxWidth - sidebarWidth) / numDays).toDp(), + daySize.minSize + ) + } + } + val hourHeight: Dp = when (hourSize) { + is ScheduleSize.FixedSize -> hourSize.size + is ScheduleSize.FixedCount -> with(LocalDensity.current) { ((constraints.maxHeight - headerHeight) / hourSize.count).toDp() } + is ScheduleSize.Adaptive -> with(LocalDensity.current) { + maxOf( + ((constraints.maxHeight - headerHeight) / numHours).toDp(), + hourSize.minSize + ) + } + } + Column(modifier = modifier) { + ScheduleHeader( + minDate = minDate, + maxDate = maxDate, + dayWidth = dayWidth, + dayHeader = dayHeader, + modifier = Modifier + .padding(start = with(LocalDensity.current) { sidebarWidth.toDp() }) +// .horizontalScroll(horizontalScrollState) + .onGloballyPositioned { headerHeight = it.size.height } + ) + Row( + modifier = Modifier + .weight(1f) + .align(Alignment.Start) + ) { + ScheduleSidebar( + hourHeight = hourHeight, + minTime = minTime, + maxTime = maxTime, + label = timeLabel, + modifier = Modifier + .verticalScroll(verticalScrollState) + .onGloballyPositioned { sidebarWidth = it.size.width } + ) + BasicSchedule( + events = events, + eventContent = eventContent, + minDate = minDate, + maxDate = maxDate, + minTime = minTime, + maxTime = maxTime, + dayWidth = dayWidth, + hourHeight = hourHeight, + modifier = Modifier + .weight(1f) + .verticalScroll(verticalScrollState) +// .horizontalScroll(horizontalScrollState) + ) + } + } + } +} + +@Composable +fun BasicSchedule( + events: List, + modifier: Modifier = Modifier, + eventContent: @Composable (positionedEvent: PositionedEvent) -> Unit = { + BasicEvent( + positionedEvent = it + ) + }, + minDate: LocalDate = events.minByOrNull(Event::start)?.start?.toLocalDate() ?: LocalDate.now(), + maxDate: LocalDate = events.maxByOrNull(Event::end)?.end?.toLocalDate() ?: LocalDate.now(), + minTime: LocalTime = LocalTime.MIN, + maxTime: LocalTime = LocalTime.MAX, + dayWidth: Dp, + hourHeight: Dp, +) { +// val numDays = ChronoUnit.DAYS.between(minDate, maxDate).toInt() + 1 + val numDays = 7 + val numMinutes = ChronoUnit.MINUTES.between(minTime, maxTime).toInt() + 1 + val numHours = numMinutes / 60 + val dividerColor = if (MaterialTheme.colors.isLight) Color.LightGray else Color.DarkGray + val positionedEvents = + remember(events) { arrangeEvents(splitEvents(events.sortedBy(Event::start))).filter { it.end > minTime && it.start < maxTime } } + Layout( + content = { + positionedEvents.forEach { positionedEvent -> + Box(modifier = Modifier.eventData(positionedEvent)) { + eventContent(positionedEvent) + } + } + }, + modifier = modifier + .drawBehind { + val firstHour = minTime.truncatedTo(ChronoUnit.HOURS) + val firstHourOffsetMinutes = + if (firstHour == minTime) 0 else ChronoUnit.MINUTES.between( + minTime, + firstHour.plusHours(1) + ) + val firstHourOffset = (firstHourOffsetMinutes / 60f) * hourHeight.toPx() + repeat(numHours) { + drawLine( + dividerColor, + start = Offset(0f, it * hourHeight.toPx() + firstHourOffset), + end = Offset(size.width, it * hourHeight.toPx() + firstHourOffset), + strokeWidth = 0.3.dp.toPx() + ) + } + repeat(numDays - 1) { + drawLine( + dividerColor, + start = Offset((it + 1) * dayWidth.toPx(), 0f), + end = Offset((it + 1) * dayWidth.toPx(), size.height), + strokeWidth = 0.3.dp.toPx() + ) + } + } + ) { measureables, constraints -> + val height = (hourHeight.toPx() * (numMinutes / 60f)).roundToInt() + val width = dayWidth.roundToPx() * numDays + val placeablesWithEvents = measureables.map { measurable -> + val splitEvent = measurable.parentData as PositionedEvent + val eventDurationMinutes = + ChronoUnit.MINUTES.between(splitEvent.start, minOf(splitEvent.end, maxTime)) + val eventHeight = ((eventDurationMinutes / 60f) * hourHeight.toPx()).roundToInt() + val eventWidth = + ((splitEvent.colSpan.toFloat() / splitEvent.colTotal.toFloat()) * dayWidth.toPx()).roundToInt() + val placeable = measurable.measure( + constraints.copy( + minWidth = eventWidth, + maxWidth = eventWidth, + minHeight = eventHeight, + maxHeight = eventHeight + ) + ) + Pair(placeable, splitEvent) + } + layout(width, height) { + placeablesWithEvents.forEach { (placeable, splitEvent) -> + val eventOffsetMinutes = if (splitEvent.start > minTime) ChronoUnit.MINUTES.between( + minTime, + splitEvent.start + ) else 0 + val eventY = ((eventOffsetMinutes / 60f) * hourHeight.toPx()).roundToInt() + val eventOffsetDays = ChronoUnit.DAYS.between(minDate, splitEvent.date).toInt() + val eventX = + eventOffsetDays * dayWidth.roundToPx() + (splitEvent.col * (dayWidth.toPx() / splitEvent.colTotal.toFloat())).roundToInt() + placeable.place(eventX, eventY) + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun SchedulePreview() { + + Schedule(sampleEvents) + +} \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/WeekPageViewModel.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/WeekPageViewModel.kt index fe33d052ea33cb25fb3df51f4db2e405c17eccb5..3276f48b61e0f42caa5b47699195061814eec43f 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/WeekPageViewModel.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/WeekPageViewModel.kt @@ -1,6 +1,7 @@ package org.kannaj.slackerkiller.ui.weekpage import android.util.Log +import androidx.compose.runtime.MutableState import androidx.lifecycle.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -32,14 +33,19 @@ class WeekPageViewModel( } } + val currentDay = MutableLiveData(curCal) + companion object { const val MaxWeekNum = 1000 const val InitPage = MaxWeekNum / 2 const val CachedNum = 3 } - private val mapCalendar = Calendar.getInstance().also { cal -> - cal.firstDayOfWeek = Calendar.MONDAY + private val mapCalendar = Calendar.getInstance().also { calendar -> + calendar.clear() + calendar.firstDayOfWeek = Calendar.MONDAY + calendar.set(Calendar.YEAR, rightNowCal.get(Calendar.YEAR)) + calendar.set(Calendar.WEEK_OF_YEAR, rightNowCal.get(Calendar.WEEK_OF_YEAR)) } private val _cachedWeekEvents = Array(CachedNum) { MutableLiveData>() } @@ -55,21 +61,33 @@ class WeekPageViewModel( var currentPage: Int = InitPage - fun documentCurrentPage(curPage: Int) { + private fun documentCurrentPage(curPage: Int) { currentPage = curPage val offset = curPage - InitPage - val year = mapCalendar.get(Calendar.YEAR) - val weekOfYear = mapCalendar.get(Calendar.WEEK_OF_YEAR) - curCal.set(Calendar.YEAR, year) - curCal.set(Calendar.WEEK_OF_YEAR, weekOfYear + offset) + curCal.let { + it.timeInMillis = mapCalendar.timeInMillis + it.add(Calendar.WEEK_OF_YEAR, offset) + } + + Log.i(TAG, "documentCurrentPage: currentPage${currentPage}") + Log.i(TAG, "documentCurrentPage: ${curCal.time}") + + currentDay.value = curCal } fun getCurWeekId() = curCal.timeInMillis - fun flashWeekPage(curPage: Int) { - val offset = curPage - InitPage - val year = mapCalendar.get(Calendar.YEAR) - val weekOfYear = mapCalendar.get(Calendar.WEEK_OF_YEAR) + // make sure you already update the curCal + suspend fun flashWeekPage(curPage: Int) { + + documentCurrentPage(curPage) + + val cal = Calendar.getInstance().also { + it.clear() + it.firstDayOfWeek = Calendar.MONDAY + it.timeInMillis = curCal.timeInMillis + } + cal.add(Calendar.WEEK_OF_YEAR, -1) for (i in -1..1) { val index = (curPage + i) % CachedNum @@ -77,24 +95,27 @@ class WeekPageViewModel( Log.i(TAG, "flashWeekPage: Page:${curPage + i} Hit!") } else { Log.i(TAG, "flashWeekPage: Page:${curPage + i} Miss!") - viewModelScope.launch { - _cachedWeekEvents[index].value = - eventRepository.getEventByWeekBlocked(year, weekOfYear + offset + i) - _cachedPage[index].value = curPage + i - } + _cachedWeekEvents[index].value = + eventRepository.getEventByWeekBlocked(cal.timeInMillis) + _cachedPage[index].value = curPage + i } + cal.add(Calendar.WEEK_OF_YEAR, 1) } } - private suspend fun flashCurrentPage() { - val offset = currentPage - InitPage - val year = mapCalendar.get(Calendar.YEAR) - val weekOfYear = mapCalendar.get(Calendar.WEEK_OF_YEAR) - for (i in -1..1){ + suspend fun flashCurrentPage() { + val cal = Calendar.getInstance().also { + it.clear() + it.firstDayOfWeek = Calendar.MONDAY + it.timeInMillis = curCal.timeInMillis + } + cal.add(Calendar.WEEK_OF_YEAR, -1) + for (i in -1..1) { val index = (currentPage + i) % CachedNum _cachedWeekEvents[index].value = - eventRepository.getEventByWeekBlocked(year, weekOfYear + offset + i) + eventRepository.getEventByWeekBlocked(cal.timeInMillis) _cachedPage[index].value = currentPage + i + cal.add(Calendar.WEEK_OF_YEAR, 1) } } @@ -116,7 +137,7 @@ class WeekPageViewModel( val cal = Calendar.getInstance() cal.clear() - viewModelScope.launch{ + viewModelScope.launch { var i = -4 while (i < 5) { cal.set(2021, 11, 13 + i * 7, 10, 10) diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/Weekpage.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/Weekpage.kt index 898155028927821ef0f4023b3fe112e33edebe32..611dae3c6bcdf4eee1d9d5a2e14a1e60c6d6abf7 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/Weekpage.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/weekpage/Weekpage.kt @@ -1,106 +1,178 @@ package org.kannaj.slackerkiller.ui.weekpage import android.util.Log +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.Button -import androidx.compose.material.Card -import androidx.compose.material.Text +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.FormatListBulleted +import androidx.compose.material.icons.filled.Send import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import com.google.accompanist.insets.LocalWindowInsets +import com.google.accompanist.insets.rememberInsetsPaddingValues +import com.google.accompanist.insets.statusBarsHeight +import com.google.accompanist.insets.ui.Scaffold +import com.google.accompanist.insets.ui.TopAppBar import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState -import com.google.accompanist.pager.rememberPagerState import org.kannaj.slackerkiller.model.eventmodel.Event +import org.kannaj.slackerkiller.model.taskmodel.Task +import org.kannaj.slackerkiller.ui.widgets.TodoEditEntry import org.kannaj.slackerkiller.utils.* +import java.time.LocalDate +import java.time.ZoneId +import java.util.* @ExperimentalPagerApi @Composable -fun ScrollableWeekPage(navController: NavController, weekPageViewModel: WeekPageViewModel, pagerState: PagerState) { +fun ScrollableWeekPage( + navController: NavController, + weekPageViewModel: WeekPageViewModel, + pagerState: PagerState +) { val TAG = "HEYHEY" LaunchedEffect(pagerState) { pagerState.scrollToPage(weekPageViewModel.currentPage) + weekPageViewModel.flashCurrentPage() } LaunchedEffect(pagerState.currentPage) { - weekPageViewModel.documentCurrentPage(pagerState.currentPage) weekPageViewModel.flashWeekPage(pagerState.currentPage) } - Column() { - Row() { - Button( - onClick = { - weekPageViewModel.genEventsForTest() - }, - modifier = Modifier.fillMaxWidth(0.5f) - ) { - Text(text = "Gen") - } + Scaffold( + topBar = { + // We use TopAppBar from accompanist-insets-ui which allows us to provide + // content padding matching the system bars insets. + TopAppBar( + title = { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text("SlackerKiller") + Spacer(modifier = Modifier.weight(1f)) + IconButton(onClick = { + val weekId = weekPageViewModel.getCurWeekId() + Log.i(TAG, "ScrollableWeekPage: navigate weekId = $weekId") + navController.navigate("todoList/$weekId") + }) { + Icon(Icons.Default.FormatListBulleted, "todo") + } + } - Button( - onClick = { - weekPageViewModel.removeAllEvents() }, - modifier = Modifier.fillMaxWidth(0.5f) - ) { - Text(text = "Clear") - } + backgroundColor = MaterialTheme.colors.surface, + contentPadding = rememberInsetsPaddingValues( + LocalWindowInsets.current.statusBars, + applyBottom = false, + ), + modifier = Modifier.fillMaxWidth(), + ) + }, + modifier = Modifier.fillMaxSize() + ) { contentPadding -> - Button( - onClick = { - val weekId = weekPageViewModel.getCurWeekId() - Log.i(TAG, "ScrollableWeekPage: navigate weekId = $weekId") - navController.navigate("todoList/$weekId") - }, - modifier = Modifier.fillMaxWidth(1f) - ) { - Text(text = "todo") - } - } + Box(modifier = Modifier.padding(contentPadding)) { - HorizontalPager( - count = WeekPageViewModel.MaxWeekNum, - state = pagerState, - ) { page -> - val index = page % WeekPageViewModel.CachedNum - val items: List by weekPageViewModel.cachedWeekEvents[index].observeAsState( - listOf() - ) - val cachedPage: Int by weekPageViewModel.cachedPage[index].observeAsState(-1) - - if (cachedPage == page) { - Log.i(TAG, "ScrollableWeekPage: page:$page Hit!") - EventScreen(items = items) - } else { - // flashing screen - Spacer(modifier = Modifier - .fillMaxHeight() - .fillMaxWidth()) - } + HorizontalPager( + count = WeekPageViewModel.MaxWeekNum, + state = pagerState, + ) { page -> + val index = page % WeekPageViewModel.CachedNum + val items: List by weekPageViewModel.cachedWeekEvents[index].observeAsState( + listOf() + ) + val cachedPage: Int by weekPageViewModel.cachedPage[index].observeAsState(-1) + + val firstDayOfWeek: Calendar by weekPageViewModel.currentDay.observeAsState( + Calendar.getInstance().also { + val year = it.get(Calendar.YEAR) + val week = it.get(Calendar.WEEK_OF_YEAR) + it.set(Calendar.YEAR, year) + it.set(Calendar.WEEK_OF_YEAR, week) + }) + + if (cachedPage == page) { + Log.i(TAG, "ScrollableWeekPage: page:$page Hit!") + + val newItems = items.map { + trans(it) + } + Schedule( + events = newItems, daySize = ScheduleSize.FixedSize(45.dp), + minDate = getLocalDate(firstDayOfWeek)!! + ) + + } else { + // flashing screen + Spacer( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth() + ) + } + + } } } } +fun getLocalDate(cal: Calendar): LocalDate? = + cal.time.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + +fun trans(event: Event): org.kannaj.slackerkiller.ui.weekpage.Event { + val calBegin = Calendar.getInstance().also { + it.timeInMillis = event.beginTime + } + val calEnd = Calendar.getInstance().also { + it.timeInMillis = event.endTime + } + val localStart = calBegin.time.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + val localEnd = calEnd.time.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() + + val list = listOf( +// Color(0xFFAFBBF2), + Color(0xFFAFBBF2), +// Color(0xFF1B998B), +// Color(0xFF6DD3CE), +// Color(0xFFF4BFDB), +// Color(0xFF1B998B) + ) + return Event( + name = event.name, + color = list.random(), start = localStart, end = localEnd + ) +} + + @Composable fun EventScreen( items: List, ) { - if(items.isEmpty()) { - Text(text = "没有日程!", modifier = Modifier - .fillMaxWidth() - .fillMaxHeight()) + if (items.isEmpty()) { + Text( + text = "没有日程!", modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + ) } else { val padding = 8.dp LazyColumn( diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/widgets/TodoComponents.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/widgets/TodoComponents.kt new file mode 100644 index 0000000000000000000000000000000000000000..56beca6c2cc8047fc8c2afb15c3076a8c6ebf57b --- /dev/null +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/ui/widgets/TodoComponents.kt @@ -0,0 +1,420 @@ +package org.kannaj.slackerkiller.ui.widgets + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.TweenSpec +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Grade +import androidx.compose.material.icons.filled.PriorityHigh +import androidx.compose.material.icons.outlined.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.chargemap.compose.numberpicker.NumberPicker +import com.google.accompanist.insets.navigationBarsWithImePadding +import org.kannaj.slackerkiller.model.taskmodel.Task +import org.kannaj.slackerkiller.ui.theme.Shapes + +@Composable +fun DurationDialog( + dialogIsOpen: Boolean, + setOpen: (Boolean) -> Unit, + pickerValue: Int, + setPickerVal: (Int) -> Unit +) { + if (dialogIsOpen) { + Dialog( + onDismissRequest = { + setOpen(false) + }, + ) { + Box( + Modifier + .fillMaxWidth(0.8f) + .fillMaxHeight(0.6f) + .background(Color.White) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp), + contentAlignment = Alignment.Center + ) { + NumberPicker( + value = pickerValue, + range = 0..20, + onValueChange = { + setPickerVal(it) + }, + label = { + (it * 5).toString() + } + ) + } + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier + .padding(all = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Button( + onClick = { setOpen(false) } + ) { + Text("${(pickerValue * 5)} 分钟") + } + } + } + + } + + } + + } +} + + +@Composable +fun TodoEditEntry( + pickerValue: Int, + setPickerVal: (Int) -> Unit, + text: TextFieldValue, + setText: (TextFieldValue) -> Unit, + editIsShow: Boolean, + setEditIsShow: (Boolean) -> Unit, + setDialogOpen: (Boolean) -> Unit, + onCommit: () -> Unit +) { + val (durationTime, setDuration) = remember { mutableStateOf(0) } + LaunchedEffect(pickerValue) { + setDuration(pickerValue * 5) + } + + val waringDialog = remember { mutableStateOf(false) } + + val (importanceDialog, setImportance) = remember { mutableStateOf(false) } + val (importancePicker, importanceSeter) = remember { mutableStateOf(0) } + val (priorityDialog, setPriority) = remember { mutableStateOf(false) } + val (priorityPicker, prioritySeter) = remember { mutableStateOf(0) } + + ImportanceDialog( + dialogIsOpen = importanceDialog, setOpen = setImportance, + pickerValue = importancePicker, setPickerVal = importanceSeter + ) + + PriorityDialog( + dialogIsOpen = priorityDialog, setOpen = setPriority, + pickerValue = priorityPicker, setPickerVal = prioritySeter + ) + + + if (editIsShow) { + Column( + modifier = Modifier + .fillMaxWidth() + .navigationBarsWithImePadding() + ) { + val rowPadding = 16.dp + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding( + start = rowPadding, + end = rowPadding, + top = 8.dp + ) + ) { + Icon( + Icons.Outlined.RadioButtonUnchecked, + contentDescription = "", + modifier = Modifier.size(30.dp), + ) + OutlinedTextField( + value = text, + onValueChange = { setText(it) }, + placeholder = { Text(text = "tap your todo...") }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + .weight(1f) + ) + + IconButton( + onClick = { + setEditIsShow(false) + onCommit() + setPickerVal(0) + setText(TextFieldValue()) + }, + enabled = + text.text.isNotEmpty() && durationTime != 0 + ) { + Icon( + Icons.Outlined.ArrowCircleUp, + "send", + modifier = Modifier.size(30.dp) + ) + } + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(start = 8.dp, end = 8.dp) + ) { + Button( + onClick = { setDialogOpen(true) }, colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.White, + contentColor = Color.Black + ) + ) { + Icon(Icons.Outlined.Event, "set duration") + Spacer(modifier = Modifier.padding(4.dp)) + if (durationTime == 0) { + Text("设置预期时长", modifier = Modifier.width(100.dp)) + } else { + Text("${durationTime}分钟", modifier = Modifier.width(100.dp)) + } + } + + Spacer(modifier = Modifier.padding(6.dp)) + + Button( + onClick = { + waringDialog.value = true + }, colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.White, + contentColor = Color.Black + ) + ) { + Icon(Icons.Outlined.Bookmark, "set prefer time") + Spacer(modifier = Modifier.padding(4.dp)) + Text("设置偏好时间") + } + + } + + AnimatedIconRow(visible = text.text.isNotBlank(), modifier = Modifier.fillMaxWidth()) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp) + ) { + Button( + onClick = { setPriority(true) }, colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.White, + contentColor = Color.Black + ) + ) { + Icon(Icons.Filled.PriorityHigh, "set duration") + Spacer(modifier = Modifier.padding(4.dp)) + Text("紧急程度", modifier = Modifier.width(100.dp)) + } + + Spacer(modifier = Modifier.padding(6.dp)) + + Button( + onClick = { + setImportance(true) + }, colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.White, + contentColor = Color.Black + ) + ) { + Icon(Icons.Filled.Grade, "set prefer time") + Spacer(modifier = Modifier.padding(4.dp)) + Text("重要程度") + } + + } + } + } + + if (waringDialog.value) { + Dialog(onDismissRequest = { waringDialog.value = false }) { + Text("尽请期待(´・ω・`) ") + } + } + + } +} + + +@Composable +fun ImportanceDialog( + dialogIsOpen: Boolean, + setOpen: (Boolean) -> Unit, + pickerValue: Int, + setPickerVal: (Int) -> Unit +) { + if (dialogIsOpen) { + Dialog( + onDismissRequest = { + setOpen(false) + }, + ) { + Box( + Modifier + .fillMaxWidth(0.8f) + .fillMaxHeight(0.6f) + .background(Color.White) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp), + contentAlignment = Alignment.Center + ) { + NumberPicker( + value = pickerValue, + range = 0..3, + onValueChange = { + setPickerVal(it) + }, + label = { + when (it) { + 0 -> "无" + 1 -> "不重要" + 2 -> "一般" + 3 -> "重要" + else -> "bug" + } + } + ) + } + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier + .padding(all = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Button( + onClick = { setOpen(false) } + ) { + val str = when (pickerValue) { + 0 -> "无" + 1 -> "不重要" + 2 -> "一般" + 3 -> "重要" + else -> "bug" + } + Text(str) + } + } + } + + } + + } + + } +} + +@Composable +fun PriorityDialog( + dialogIsOpen: Boolean, + setOpen: (Boolean) -> Unit, + pickerValue: Int, + setPickerVal: (Int) -> Unit +) { + if (dialogIsOpen) { + Dialog( + onDismissRequest = { + setOpen(false) + }, + ) { + Box( + Modifier + .fillMaxWidth(0.8f) + .fillMaxHeight(0.6f) + .background(Color.White) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp), + contentAlignment = Alignment.Center + ) { + NumberPicker( + value = pickerValue, + range = 0..3, + onValueChange = { + setPickerVal(it) + }, + label = { + when (it) { + 0 -> "无" + 1 -> "不紧急" + 2 -> "一般" + 3 -> "紧急" + else -> "bug" + } + } + ) + } + Column(modifier = Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.weight(1f)) + Row( + modifier = Modifier + .padding(all = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + Button( + onClick = { setOpen(false) } + ) { + val str = when (pickerValue) { + 0 -> "无" + 1 -> "不紧急" + 2 -> "一般" + 3 -> "紧急" + else -> "bug" + } + Text(str) + } + } + } + + } + + } + + } +} + + +@Composable +fun AnimatedIconRow( + modifier: Modifier = Modifier, + visible: Boolean = true, + content: @Composable ()->Unit +) { + // remember these specs so they don't restart if recomposing during the animation + // this is required since TweenSpec restarts on interruption + val enter = remember { fadeIn(animationSpec = TweenSpec(300, easing = FastOutLinearInEasing)) } + val exit = remember { fadeOut(animationSpec = TweenSpec(100, easing = FastOutSlowInEasing)) } + Box(modifier.defaultMinSize(minHeight = 16.dp)) { + AnimatedVisibility( + visible = visible, + enter = enter, + exit = exit, + ) { + content() + } + } +} \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/utils/Schedule.kt b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/utils/Schedule.kt index c5cc09cc5cf9efda3a1173958937fb6ba691cbf7..bafb11ad4d8c74728a6057651bd85e7b3eb86024 100644 --- a/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/utils/Schedule.kt +++ b/Src/SlackerKiller/app/src/main/java/org/kannaj/slackerkiller/utils/Schedule.kt @@ -1,9 +1,42 @@ package org.kannaj.slackerkiller.utils +import org.kannaj.slackerkiller.model.eventmodel.Event import java.util.* -fun simpleTrans(time: Long) : String { +fun simpleTrans(time: Long): String { val cal = Calendar.getInstance() cal.timeInMillis = time return cal.time.toString() -} \ No newline at end of file +} + +fun toUTCDay(time: Long): Int { + val cal = Calendar.getInstance().also { + it.clear() + it.timeInMillis = time + } + + return when (cal.get(Calendar.DAY_OF_WEEK)) { + Calendar.MONDAY -> 0 + Calendar.TUESDAY -> 1 + Calendar.WEDNESDAY -> 2 + Calendar.THURSDAY -> 3 + Calendar.FRIDAY ->4 + Calendar.SATURDAY -> 5 + Calendar.SUNDAY -> 6 + else -> 0 + } +} + +fun calDiff(event: Event) : Int{ + val begin = event.beginTime / (1000L * 60) + val end = event.endTime / (1000L * 60) + return (end - begin).toInt() +} + + +fun splitEvent(events: List) = Array(7) { i -> + events.filter { + toUTCDay(it.beginTime) == i + } +} + diff --git a/Src/SlackerKiller/app/src/main/res/drawable/ic_launcher_background.xml b/Src/SlackerKiller/app/src/main/res/drawable/ic_launcher_background.xml index 07d5da9cbf141911847041df5d7b87f0dd5ef9d4..ca3826a46ce070f906d0d3fbe6987df882134381 100644 --- a/Src/SlackerKiller/app/src/main/res/drawable/ic_launcher_background.xml +++ b/Src/SlackerKiller/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index eca70cfe52eac1ba66ba280a68ca7be8fcf88a16..c4a603d4cce78b2fbd8094bd0224d4778bc8c976 100644 --- a/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index eca70cfe52eac1ba66ba280a68ca7be8fcf88a16..c4a603d4cce78b2fbd8094bd0224d4778bc8c976 100644 --- a/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/Src/SlackerKiller/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher.png b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..129229963566dc8003ed0507cb41afaa1264d7db Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78ecd372343283f4157dcfd918ec5165bb3..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..5875099dd65aaee5842e768992c1aa8b3bd7208c Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..bed2ec3017cd09703ead241c5dec88c18bb4911b Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher.png b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..69ca4cfbd1e73d9ba56e66a2d90328625ee3e77b Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d64e58ba64d180ce43ee13bf9a17835fbca..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..9ef765ede2e330a0d9cf95b25665032ffa4fba7b Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..24623c35860b7acd9d433e5a24b9fc3e218b48a9 Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611da081676d42f6c3f78a2c91e7bcedddedb..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..90e7fcfc52525d98dbf696fcfafdcdddbf2f1c89 Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a3070fe34c611c42c0d3ad3013a0dce358be0..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..d736bca23a54b744c3bac989b6aa4cc328f204ca Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..fa534b3d45bfd594aed52f13f9fadba82ed3eae8 Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d57dc257fd2543a72ec73f3ac671b7187300bb3f Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77f9f036a47549d47db79c16788749dca10..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..0133174ca50323a3bae5425c6baf15b7cb2c0c59 Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..60f911a999c0984088cfcb99aea6f8538816533d Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f5083623b375139afb391af71cc533a7dd37..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8f6567db9b54d8efbd090cc52ca481fda0f9f23e Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d6427e6fa1074b79ccd52ef67ac15c5637e85..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..92d964f6b9c29e482686ec47fe733fc496112844 Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8e54fc80fd447dfee5057df40b024a36728e2109 Binary files /dev/null and b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae37cbc3587421d6889eadd1d91fbf1994d4..0000000000000000000000000000000000000000 Binary files a/Src/SlackerKiller/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/Src/SlackerKiller/build.gradle b/Src/SlackerKiller/build.gradle index f28d348eb27511dec32ad5d1dd49de574a36f331..93253acce9c82083b54649fa4d8aad81c92824f6 100644 --- a/Src/SlackerKiller/build.gradle +++ b/Src/SlackerKiller/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext { - compose_version = '1.0.1' + compose_version = '1.0.5' } repositories { google() @@ -9,7 +9,7 @@ buildscript { } dependencies { classpath "com.android.tools.build:gradle:7.0.4" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files